From 9d4e6d69c9d181a6f913f5a46372b7ee72f0e8b6 Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Mon, 1 Jun 2026 10:18:52 +0200 Subject: [PATCH 1/2] Allow reordering terminal tabs via drag and drop Dragging a terminal tab within the same Terminals view now reorders it to the drop position, matching the drag-to-reorder behavior of editor and view tabs. The drop within the same view was previously rejected. The reorder uses the new CTabFolder.moveItem(int, int) API, so the live terminal control stays attached and no longer has to be recreated. The existing cross-view move (which transfers a terminal to a different Terminals view) is unchanged, as it still requires re-parenting. Requires SWT 3.135. Fixes https://github.com/eclipse-platform/eclipse.platform/issues/2679 --- .../META-INF/MANIFEST.MF | 3 +- .../view/ui/internal/view/TerminalsView.java | 70 ++++++++++- .../META-INF/MANIFEST.MF | 3 +- .../terminal/test/AutomatedTestSuite.java | 1 + .../ui/tests/TerminalsViewReorderTest.java | 111 ++++++++++++++++++ 5 files changed, 180 insertions(+), 8 deletions(-) create mode 100644 terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/ui/tests/TerminalsViewReorderTest.java diff --git a/terminal/bundles/org.eclipse.terminal.view.ui/META-INF/MANIFEST.MF b/terminal/bundles/org.eclipse.terminal.view.ui/META-INF/MANIFEST.MF index 76a78133ca0..e8dfd9f7c67 100644 --- a/terminal/bundles/org.eclipse.terminal.view.ui/META-INF/MANIFEST.MF +++ b/terminal/bundles/org.eclipse.terminal.view.ui/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.terminal.view.ui;singleton:=true -Bundle-Version: 1.1.100.qualifier +Bundle-Version: 1.1.200.qualifier Bundle-Activator: org.eclipse.terminal.view.ui.internal.UIPlugin Bundle-Vendor: %providerName Require-Bundle: org.eclipse.core.expressions;bundle-version="[3.9.0,4.0.0)", @@ -10,6 +10,7 @@ Require-Bundle: org.eclipse.core.expressions;bundle-version="[3.9.0,4.0.0)", org.eclipse.core.resources;bundle-version="[3.22.0,4.0.0)";resolution:=optional, org.eclipse.core.variables;bundle-version="[3.6.0,4.0.0)", org.eclipse.debug.ui;bundle-version="[3.18.0,4.0.0)";resolution:=optional, + org.eclipse.swt;bundle-version="[3.135.0,4.0.0)", org.eclipse.ui;bundle-version="[3.208.0,4.0.0)", org.eclipse.ui.ide;bundle-version="[3.22.0,4.0.0)";resolution:=optional, org.eclipse.ui.editors;bundle-version="[3.20.0,4.0.0)";resolution:=optional, diff --git a/terminal/bundles/org.eclipse.terminal.view.ui/src/org/eclipse/terminal/view/ui/internal/view/TerminalsView.java b/terminal/bundles/org.eclipse.terminal.view.ui/src/org/eclipse/terminal/view/ui/internal/view/TerminalsView.java index ef41338cbd9..f9faac493d4 100644 --- a/terminal/bundles/org.eclipse.terminal.view.ui/src/org/eclipse/terminal/view/ui/internal/view/TerminalsView.java +++ b/terminal/bundles/org.eclipse.terminal.view.ui/src/org/eclipse/terminal/view/ui/internal/view/TerminalsView.java @@ -261,12 +261,9 @@ private void addDropSupport() { target.addDropListener(new DropTargetListener() { @Override public void dragEnter(DropTargetEvent event) { - // only if the drop target is different then the drag source - if (TerminalTransfer.getInstance().getTabFolderManager() == tabFolderManager) { - event.detail = DND.DROP_NONE; - } else { - event.detail = DND.DROP_MOVE; - } + // Accept the move both for a different terminals view (the terminal is moved + // to the other view) and for the same view (the tab is reordered). + event.detail = DND.DROP_MOVE; } @Override @@ -290,6 +287,12 @@ public void drop(DropTargetEvent event) { if (TerminalTransfer.getInstance().getDraggedFolderItem() != null && tabFolderManager != null) { CTabItem draggedItem = TerminalTransfer.getInstance().getDraggedFolderItem(); + // Drop within the same terminals view: reorder the dragged tab in place. + if (TerminalTransfer.getInstance().getTabFolderManager() == tabFolderManager) { + reorderTabItem(draggedItem, event.x, event.y); + return; + } + CTabItem item = tabFolderManager.cloneTabItemAfterDrop(draggedItem); tabFolderManager.bringToTop(item); switchToTabFolderControl(); @@ -313,6 +316,61 @@ public void drop(DropTargetEvent event) { }); } + /** + * Reorder the dragged tab item within its own tab folder so that it is dropped at the position + * the mouse points to. + * + * @param draggedItem the tab item being dragged, must not be null. + * @param x the x coordinate of the drop, in display-relative coordinates. + * @param y the y coordinate of the drop, in display-relative coordinates. + */ + private void reorderTabItem(CTabItem draggedItem, int x, int y) { + if (tabFolderControl == null || tabFolderControl.isDisposed()) { + return; + } + + int from = tabFolderControl.indexOf(draggedItem); + if (from == -1) { + return; + } + + // Map the display-relative drop coordinates to the tab folder and find the tab below them. + // A drop next to the tabs (e.g. on the trailing empty space) targets the last position. + Point point = tabFolderControl.toControl(x, y); + CTabItem targetItem = tabFolderControl.getItem(point); + int indexUnderCursor = targetItem != null ? tabFolderControl.indexOf(targetItem) : -1; + + int to = computeReorderIndex(from, indexUnderCursor, tabFolderControl.getItemCount()); + if (to != -1) { + tabFolderControl.moveItem(from, to); + } + + // Keep the moved terminal selected and focused. + tabFolderManager.bringToTop(draggedItem); + setFocus(); + } + + /** + * Computes the destination index for a tab reorder triggered by a drop. + *

+ * This method is internal and only exposed for testing. + *

+ * + * @param from the current index of the dragged tab. + * @param indexUnderCursor the index of the tab below the drop location, or -1 if the + * drop did not happen over a tab (for example on the empty space following the last tab). + * @param itemCount the total number of tabs in the folder. + * @return the index the dragged tab should be moved to, or -1 if no move is required + * because the tab would keep its position. + */ + public static int computeReorderIndex(int from, int indexUnderCursor, int itemCount) { + int to = indexUnderCursor != -1 ? indexUnderCursor : itemCount - 1; + if (to < 0 || to == from) { + return -1; + } + return to; + } + @Override public void dispose() { // Dispose the tab folder manager diff --git a/terminal/tests/org.eclipse.terminal.test/META-INF/MANIFEST.MF b/terminal/tests/org.eclipse.terminal.test/META-INF/MANIFEST.MF index f7415294243..09adac293ec 100644 --- a/terminal/tests/org.eclipse.terminal.test/META-INF/MANIFEST.MF +++ b/terminal/tests/org.eclipse.terminal.test/META-INF/MANIFEST.MF @@ -8,7 +8,8 @@ Bundle-Localization: plugin Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.33.0,4)", org.eclipse.ui;bundle-version="[3.208.0,4)", org.opentest4j;bundle-version="[1.3.0,2)", - org.eclipse.terminal.control;bundle-version="1.0.0" + org.eclipse.terminal.control;bundle-version="1.0.0", + org.eclipse.terminal.view.ui;bundle-version="[1.1.200,2.0.0)" Bundle-RequiredExecutionEnvironment: JavaSE-21 Export-Package: org.eclipse.terminal.internal.connector;x-internal:=true, org.eclipse.terminal.internal.emulator;x-internal:=true, diff --git a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/test/AutomatedTestSuite.java b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/test/AutomatedTestSuite.java index d07b27e91f6..c146029c61a 100644 --- a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/test/AutomatedTestSuite.java +++ b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/test/AutomatedTestSuite.java @@ -25,6 +25,7 @@ org.eclipse.terminal.model.AllTestSuite.class, // org.eclipse.terminal.internal.connector.TerminalConnectorTest.class, // org.eclipse.terminal.internal.connector.TerminalToRemoteInjectionOutputStreamTest.class, // + org.eclipse.terminal.view.ui.tests.TerminalsViewReorderTest.class, // }) public class AutomatedTestSuite { diff --git a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/ui/tests/TerminalsViewReorderTest.java b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/ui/tests/TerminalsViewReorderTest.java new file mode 100644 index 00000000000..95c412e8eaa --- /dev/null +++ b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/ui/tests/TerminalsViewReorderTest.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2026 Eclipse contributors and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.terminal.view.ui.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CTabFolder; +import org.eclipse.swt.custom.CTabItem; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.terminal.view.ui.internal.view.TerminalsView; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Tests for reordering terminal tabs in the terminals view (see + * issue 2679). + */ +public class TerminalsViewReorderTest { + + private static Display display = null; + + @BeforeAll + public static void createDisplay() { + if (Display.getCurrent() == null) { + display = new Display(); + } + } + + @AfterAll + public static void disposeDisplay() { + if (display != null) { + display.dispose(); + display = null; + } + } + + @Test + public void dropOverAnotherTabTargetsThatTab() { + assertEquals(2, TerminalsView.computeReorderIndex(0, 2, 4)); + } + + @Test + public void dropOverItselfIsNoOp() { + assertEquals(-1, TerminalsView.computeReorderIndex(2, 2, 4)); + } + + @Test + public void dropNextToTheTabsTargetsTheLastPosition() { + assertEquals(3, TerminalsView.computeReorderIndex(1, -1, 4)); + } + + @Test + public void dropNextToTheTabsWhileAlreadyLastIsNoOp() { + assertEquals(-1, TerminalsView.computeReorderIndex(3, -1, 4)); + } + + @Test + public void singleTabIsNeverReordered() { + assertEquals(-1, TerminalsView.computeReorderIndex(0, -1, 1)); + } + + /** + * Verifies the {@link CTabFolder#moveItem(int, int)} contract the reorder feature relies on: + * the items are reordered and the previously selected item stays selected. + */ + @Test + public void moveItemReordersAndKeepsSelection() { + Shell shell = new Shell(display); + try { + CTabFolder folder = new CTabFolder(shell, SWT.NONE); + CTabItem a = newItem(folder, "A"); + CTabItem b = newItem(folder, "B"); + newItem(folder, "C"); + newItem(folder, "D"); + + folder.setSelection(b); + + // Move "A" (index 0) to position 2: expected order is B, C, A, D. + folder.moveItem(0, 2); + + assertEquals("B", folder.getItem(0).getText()); + assertEquals("C", folder.getItem(1).getText()); + assertEquals("A", folder.getItem(2).getText()); + assertEquals("D", folder.getItem(3).getText()); + assertEquals(2, folder.indexOf(a)); + + // The selected item is unchanged even though its index moved. + assertSame(b, folder.getSelection()); + } finally { + shell.dispose(); + } + } + + private static CTabItem newItem(CTabFolder folder, String text) { + CTabItem item = new CTabItem(folder, SWT.CLOSE); + item.setText(text); + return item; + } +} From 75a1ea4c039c4529052ced9839a8c121b86134f3 Mon Sep 17 00:00:00 2001 From: Eclipse Platform Bot Date: Wed, 3 Jun 2026 08:17:43 +0000 Subject: [PATCH 2/2] Version bump(s) for 4.41 stream --- terminal/features/org.eclipse.terminal.feature/feature.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminal/features/org.eclipse.terminal.feature/feature.xml b/terminal/features/org.eclipse.terminal.feature/feature.xml index 94b37131c25..044c0b1ba2c 100644 --- a/terminal/features/org.eclipse.terminal.feature/feature.xml +++ b/terminal/features/org.eclipse.terminal.feature/feature.xml @@ -2,7 +2,7 @@