From d2732928670340c8d81e965e90aee162a73cdd66 Mon Sep 17 00:00:00 2001 From: Konstantin Hudyakov Date: Fri, 10 Oct 2025 18:42:55 +0300 Subject: [PATCH] [toolwindow] IJPL-59194 Create better API for allowing tab splits Let's add the methods to the `ToolWindow` interface. Deprecate and replace the usages of the old API. GitOrigin-RevId: 3aeea95849457fba42faf782a3c1561daebf32dc --- .../execution/ui/RunContentManagerImpl.kt | 3 +-- platform/ide-core/api-dump-experimental.txt | 4 ++++ .../com/intellij/openapi/wm/ToolWindow.java | 13 +++++++++++++ .../ToolWindowMoveToSplitterActions.kt | 5 +---- .../ide/actions/ToolWindowSplitActions.kt | 3 +-- .../actions/ToolWindowSplitAndMoveActions.kt | 3 +-- .../ide/actions/ToolWindowUnsplitAction.kt | 4 +--- .../openapi/wm/impl/ToolWindowImpl.kt | 15 +++++++++++++++ .../wm/impl/content/ToolWindowContentUi.java | 19 ++++++++++--------- .../toolWindow/InternalDecoratorImpl.kt | 3 +-- .../intellij/toolWindow/ToolWindowHeader.kt | 14 +++----------- .../toolWindow/ToolWindowInnerDragHelper.kt | 2 +- .../impl/TerminalToolWindowTabsManagerImpl.kt | 2 +- 13 files changed, 53 insertions(+), 37 deletions(-) diff --git a/platform/execution-impl/src/com/intellij/execution/ui/RunContentManagerImpl.kt b/platform/execution-impl/src/com/intellij/execution/ui/RunContentManagerImpl.kt index f7ce8999525f..12784d0ba0d2 100644 --- a/platform/execution-impl/src/com/intellij/execution/ui/RunContentManagerImpl.kt +++ b/platform/execution-impl/src/com/intellij/execution/ui/RunContentManagerImpl.kt @@ -34,7 +34,6 @@ import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ToolWindowManager import com.intellij.openapi.wm.ex.ToolWindowManagerListener import com.intellij.openapi.wm.impl.content.SingleContentSupplier -import com.intellij.openapi.wm.impl.content.ToolWindowContentUi import com.intellij.ui.ExperimentalUI import com.intellij.ui.content.* import com.intellij.ui.content.Content.CLOSE_LISTENER_KEY @@ -176,7 +175,7 @@ class RunContentManagerImpl(private val project: Project) : RunContentManager { )) toolWindow.setToHideOnEmptyContent(true) if (DefaultRunExecutor.EXECUTOR_ID == executor.id || Registry.`is`("debugger.new.tool.window.layout.dnd", false)) { - ToolWindowContentUi.setAllowTabsReordering(toolWindow, true) + toolWindow.setTabsSplittingAllowed(true) } val contentManager = toolWindow.contentManager contentManager.addUiDataProvider { sink -> diff --git a/platform/ide-core/api-dump-experimental.txt b/platform/ide-core/api-dump-experimental.txt index d8b1bf092240..be41fbe3adea 100644 --- a/platform/ide-core/api-dump-experimental.txt +++ b/platform/ide-core/api-dump-experimental.txt @@ -63,6 +63,10 @@ f:com.intellij.openapi.vfs.FilePermissionsKt - com.intellij.openapi.wm.WidgetPresentation - a:getAlignment():F - a:text():kotlinx.coroutines.flow.Flow +com.intellij.openapi.wm.ToolWindow +- com.intellij.openapi.util.BusyObject +- *:canSplitTabs():Z +- *:setTabsSplittingAllowed(Z):V *:com.intellij.openapi.wm.WidgetPresentation - getClickConsumer():kotlin.jvm.functions.Function1 - getShortcutText(kotlin.coroutines.Continuation):java.lang.Object diff --git a/platform/ide-core/src/com/intellij/openapi/wm/ToolWindow.java b/platform/ide-core/src/com/intellij/openapi/wm/ToolWindow.java index dae14f93e5bf..5c21c7318a8c 100644 --- a/platform/ide-core/src/com/intellij/openapi/wm/ToolWindow.java +++ b/platform/ide-core/src/com/intellij/openapi/wm/ToolWindow.java @@ -246,4 +246,17 @@ public interface ToolWindow extends BusyObject { void setAdditionalGearActions(@Nullable ActionGroup additionalGearActions); @NotNull Project getProject(); + + /** + * @return whether the user can be able to split the tool window pane into separate views with tool window tabs. + * For example, using drag and drop functionality or Split Right/Left actions. + * Useful for tool windows that can contain several contents, for example, Run and Terminal. + */ + @ApiStatus.Experimental + default boolean canSplitTabs() { + return false; + } + + @ApiStatus.Experimental + default void setTabsSplittingAllowed(boolean allowed) { } } diff --git a/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowMoveToSplitterActions.kt b/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowMoveToSplitterActions.kt index 69d6b87c7c4a..55b76270ada9 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowMoveToSplitterActions.kt +++ b/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowMoveToSplitterActions.kt @@ -6,7 +6,6 @@ import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehaviorSpecification import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ToolWindowContextMenuActionBase -import com.intellij.openapi.wm.impl.content.ToolWindowContentUi import com.intellij.toolWindow.InternalDecoratorImpl import com.intellij.ui.content.Content @@ -26,9 +25,7 @@ internal abstract class ToolWindowMoveToSplitterAction( val component = e.getData(PlatformDataKeys.CONTEXT_COMPONENT) val topDecorator = InternalDecoratorImpl.findTopLevelDecorator(component) e.presentation.isEnabled = topDecorator?.mode?.isSplit == true - e.presentation.isVisible = (e.presentation.isEnabled || !e.isFromContextMenu) && - ToolWindowContentUi.isTabsReorderingAllowed(toolWindow) - + e.presentation.isVisible = (e.presentation.isEnabled || !e.isFromContextMenu) && toolWindow.canSplitTabs() } } diff --git a/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowSplitActions.kt b/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowSplitActions.kt index 383d2e791a62..bfc6e4402484 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowSplitActions.kt +++ b/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowSplitActions.kt @@ -5,7 +5,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehaviorSpecification import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ToolWindowContextMenuActionBase -import com.intellij.openapi.wm.impl.content.ToolWindowContentUi import com.intellij.toolWindow.ToolWindowSplitContentProviderBean import com.intellij.ui.content.Content import javax.swing.SwingConstants @@ -23,7 +22,7 @@ internal abstract class ToolWindowSplitActionBase( } override fun update(e: AnActionEvent, toolWindow: ToolWindow, content: Content?) { - e.presentation.isEnabledAndVisible = ToolWindowContentUi.isTabsReorderingAllowed(toolWindow) && + e.presentation.isEnabledAndVisible = toolWindow.canSplitTabs() && ToolWindowSplitContentProviderBean.getForToolWindow(toolWindow.id) != null } } diff --git a/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowSplitAndMoveActions.kt b/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowSplitAndMoveActions.kt index 094398560e20..5a4f12eb4dad 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowSplitAndMoveActions.kt +++ b/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowSplitAndMoveActions.kt @@ -5,7 +5,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehaviorSpecification import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ToolWindowContextMenuActionBase -import com.intellij.openapi.wm.impl.content.ToolWindowContentUi import com.intellij.ui.content.Content import javax.swing.SwingConstants @@ -20,7 +19,7 @@ internal abstract class ToolWindowSplitAndMoveActionBase( override fun update(e: AnActionEvent, toolWindow: ToolWindow, content: Content?) { val contentManager = content?.manager - e.presentation.isEnabledAndVisible = ToolWindowContentUi.isTabsReorderingAllowed(toolWindow) && + e.presentation.isEnabledAndVisible = toolWindow.canSplitTabs() && contentManager != null && contentManager.contentCount > 1 } } diff --git a/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowUnsplitAction.kt b/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowUnsplitAction.kt index 200609c5afee..b04a286e7378 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowUnsplitAction.kt +++ b/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowUnsplitAction.kt @@ -5,7 +5,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehaviorSpecification import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ToolWindowContextMenuActionBase -import com.intellij.openapi.wm.impl.content.ToolWindowContentUi import com.intellij.ui.content.Content internal class ToolWindowUnsplitAction : ToolWindowContextMenuActionBase(), ActionRemoteBehaviorSpecification.Frontend { @@ -19,7 +18,6 @@ internal class ToolWindowUnsplitAction : ToolWindowContextMenuActionBase(), Acti override fun update(e: AnActionEvent, toolWindow: ToolWindow, content: Content?) { val decorator = findNearestDecorator(e) e.presentation.isEnabled = decorator != null && decorator.canUnsplit() - e.presentation.isVisible = (e.presentation.isEnabled || !e.isFromContextMenu) - && ToolWindowContentUi.isTabsReorderingAllowed(toolWindow) + e.presentation.isVisible = (e.presentation.isEnabled || !e.isFromContextMenu) && toolWindow.canSplitTabs() } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowImpl.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowImpl.kt index 9fdd9404dcaa..5d8ef1cd5243 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowImpl.kt +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowImpl.kt @@ -133,6 +133,8 @@ import kotlin.math.abs internal var icon: Icon? = null + private var tabsSplittingAllowed: Boolean = false + private val contentManager = SynchronizedClearableLazy { val result = createContentManager() if (toolWindowManager.isNewUi) { @@ -639,6 +641,19 @@ import kotlin.math.abs contentUi?.update() } + override fun canSplitTabs(): Boolean { + return tabsSplittingAllowed + } + + override fun setTabsSplittingAllowed(allowed: Boolean) { + tabsSplittingAllowed = allowed + + val header = decorator?.header ?: return + if (header.isShowing) { + header.manageWestPanelTabComponentAndToolbar(true) + } + } + fun fireActivated(source: ToolWindowEventSource) { toolWindowManager.activated(this, source) } diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/content/ToolWindowContentUi.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/content/ToolWindowContentUi.java index 0eada08cb9e7..b4fd7bcd48b4 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/content/ToolWindowContentUi.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/content/ToolWindowContentUi.java @@ -581,7 +581,7 @@ public final class ToolWindowContentUi implements ContentUI, UiCompatibleDataPro group.add(actionManager.getAction("TW.CloseAllTabs")); group.add(actionManager.getAction("TW.CloseOtherTabs")); group.addSeparator(); - if (isTabsReorderingAllowed(window)) { + if (window.canSplitTabs()) { group.add(actionManager.getAction("TW.SplitRight")); group.add(actionManager.getAction("TW.SplitAndMoveRight")); group.add(actionManager.getAction("TW.SplitDown")); @@ -799,28 +799,29 @@ public final class ToolWindowContentUi implements ContentUI, UiCompatibleDataPro } /** - * @deprecated please use {@link ToolWindowContentUi#setAllowTabsReordering(ToolWindow, boolean)} instead. + * @deprecated please use {@link ToolWindow#setTabsSplittingAllowed(boolean)} instead. */ @Deprecated public static final @NonNls Key ALLOW_DND_FOR_TABS = Key.create("AllowDragAndDropForTabs"); - @ApiStatus.Internal - public static final Key ALLOW_TABS_REORDERING = ALLOW_DND_FOR_TABS; - /** - * If {@code allow} parameter is specified as {@code true} then it will be possible to reorder and split + * @deprecated please use {@link ToolWindow#setTabsSplittingAllowed(boolean)} instead. + * If {@code allow} parameter is specified as {@code true} then it will be possible to split * tabs of the provided tool window using drag and drop and specific actions, such as * {@link com.intellij.ide.actions.ToolWindowSplitRightAction}. */ + @Deprecated public static void setAllowTabsReordering(@NotNull ToolWindow toolWindow, boolean allow) { - toolWindow.getComponent().putClientProperty(ALLOW_TABS_REORDERING, allow); + toolWindow.setTabsSplittingAllowed(allow); } /** - * @return whether reorder and split of tabs in the provided tool window is allowed. + * @deprecated please use {@link ToolWindow#setTabsSplittingAllowed(boolean)} instead. + * @return whether splitting of tabs in the provided tool window is allowed. */ + @Deprecated public static boolean isTabsReorderingAllowed(@NotNull ToolWindow window) { - return ClientProperty.isTrue(window.getComponent(), ALLOW_TABS_REORDERING); + return window.canSplitTabs(); } private static final Key TOOLWINDOW_IN_EDITOR_SUPPORT = Key.create("ToolWindowInEditorSupport"); diff --git a/platform/platform-impl/src/com/intellij/toolWindow/InternalDecoratorImpl.kt b/platform/platform-impl/src/com/intellij/toolWindow/InternalDecoratorImpl.kt index 1b1a6ec6001d..91c7303a04ef 100644 --- a/platform/platform-impl/src/com/intellij/toolWindow/InternalDecoratorImpl.kt +++ b/platform/platform-impl/src/com/intellij/toolWindow/InternalDecoratorImpl.kt @@ -933,8 +933,7 @@ class InternalDecoratorImpl internal constructor( } contentUi.update() - if ((toolWindow.type == ToolWindowType.WINDOWED || toolWindow.type == ToolWindowType.FLOATING) && - ToolWindowContentUi.isTabsReorderingAllowed(toolWindow)) { + if ((toolWindow.type == ToolWindowType.WINDOWED || toolWindow.type == ToolWindowType.FLOATING) && toolWindow.canSplitTabs()) { ToolWindowInnerDragHelper(disposable, this).start() } } diff --git a/platform/platform-impl/src/com/intellij/toolWindow/ToolWindowHeader.kt b/platform/platform-impl/src/com/intellij/toolWindow/ToolWindowHeader.kt index 4b229867d97b..0ff4194fd739 100644 --- a/platform/platform-impl/src/com/intellij/toolWindow/ToolWindowHeader.kt +++ b/platform/platform-impl/src/com/intellij/toolWindow/ToolWindowHeader.kt @@ -33,8 +33,6 @@ import org.jetbrains.annotations.ApiStatus import java.awt.* import java.awt.event.MouseAdapter import java.awt.event.MouseEvent -import java.beans.PropertyChangeEvent -import java.beans.PropertyChangeListener import java.util.function.Supplier import javax.swing.* import javax.swing.GroupLayout.DEFAULT_SIZE @@ -46,8 +44,8 @@ import javax.swing.event.PopupMenuListener abstract class ToolWindowHeader internal constructor( private val toolWindow: ToolWindowImpl, private val contentUi: ToolWindowContentUi, - private val gearProducer: Supplier -) : BorderLayoutPanel(), PropertyChangeListener { + private val gearProducer: Supplier, +) : BorderLayoutPanel() { @ApiStatus.Internal companion object { @@ -231,7 +229,7 @@ abstract class ToolWindowHeader internal constructor( ) } - private fun manageWestPanelTabComponentAndToolbar(init: Boolean) { + internal fun manageWestPanelTabComponentAndToolbar(init: Boolean) { if (init) { westPanel.setComponents(contentUi.tabComponent, sideComponent) contentUi.connectTabToolbar() @@ -242,18 +240,12 @@ abstract class ToolWindowHeader internal constructor( } } - override fun propertyChange(evt: PropertyChangeEvent?) { - manageWestPanelTabComponentAndToolbar(true) - } - override fun addNotify() { super.addNotify() - toolWindow.component.addPropertyChangeListener(ToolWindowContentUi.ALLOW_TABS_REORDERING.toString(), this) manageWestPanelTabComponentAndToolbar(true) } override fun removeNotify() { - toolWindow.component.removePropertyChangeListener(ToolWindowContentUi.ALLOW_TABS_REORDERING.toString(), this) super.removeNotify() manageWestPanelTabComponentAndToolbar(false) } diff --git a/platform/platform-impl/src/com/intellij/toolWindow/ToolWindowInnerDragHelper.kt b/platform/platform-impl/src/com/intellij/toolWindow/ToolWindowInnerDragHelper.kt index f5632c3609e4..54f0502c5ad6 100644 --- a/platform/platform-impl/src/com/intellij/toolWindow/ToolWindowInnerDragHelper.kt +++ b/platform/platform-impl/src/com/intellij/toolWindow/ToolWindowInnerDragHelper.kt @@ -55,7 +55,7 @@ internal class ToolWindowInnerDragHelper(parent: Disposable, val pane: JComponen val decorator = InternalDecoratorImpl.findTopLevelDecorator(child) val editorSupport = getEditorSupport(decorator) if (decorator != null && - ToolWindowContentUi.isTabsReorderingAllowed(decorator.toolWindow) && + decorator.toolWindow.canSplitTabs() && child is ContentTabLabel && (child.parent is ToolWindowContentUi.TabPanel || Registry.`is`("debugger.new.tool.window.layout.dnd", false) && child.parent is SingleContentLayout.TabAdapter) && diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/toolwindow/impl/TerminalToolWindowTabsManagerImpl.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/toolwindow/impl/TerminalToolWindowTabsManagerImpl.kt index e522067b1d08..4d47846fa17d 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/toolwindow/impl/TerminalToolWindowTabsManagerImpl.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/toolwindow/impl/TerminalToolWindowTabsManagerImpl.kt @@ -337,7 +337,7 @@ internal class TerminalToolWindowTabsManagerImpl( val toolWindowActions = ActionManager.getInstance().getAction("Terminal.ToolWindowActions") as? ActionGroup toolWindow.setAdditionalGearActions(toolWindowActions) - ToolWindowContentUi.setAllowTabsReordering(toolWindow, true) + toolWindow.setTabsSplittingAllowed(true) ToolWindowContentUi.setToolWindowInEditorSupport(toolWindow, TerminalInEditorSupport()) if (toolWindow is ToolWindowEx) {