[ByteCodeViewer] IDEA-375222 Fully match the "tab name deduplication behavior" of the main editor

Previously, the behavior was mostly correct but there were edge cases, for example if 'Show Bytecode' action was triggered for the following files which were open in the main editor:
- classes/java/org/example/Main.java
- classes/java/main/simple1/Main.java
- classes/java/main/simple1/Main.java

Then the titles of tabs in the main editor were (correct):
- example/Main.java
- simple1/Main.java
- simple2/Main.java

But the titles of tabs in the bytecode viewer tool window were (wrong):
- org/example/Main.class
- simple1/Main.class
- simple2/Main.class

But after this commit, the names of tabs in the bytecode viewer tool window are (correct):
- example/Main.class
- simple1/Main.class
- simple2/Main.class

GitOrigin-RevId: 2c903dab6fccdf08ded1f09119d46307b587d59e
This commit is contained in:
Bartek Pacia
2025-07-02 16:12:27 +00:00
committed by intellij-monorepo-bot
parent d61e2115c7
commit 0f551270ac
@@ -1,13 +1,14 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.byteCodeViewer
import com.intellij.filename.UniqueNameBuilder
import com.intellij.ide.ui.UISettings
import com.intellij.openapi.components.Service
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.wm.ToolWindow
import com.intellij.ui.content.Content
import com.intellij.ui.content.ContentManagerEvent
import com.intellij.ui.content.ContentManagerListener
import java.util.concurrent.ConcurrentHashMap
@@ -61,31 +62,28 @@ class BytecodeToolWindowService(private val project: Project) {
* Updates tab titles to avoid duplicate names by adding path information when necessary.
*/
fun deduplicateTabNames(toolWindow: ToolWindow) {
val titlesToContents = mutableMapOf<String, MutableList<Content>>()
for (content in toolWindow.contentManager.contents) {
val classFileName = content.getUserData(JAVA_CLASS_FILE)?.name
?: throw IllegalStateException("Content entry has no JAVA_CLASS_FILE or it is null. Entry: $content")
titlesToContents.getOrPut(classFileName) { mutableListOf() }.add(content)
val javaClassFiles = toolWindow.contentManager.contents.map { content ->
content.getUserData(JAVA_CLASS_FILE) ?: throw IllegalStateException("Content has no JAVA_CLASS_FILE or it is null. Content: $content")
}
for ((classFileName, contents) in titlesToContents) {
if (contents.size == 1) {
contents[0].displayName = classFileName
}
else if (contents.size > 1) {
val paths = contents.map {
it.getUserData(JAVA_CLASS_FILE) ?: throw IllegalStateException("No class file path for content entry $it")
}
val commonAncestor = VfsUtil.getCommonAncestor(paths) ?: continue
if (javaClassFiles.size == 1) {
toolWindow.contentManager.contents[0].displayName = javaClassFiles[0].name
return
}
for (i in contents.indices) {
val content = contents[i]
content.displayName = VfsUtil.getRelativePath(
content.getUserData(JAVA_CLASS_FILE) ?: throw IllegalStateException("No class file path for content entry $content"),
commonAncestor,
)
}
}
val commonAncestor = VfsUtil.getCommonAncestor(javaClassFiles)?.path ?: return
val uniqueNameBuilder = UniqueNameBuilder<String>(commonAncestor, "/")
for (javaClassFile in javaClassFiles) {
uniqueNameBuilder.addPath(javaClassFile.path, javaClassFile.path)
}
for (content in toolWindow.contentManager.contents) {
val javaClassFile = content.getUserData(JAVA_CLASS_FILE)
?: throw IllegalStateException("Content has no JAVA_CLASS_FILE or it is null. Content: $content")
@NlsSafe val displayName = uniqueNameBuilder.getShortPath(javaClassFile.path)
?: throw IllegalStateException("Cannot get short path for ${javaClassFile.path}")
content.displayName = displayName
}
}