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/build.properties b/terminal/tests/org.eclipse.terminal.test/build.properties
index 8dc3df69b6b..b160f62e77d 100644
--- a/terminal/tests/org.eclipse.terminal.test/build.properties
+++ b/terminal/tests/org.eclipse.terminal.test/build.properties
@@ -21,3 +21,5 @@ bin.includes = META-INF/,\
src.includes = teamConfig/,\
about.html
pom.model.property.testClass = org.eclipse.terminal.test.AutomatedIntegrationSuite
+pom.model.property.tycho.surefire.useUIHarness = true
+pom.model.property.tycho.surefire.useUIThread = 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;
+ }
+}