[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
This commit is contained in:
Konstantin Hudyakov
2025-10-14 20:21:33 +00:00
committed by intellij-monorepo-bot
parent 9d431accc3
commit d273292867
13 changed files with 53 additions and 37 deletions
@@ -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 ->
@@ -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
@@ -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) { }
}
@@ -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()
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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()
}
}
@@ -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)
}
@@ -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<Boolean> ALLOW_DND_FOR_TABS = Key.create("AllowDragAndDropForTabs");
@ApiStatus.Internal
public static final Key<Boolean> 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<ToolWindowInEditorSupport> TOOLWINDOW_IN_EDITOR_SUPPORT = Key.create("ToolWindowInEditorSupport");
@@ -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()
}
}
@@ -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<ActionGroup>
) : BorderLayoutPanel(), PropertyChangeListener {
private val gearProducer: Supplier<ActionGroup>,
) : 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)
}
@@ -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) &&
@@ -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) {