From 4f13fc2ae52285e72ce01191a05b131e42c4aa07 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A9lios=20GILLES?=
Date: Thu, 30 Jul 2026 19:28:52 +0200
Subject: [PATCH 1/6] Add Ctrl+click support for file:line:column references in
terminal
Introduce FileReference record and FileReferenceParser to handle
path:line:column references displayed in the terminal (e.g., from
'git grep -n' output).
- FileReferenceParser parses hover text into path + optional line/column
- OpenFileMouseHandler uses it instead of inline regex parsing
- Both classes in view.core.internal (x-internal, no new public API)
- 25 unit tests cover plain paths, :line, :line:col, Windows paths,
edge cases (zero values, URLs, timestamps, Maven coordinates)
---
.../view/core/internal/FileReference.java | 36 +++
.../core/internal/FileReferenceParser.java | 82 +++++++
.../internal/tabs/OpenFileMouseHandler.java | 88 +++----
.../META-INF/MANIFEST.MF | 3 +-
.../terminal/test/AutomatedTestSuite.java | 2 +
.../core/tests/FileReferenceParserTest.java | 225 ++++++++++++++++++
.../view/core/tests/FileReferenceTest.java | 58 +++++
7 files changed, 434 insertions(+), 60 deletions(-)
create mode 100644 terminal/bundles/org.eclipse.terminal.view.core/src/org/eclipse/terminal/view/core/internal/FileReference.java
create mode 100644 terminal/bundles/org.eclipse.terminal.view.core/src/org/eclipse/terminal/view/core/internal/FileReferenceParser.java
create mode 100644 terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceParserTest.java
create mode 100644 terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceTest.java
diff --git a/terminal/bundles/org.eclipse.terminal.view.core/src/org/eclipse/terminal/view/core/internal/FileReference.java b/terminal/bundles/org.eclipse.terminal.view.core/src/org/eclipse/terminal/view/core/internal/FileReference.java
new file mode 100644
index 00000000000..a874d78d714
--- /dev/null
+++ b/terminal/bundles/org.eclipse.terminal.view.core/src/org/eclipse/terminal/view/core/internal/FileReference.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Fabrizio Iannetti 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.core.internal;
+
+/**
+ * An immutable value object representing a file reference that may include
+ * a line and column position.
+ *
+ * @param path the file path
+ * @param line the 1-based line number, or {@code -1} if absent
+ * @param column the 1-based column number, or {@code -1} if absent
+ */
+public record FileReference(String path, int line, int column) {
+
+ /**
+ * @return {@code true} if this reference contains a valid line number
+ */
+ public boolean hasLine() {
+ return line > 0;
+ }
+
+ /**
+ * @return {@code true} if this reference contains a valid column number
+ */
+ public boolean hasColumn() {
+ return column > 0;
+ }
+}
diff --git a/terminal/bundles/org.eclipse.terminal.view.core/src/org/eclipse/terminal/view/core/internal/FileReferenceParser.java b/terminal/bundles/org.eclipse.terminal.view.core/src/org/eclipse/terminal/view/core/internal/FileReferenceParser.java
new file mode 100644
index 00000000000..16c0521bb98
--- /dev/null
+++ b/terminal/bundles/org.eclipse.terminal.view.core/src/org/eclipse/terminal/view/core/internal/FileReferenceParser.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Fabrizio Iannetti 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.core.internal;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Parses terminal hover text to extract a {@link FileReference} containing
+ * the file path and optional line and column numbers.
+ *
+ * Supported formats:
+ *
+ * - {@code path}
+ * - {@code path:line}
+ * - {@code path:line:}
+ * - {@code path:line:column}
+ * - {@code path:line:column:}
+ *
+ * Line and column numbers must be strictly positive (≥ 1).
+ * Windows drive letters ({@code C:\...}) are correctly distinguished
+ * from the {@code :line} suffix.
+ *
+ *
+ * This class is not intended to be instantiated or subclassed by clients.
+ *
+ */
+public class FileReferenceParser {
+
+ private static final Pattern COLON_LINE_COL_PATTERN = Pattern
+ .compile("^(.+):([1-9]\\d*):([1-9]\\d*):?$"); //$NON-NLS-1$
+ private static final Pattern COLON_LINE_PATTERN = Pattern.compile("^(.+):([1-9]\\d*):?$"); //$NON-NLS-1$
+
+ private FileReferenceParser() {
+ }
+
+ /**
+ * Parses the given text into a {@link FileReference}.
+ *
+ * Never returns {@code null}. If no line/column suffix is detected,
+ * the result contains the whole input as the path with line and column
+ * set to {@code -1}.
+ *
+ *
+ * @param text the hover selection text, may be {@code null}
+ * @return a {@link FileReference}, never {@code null}
+ */
+ public static FileReference parse(String text) {
+ if (text == null || text.isEmpty()) {
+ return new FileReference(text, -1, -1);
+ }
+
+ Matcher m = COLON_LINE_COL_PATTERN.matcher(text);
+ if (m.matches()) {
+ String path = m.group(1);
+ int line = Integer.parseInt(m.group(2));
+ int column = Integer.parseInt(m.group(3));
+ if (!path.isEmpty()) {
+ return new FileReference(path, line, column);
+ }
+ }
+
+ m = COLON_LINE_PATTERN.matcher(text);
+ if (m.matches()) {
+ String path = m.group(1);
+ int line = Integer.parseInt(m.group(2));
+ if (!path.isEmpty()) {
+ return new FileReference(path, line, -1);
+ }
+ }
+
+ return new FileReference(text, -1, -1);
+ }
+}
diff --git a/terminal/bundles/org.eclipse.terminal.view.ui/src/org/eclipse/terminal/view/ui/internal/tabs/OpenFileMouseHandler.java b/terminal/bundles/org.eclipse.terminal.view.ui/src/org/eclipse/terminal/view/ui/internal/tabs/OpenFileMouseHandler.java
index 700e22a2191..9da560e5377 100644
--- a/terminal/bundles/org.eclipse.terminal.view.ui/src/org/eclipse/terminal/view/ui/internal/tabs/OpenFileMouseHandler.java
+++ b/terminal/bundles/org.eclipse.terminal.view.ui/src/org/eclipse/terminal/view/ui/internal/tabs/OpenFileMouseHandler.java
@@ -16,8 +16,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IFile;
@@ -36,6 +34,8 @@
import org.eclipse.terminal.control.ITerminalMouseListener;
import org.eclipse.terminal.control.ITerminalViewControl;
import org.eclipse.terminal.model.ITerminalTextDataReadOnly;
+import org.eclipse.terminal.view.core.internal.FileReference;
+import org.eclipse.terminal.view.core.internal.FileReferenceParser;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
@@ -61,7 +61,6 @@ public class OpenFileMouseHandler implements ITerminalMouseListener {
"org.eclipse.text"); //$NON-NLS-1$
private final ITerminalViewControl terminal;
- private final Pattern regex = Pattern.compile("(\\d*)(:(\\d*))?.*"); //$NON-NLS-1$
private final IWorkbenchPartSite site;
/**
@@ -96,7 +95,6 @@ public void mouseUp(ITerminalTextDataReadOnly terminalText, int line, int column
return;
}
String textToOpen = terminal.getHoverSelection();
- String lineAndCol = null;
if (textToOpen.length() > 0) {
try {
// if the selection looks like a web URL, open using the browser
@@ -118,18 +116,10 @@ public void mouseUp(ITerminalTextDataReadOnly terminalText, int line, int column
if (textToOpen.startsWith("file://")) { //$NON-NLS-1$
textToOpen = textToOpen.substring(7);
}
- // remove optional position info name:[row[:col]]
- {
- int startOfRowCol = textToOpen.indexOf(':');
- if (startOfRowCol == 1 && textToOpen.length() > 2) {
- // assume this is the device separator on Windows
- startOfRowCol = textToOpen.indexOf(':', startOfRowCol + 1);
- }
- if (startOfRowCol >= 0) {
- lineAndCol = textToOpen.substring(startOfRowCol + 1);
- textToOpen = textToOpen.substring(0, startOfRowCol);
- }
- }
+ // Parse file reference to extract path and optional line:column
+ FileReference fileRef = FileReferenceParser.parse(textToOpen);
+ textToOpen = fileRef.path();
+
Optional fullPath = Optional.empty();
if (!textToOpen.startsWith("/")) { //$NON-NLS-1$
// relative path: try to append to the working directory
@@ -146,7 +136,7 @@ public void mouseUp(ITerminalTextDataReadOnly terminalText, int line, int column
IEditorPart editor = IDE.openEditor(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(), fileForLocation,
true);
- goToLine(lineAndCol, editor);
+ goToLine(fileRef, editor);
return;
}
// try an external file, if it exists
@@ -155,7 +145,7 @@ public void mouseUp(ITerminalTextDataReadOnly terminalText, int line, int column
try {
IEditorPart editor = IDE.openEditor(site.getPage(), file.toURI(),
IDE.getEditorDescriptor(file.getName(), true, true).getId(), true);
- goToLine(lineAndCol, editor);
+ goToLine(fileRef, editor);
return;
} catch (Exception e) {
// continue
@@ -190,7 +180,7 @@ public void mouseUp(ITerminalTextDataReadOnly terminalText, int line, int column
try {
for (IFile iFile : files) {
IEditorPart editor = IDE.openEditor(page, iFile, true);
- goToLine(lineAndCol, editor);
+ goToLine(fileRef, editor);
}
} catch (final PartInitException e) {
throw new ExecutionException("error opening file in editor", e); //$NON-NLS-1$
@@ -211,64 +201,44 @@ private boolean bundleAvailable(String symbolicName) {
return available;
}
- private void goToLine(String lineAndCol, IEditorPart editor) {
+ private void goToLine(FileReference fileRef, IEditorPart editor) {
+ if (!fileRef.hasLine()) {
+ return;
+ }
ITextEditor textEditor = Adapters.adapt(editor, ITextEditor.class);
if (textEditor != null) {
- Optional optionalOffset = getRegionFromLineAndCol(textEditor, lineAndCol);
+ Optional optionalOffset = getOffsetFromFileRef(textEditor, fileRef);
optionalOffset.ifPresent(offset -> textEditor.selectAndReveal(offset, 0));
}
}
- /**
- * Returns the line information for the given line in the given editor
- */
- private Optional getRegionFromLineAndCol(ITextEditor editor, String lineAndCol) {
- if (lineAndCol == null) {
- return Optional.empty();
- }
- Matcher matcher = regex.matcher(lineAndCol);
- if (!matcher.matches()) {
- return Optional.empty();
- }
- String lineStr = matcher.group(1);
- String colStr = matcher.group(3);
- int line;
- int col = 0;
- try {
- line = Integer.parseInt(lineStr);
- } catch (NumberFormatException e1) {
- return Optional.empty();
- }
- try {
- col = Integer.parseInt(colStr);
- } catch (NumberFormatException e1) {
- // if we can't get a column, go to the line alone
- }
+ private static Optional getOffsetFromFileRef(ITextEditor editor, FileReference fileRef) {
+ int line = fileRef.line();
IDocumentProvider provider = editor.getDocumentProvider();
IEditorInput input = editor.getEditorInput();
try {
provider.connect(input);
} catch (CoreException e) {
- return null;
+ return Optional.empty();
}
try {
IDocument document = provider.getDocument(input);
- if (document != null && line > 0) {
- // document's lines are 0-offset
- line = line - 1;
- int lineOffset = document.getLineOffset(line);
- if (col > 0) {
- int lineLength = document.getLineLength(line);
- if (col < lineLength) {
- lineOffset += col;
- }
- }
- return Optional.of(lineOffset);
+ if (document == null) {
+ return Optional.empty();
}
+ // document lines are 0-based, input lines are 1-based
+ int zeroBasedLine = line - 1;
+ int lineOffset = document.getLineOffset(zeroBasedLine);
+ if (fileRef.hasColumn()) {
+ int lineLength = document.getLineLength(zeroBasedLine);
+ int col = Math.min(fileRef.column(), lineLength);
+ lineOffset += col;
+ }
+ return Optional.of(lineOffset);
} catch (BadLocationException e) {
+ return Optional.empty();
} finally {
provider.disconnect(input);
}
- return Optional.empty();
}
}
\ No newline at end of file
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 09adac293ec..d173673596f 100644
--- a/terminal/tests/org.eclipse.terminal.test/META-INF/MANIFEST.MF
+++ b/terminal/tests/org.eclipse.terminal.test/META-INF/MANIFEST.MF
@@ -9,7 +9,8 @@ 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.view.ui;bundle-version="[1.1.200,2.0.0)"
+ org.eclipse.terminal.view.ui;bundle-version="[1.1.200,2.0.0)",
+ org.eclipse.terminal.view.core;bundle-version="[1.0.0,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 c146029c61a..9bafb3e96fb 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
@@ -26,6 +26,8 @@
org.eclipse.terminal.internal.connector.TerminalConnectorTest.class, //
org.eclipse.terminal.internal.connector.TerminalToRemoteInjectionOutputStreamTest.class, //
org.eclipse.terminal.view.ui.tests.TerminalsViewReorderTest.class, //
+ org.eclipse.terminal.view.core.tests.FileReferenceTest.class, //
+ org.eclipse.terminal.view.core.tests.FileReferenceParserTest.class, //
})
public class AutomatedTestSuite {
diff --git a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceParserTest.java b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceParserTest.java
new file mode 100644
index 00000000000..0b621d5a1df
--- /dev/null
+++ b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceParserTest.java
@@ -0,0 +1,225 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Fabrizio Iannetti 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.core.tests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.eclipse.terminal.view.core.internal.FileReference;
+import org.eclipse.terminal.view.core.internal.FileReferenceParser;
+import org.junit.jupiter.api.Test;
+
+class FileReferenceParserTest {
+
+ @Test
+ void testPlainPath() {
+ FileReference ref = FileReferenceParser.parse("src/Test.java");
+ assertEquals("src/Test.java", ref.path());
+ assertEquals(-1, ref.line());
+ assertEquals(-1, ref.column());
+ assertFalse(ref.hasLine());
+ assertFalse(ref.hasColumn());
+ }
+
+ @Test
+ void testPathWithLine() {
+ FileReference ref = FileReferenceParser.parse("src/Test.java:1");
+ assertEquals("src/Test.java", ref.path());
+ assertEquals(1, ref.line());
+ assertEquals(-1, ref.column());
+ assertTrue(ref.hasLine());
+ assertFalse(ref.hasColumn());
+ }
+
+ @Test
+ void testPathWithLineAndTrailingColon() {
+ FileReference ref = FileReferenceParser.parse("src/Test.java:42:");
+ assertEquals("src/Test.java", ref.path());
+ assertEquals(42, ref.line());
+ assertEquals(-1, ref.column());
+ }
+
+ @Test
+ void testPathWithLineAndColumn() {
+ FileReference ref = FileReferenceParser.parse("src/Test.java:42:7");
+ assertEquals("src/Test.java", ref.path());
+ assertEquals(42, ref.line());
+ assertEquals(7, ref.column());
+ assertTrue(ref.hasColumn());
+ }
+
+ @Test
+ void testPathWithLineAndColumnAndTrailingColon() {
+ FileReference ref = FileReferenceParser.parse("src/Test.java:42:7:");
+ assertEquals("src/Test.java", ref.path());
+ assertEquals(42, ref.line());
+ assertEquals(7, ref.column());
+ }
+
+ @Test
+ void testPathWithLineAndTrailingTextAfterSpace() {
+ FileReference ref = FileReferenceParser.parse("src/Test.java:42: private Prix prix;");
+ assertEquals("src/Test.java:42: private Prix prix;", ref.path());
+ assertEquals(-1, ref.line());
+ }
+
+ @Test
+ void testPathWithLineColumnAndTrailingText() {
+ FileReference ref = FileReferenceParser.parse("src/Test.java:42:7: message");
+ assertEquals("src/Test.java:42:7: message", ref.path());
+ assertEquals(-1, ref.line());
+ }
+
+ @Test
+ void testPathWithSpaces() {
+ FileReference ref = FileReferenceParser.parse("src/main/My File.java:12");
+ assertEquals("src/main/My File.java", ref.path());
+ assertEquals(12, ref.line());
+ }
+
+ @Test
+ void testUnixAbsolutePath() {
+ FileReference ref = FileReferenceParser.parse("/home/user/project/Test.java:12");
+ assertEquals("/home/user/project/Test.java", ref.path());
+ assertEquals(12, ref.line());
+ }
+
+ @Test
+ void testWindowsAbsolutePath() {
+ FileReference ref = FileReferenceParser.parse("C:\\work\\project\\Test.java:12");
+ assertEquals("C:\\work\\project\\Test.java", ref.path());
+ assertEquals(12, ref.line());
+ }
+
+ @Test
+ void testWindowsAbsolutePathWithColumn() {
+ FileReference ref = FileReferenceParser.parse("C:\\work\\project\\Test.java:12:8");
+ assertEquals("C:\\work\\project\\Test.java", ref.path());
+ assertEquals(12, ref.line());
+ assertEquals(8, ref.column());
+ }
+
+ @Test
+ void testWindowsRelativePath() {
+ FileReference ref = FileReferenceParser.parse("..\\module\\Test.java:15");
+ assertEquals("..\\module\\Test.java", ref.path());
+ assertEquals(15, ref.line());
+ }
+
+ @Test
+ void testWindowsForwardSlash() {
+ FileReference ref = FileReferenceParser.parse("C:/work/project/Test.java:12");
+ assertEquals("C:/work/project/Test.java", ref.path());
+ assertEquals(12, ref.line());
+ }
+
+ @Test
+ void testLineZeroIsNotLine() {
+ FileReference ref = FileReferenceParser.parse("src/Test.java:0");
+ assertEquals("src/Test.java:0", ref.path());
+ assertEquals(-1, ref.line());
+ assertFalse(ref.hasLine());
+ }
+
+ @Test
+ void testColumnZeroIsNotColumn() {
+ FileReference ref = FileReferenceParser.parse("src/Test.java:42:0");
+ assertEquals("src/Test.java:42:0", ref.path());
+ assertEquals(-1, ref.line());
+ assertEquals(-1, ref.column());
+ }
+
+ @Test
+ void testNonNumericSuffix() {
+ FileReference ref = FileReferenceParser.parse("src/Test.java:abc");
+ assertEquals("src/Test.java:abc", ref.path());
+ assertEquals(-1, ref.line());
+ }
+
+ @Test
+ void testUrlLikeText() {
+ FileReference ref = FileReferenceParser.parse("http://localhost:8080/path");
+ assertEquals("http://localhost:8080/path", ref.path());
+ assertEquals(-1, ref.line());
+ assertEquals(-1, ref.column());
+ }
+
+ @Test
+ void testTimeLikeText() {
+ FileReference ref = FileReferenceParser.parse("12:30:45");
+ assertEquals("12:30:45", ref.path());
+ assertEquals(-1, ref.line());
+ assertEquals(-1, ref.column());
+ }
+
+ @Test
+ void testMavenCoordinates() {
+ FileReference ref = FileReferenceParser.parse("groupId:artifactId:version");
+ assertEquals("groupId:artifactId:version", ref.path());
+ assertEquals(-1, ref.line());
+ }
+
+ @Test
+ void testNullInput() {
+ FileReference ref = FileReferenceParser.parse(null);
+ assertEquals(null, ref.path());
+ assertEquals(-1, ref.line());
+ assertFalse(ref.hasLine());
+ }
+
+ @Test
+ void testEmptyInput() {
+ FileReference ref = FileReferenceParser.parse("");
+ assertEquals("", ref.path());
+ assertEquals(-1, ref.line());
+ assertFalse(ref.hasLine());
+ }
+
+ @Test
+ void testGitGrepOutput() {
+ FileReference ref = FileReferenceParser
+ .parse("ecore/app/src/main/java/com/example/CombinaisonTarifaire.java:42:");
+ assertEquals("ecore/app/src/main/java/com/example/CombinaisonTarifaire.java", ref.path());
+ assertEquals(42, ref.line());
+ assertEquals(-1, ref.column());
+ }
+
+ @Test
+ void testGitGrepOutputWithColumn() {
+ FileReference ref = FileReferenceParser
+ .parse("ecore/app/src/main/java/com/example/MyClass.java:42:17");
+ assertEquals("ecore/app/src/main/java/com/example/MyClass.java", ref.path());
+ assertEquals(42, ref.line());
+ assertEquals(17, ref.column());
+ }
+
+ @Test
+ void testMultipleColonsInPath() {
+ FileReference ref = FileReferenceParser.parse("/path/with:colon/File.java:42");
+ assertEquals("/path/with:colon/File.java", ref.path());
+ assertEquals(42, ref.line());
+ }
+
+ @Test
+ void testJustFileNameWithLine() {
+ FileReference ref = FileReferenceParser.parse("MyClass.java:42");
+ assertEquals("MyClass.java", ref.path());
+ assertEquals(42, ref.line());
+ }
+
+ @Test
+ void testNonFilePathLikeMot123() {
+ FileReference ref = FileReferenceParser.parse("mot:123");
+ assertEquals("mot", ref.path());
+ assertEquals(123, ref.line());
+ }
+}
diff --git a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceTest.java b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceTest.java
new file mode 100644
index 00000000000..7e424c440df
--- /dev/null
+++ b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceTest.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Fabrizio Iannetti 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.core.tests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.eclipse.terminal.view.core.internal.FileReference;
+import org.junit.jupiter.api.Test;
+
+class FileReferenceTest {
+
+ @Test
+ void testDefaultValues() {
+ FileReference ref = new FileReference("/path/to/file.java", -1, -1);
+ assertEquals("/path/to/file.java", ref.path());
+ assertEquals(-1, ref.line());
+ assertEquals(-1, ref.column());
+ assertFalse(ref.hasLine());
+ assertFalse(ref.hasColumn());
+ }
+
+ @Test
+ void testWithLineOnly() {
+ FileReference ref = new FileReference("/path/to/file.java", 42, -1);
+ assertTrue(ref.hasLine());
+ assertFalse(ref.hasColumn());
+ }
+
+ @Test
+ void testWithLineAndColumn() {
+ FileReference ref = new FileReference("/path/to/file.java", 42, 7);
+ assertTrue(ref.hasLine());
+ assertTrue(ref.hasColumn());
+ }
+
+ @Test
+ void testLineZeroIsAbsent() {
+ FileReference ref = new FileReference("file.java", 0, -1);
+ assertFalse(ref.hasLine());
+ }
+
+ @Test
+ void testColumnZeroIsAbsent() {
+ FileReference ref = new FileReference("file.java", 42, 0);
+ assertTrue(ref.hasLine());
+ assertFalse(ref.hasColumn());
+ }
+}
From 33ca3bda714f8f8ca65d7bb921e4ce8e2df60ae9 Mon Sep 17 00:00:00 2001
From: Eclipse Platform Bot
Date: Thu, 30 Jul 2026 17:36:59 +0000
Subject: [PATCH 2/6] Version bump(s) for 4.41 stream
---
.../bundles/org.eclipse.terminal.view.core/META-INF/MANIFEST.MF | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/terminal/bundles/org.eclipse.terminal.view.core/META-INF/MANIFEST.MF b/terminal/bundles/org.eclipse.terminal.view.core/META-INF/MANIFEST.MF
index 0451eb54fce..1d1e10a0b8b 100644
--- a/terminal/bundles/org.eclipse.terminal.view.core/META-INF/MANIFEST.MF
+++ b/terminal/bundles/org.eclipse.terminal.view.core/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.terminal.view.core;singleton:=true
-Bundle-Version: 1.0.100.qualifier
+Bundle-Version: 1.0.200.qualifier
Bundle-Activator: org.eclipse.terminal.view.core.internal.CoreBundleActivator
Bundle-Vendor: %providerName
Require-Bundle: org.eclipse.core.expressions;bundle-version="[3.9.0,4.0.0)",
From 02423181aaf4bc4127809365fb2e18678315e473 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A9lios=20GILLES?=
Date: Thu, 30 Jul 2026 23:00:56 +0200
Subject: [PATCH 3/6] Fix column offset, handle path: with trailing colon,
update tests
- Strip trailing colon from path when no line/col suffix matches
- Fix column offset: 1-based input -> 0-based document (col - 1)
- Rename parser patterns for clarity
- Make test classes/methods public
- Add test for plain path ending with colon
- Update time-like test: 12:30:45 parsed as path=12, line=30, col=45
---
.../core/internal/FileReferenceParser.java | 19 ++---
.../internal/tabs/OpenFileMouseHandler.java | 4 +-
.../core/tests/FileReferenceParserTest.java | 75 +++++++++++--------
.../view/core/tests/FileReferenceTest.java | 14 ++--
4 files changed, 61 insertions(+), 51 deletions(-)
diff --git a/terminal/bundles/org.eclipse.terminal.view.core/src/org/eclipse/terminal/view/core/internal/FileReferenceParser.java b/terminal/bundles/org.eclipse.terminal.view.core/src/org/eclipse/terminal/view/core/internal/FileReferenceParser.java
index 16c0521bb98..07242992933 100644
--- a/terminal/bundles/org.eclipse.terminal.view.core/src/org/eclipse/terminal/view/core/internal/FileReferenceParser.java
+++ b/terminal/bundles/org.eclipse.terminal.view.core/src/org/eclipse/terminal/view/core/internal/FileReferenceParser.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2024 Fabrizio Iannetti and others.
+ * Copyright (c) 2026 Fabrizio Iannetti and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -20,6 +20,7 @@
* Supported formats:
*
* - {@code path}
+ * - {@code path:}
* - {@code path:line}
* - {@code path:line:}
* - {@code path:line:column}
@@ -35,9 +36,9 @@
*/
public class FileReferenceParser {
- private static final Pattern COLON_LINE_COL_PATTERN = Pattern
- .compile("^(.+):([1-9]\\d*):([1-9]\\d*):?$"); //$NON-NLS-1$
- private static final Pattern COLON_LINE_PATTERN = Pattern.compile("^(.+):([1-9]\\d*):?$"); //$NON-NLS-1$
+ private static final Pattern PATH_LINE_COL_PATTERN = Pattern.compile("^(.+):([1-9]\\d*):([1-9]\\d*):?$"); //$NON-NLS-1$
+ private static final Pattern PATH_LINE_PATTERN = Pattern.compile("^(.+):([1-9]\\d*):?$"); //$NON-NLS-1$
+ private static final String COLON = ":"; //$NON-NLS-1$
private FileReferenceParser() {
}
@@ -57,8 +58,7 @@ public static FileReference parse(String text) {
if (text == null || text.isEmpty()) {
return new FileReference(text, -1, -1);
}
-
- Matcher m = COLON_LINE_COL_PATTERN.matcher(text);
+ Matcher m = PATH_LINE_COL_PATTERN.matcher(text);
if (m.matches()) {
String path = m.group(1);
int line = Integer.parseInt(m.group(2));
@@ -67,8 +67,7 @@ public static FileReference parse(String text) {
return new FileReference(path, line, column);
}
}
-
- m = COLON_LINE_PATTERN.matcher(text);
+ m = PATH_LINE_PATTERN.matcher(text);
if (m.matches()) {
String path = m.group(1);
int line = Integer.parseInt(m.group(2));
@@ -76,7 +75,9 @@ public static FileReference parse(String text) {
return new FileReference(path, line, -1);
}
}
-
+ if (text.endsWith(COLON)) {
+ text = text.substring(0, text.length() - COLON.length());
+ }
return new FileReference(text, -1, -1);
}
}
diff --git a/terminal/bundles/org.eclipse.terminal.view.ui/src/org/eclipse/terminal/view/ui/internal/tabs/OpenFileMouseHandler.java b/terminal/bundles/org.eclipse.terminal.view.ui/src/org/eclipse/terminal/view/ui/internal/tabs/OpenFileMouseHandler.java
index 9da560e5377..ab5ce5cbccd 100644
--- a/terminal/bundles/org.eclipse.terminal.view.ui/src/org/eclipse/terminal/view/ui/internal/tabs/OpenFileMouseHandler.java
+++ b/terminal/bundles/org.eclipse.terminal.view.ui/src/org/eclipse/terminal/view/ui/internal/tabs/OpenFileMouseHandler.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2021 Fabrizio Iannetti.
+ * Copyright (c) 2026 Fabrizio Iannetti.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -231,7 +231,7 @@ private static Optional getOffsetFromFileRef(ITextEditor editor, FileRe
int lineOffset = document.getLineOffset(zeroBasedLine);
if (fileRef.hasColumn()) {
int lineLength = document.getLineLength(zeroBasedLine);
- int col = Math.min(fileRef.column(), lineLength);
+ int col = Math.min(fileRef.column() - 1, lineLength);
lineOffset += col;
}
return Optional.of(lineOffset);
diff --git a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceParserTest.java b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceParserTest.java
index 0b621d5a1df..0bc804f73c5 100644
--- a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceParserTest.java
+++ b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceParserTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2024 Fabrizio Iannetti and others.
+ * Copyright (c) 2026 Fabrizio Iannetti and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -18,10 +18,10 @@
import org.eclipse.terminal.view.core.internal.FileReferenceParser;
import org.junit.jupiter.api.Test;
-class FileReferenceParserTest {
+public class FileReferenceParserTest {
@Test
- void testPlainPath() {
+ public void testPlainPath() {
FileReference ref = FileReferenceParser.parse("src/Test.java");
assertEquals("src/Test.java", ref.path());
assertEquals(-1, ref.line());
@@ -31,7 +31,17 @@ void testPlainPath() {
}
@Test
- void testPathWithLine() {
+ public void testPlainPathEndingWithColon() {
+ FileReference ref = FileReferenceParser.parse("src/Test.java:");
+ assertEquals("src/Test.java", ref.path());
+ assertEquals(-1, ref.line());
+ assertEquals(-1, ref.column());
+ assertFalse(ref.hasLine());
+ assertFalse(ref.hasColumn());
+ }
+
+ @Test
+ public void testPathWithLine() {
FileReference ref = FileReferenceParser.parse("src/Test.java:1");
assertEquals("src/Test.java", ref.path());
assertEquals(1, ref.line());
@@ -41,7 +51,7 @@ void testPathWithLine() {
}
@Test
- void testPathWithLineAndTrailingColon() {
+ public void testPathWithLineAndTrailingColon() {
FileReference ref = FileReferenceParser.parse("src/Test.java:42:");
assertEquals("src/Test.java", ref.path());
assertEquals(42, ref.line());
@@ -49,7 +59,7 @@ void testPathWithLineAndTrailingColon() {
}
@Test
- void testPathWithLineAndColumn() {
+ public void testPathWithLineAndColumn() {
FileReference ref = FileReferenceParser.parse("src/Test.java:42:7");
assertEquals("src/Test.java", ref.path());
assertEquals(42, ref.line());
@@ -58,7 +68,7 @@ void testPathWithLineAndColumn() {
}
@Test
- void testPathWithLineAndColumnAndTrailingColon() {
+ public void testPathWithLineAndColumnAndTrailingColon() {
FileReference ref = FileReferenceParser.parse("src/Test.java:42:7:");
assertEquals("src/Test.java", ref.path());
assertEquals(42, ref.line());
@@ -66,42 +76,42 @@ void testPathWithLineAndColumnAndTrailingColon() {
}
@Test
- void testPathWithLineAndTrailingTextAfterSpace() {
+ public void testPathWithLineAndTrailingTextAfterSpace() {
FileReference ref = FileReferenceParser.parse("src/Test.java:42: private Prix prix;");
assertEquals("src/Test.java:42: private Prix prix;", ref.path());
assertEquals(-1, ref.line());
}
@Test
- void testPathWithLineColumnAndTrailingText() {
+ public void testPathWithLineColumnAndTrailingText() {
FileReference ref = FileReferenceParser.parse("src/Test.java:42:7: message");
assertEquals("src/Test.java:42:7: message", ref.path());
assertEquals(-1, ref.line());
}
@Test
- void testPathWithSpaces() {
+ public void testPathWithSpaces() {
FileReference ref = FileReferenceParser.parse("src/main/My File.java:12");
assertEquals("src/main/My File.java", ref.path());
assertEquals(12, ref.line());
}
@Test
- void testUnixAbsolutePath() {
+ public void testUnixAbsolutePath() {
FileReference ref = FileReferenceParser.parse("/home/user/project/Test.java:12");
assertEquals("/home/user/project/Test.java", ref.path());
assertEquals(12, ref.line());
}
@Test
- void testWindowsAbsolutePath() {
+ public void testWindowsAbsolutePath() {
FileReference ref = FileReferenceParser.parse("C:\\work\\project\\Test.java:12");
assertEquals("C:\\work\\project\\Test.java", ref.path());
assertEquals(12, ref.line());
}
@Test
- void testWindowsAbsolutePathWithColumn() {
+ public void testWindowsAbsolutePathWithColumn() {
FileReference ref = FileReferenceParser.parse("C:\\work\\project\\Test.java:12:8");
assertEquals("C:\\work\\project\\Test.java", ref.path());
assertEquals(12, ref.line());
@@ -109,21 +119,21 @@ void testWindowsAbsolutePathWithColumn() {
}
@Test
- void testWindowsRelativePath() {
+ public void testWindowsRelativePath() {
FileReference ref = FileReferenceParser.parse("..\\module\\Test.java:15");
assertEquals("..\\module\\Test.java", ref.path());
assertEquals(15, ref.line());
}
@Test
- void testWindowsForwardSlash() {
+ public void testWindowsForwardSlash() {
FileReference ref = FileReferenceParser.parse("C:/work/project/Test.java:12");
assertEquals("C:/work/project/Test.java", ref.path());
assertEquals(12, ref.line());
}
@Test
- void testLineZeroIsNotLine() {
+ public void testLineZeroIsNotLine() {
FileReference ref = FileReferenceParser.parse("src/Test.java:0");
assertEquals("src/Test.java:0", ref.path());
assertEquals(-1, ref.line());
@@ -131,7 +141,7 @@ void testLineZeroIsNotLine() {
}
@Test
- void testColumnZeroIsNotColumn() {
+ public void testColumnZeroIsNotColumn() {
FileReference ref = FileReferenceParser.parse("src/Test.java:42:0");
assertEquals("src/Test.java:42:0", ref.path());
assertEquals(-1, ref.line());
@@ -139,14 +149,14 @@ void testColumnZeroIsNotColumn() {
}
@Test
- void testNonNumericSuffix() {
+ public void testNonNumericSuffix() {
FileReference ref = FileReferenceParser.parse("src/Test.java:abc");
assertEquals("src/Test.java:abc", ref.path());
assertEquals(-1, ref.line());
}
@Test
- void testUrlLikeText() {
+ public void testUrlLikeText() {
FileReference ref = FileReferenceParser.parse("http://localhost:8080/path");
assertEquals("http://localhost:8080/path", ref.path());
assertEquals(-1, ref.line());
@@ -154,22 +164,22 @@ void testUrlLikeText() {
}
@Test
- void testTimeLikeText() {
+ public void testTimeLikeText() {
FileReference ref = FileReferenceParser.parse("12:30:45");
- assertEquals("12:30:45", ref.path());
- assertEquals(-1, ref.line());
- assertEquals(-1, ref.column());
+ assertEquals("12", ref.path());
+ assertEquals(30, ref.line());
+ assertEquals(45, ref.column());
}
@Test
- void testMavenCoordinates() {
+ public void testMavenCoordinates() {
FileReference ref = FileReferenceParser.parse("groupId:artifactId:version");
assertEquals("groupId:artifactId:version", ref.path());
assertEquals(-1, ref.line());
}
@Test
- void testNullInput() {
+ public void testNullInput() {
FileReference ref = FileReferenceParser.parse(null);
assertEquals(null, ref.path());
assertEquals(-1, ref.line());
@@ -177,7 +187,7 @@ void testNullInput() {
}
@Test
- void testEmptyInput() {
+ public void testEmptyInput() {
FileReference ref = FileReferenceParser.parse("");
assertEquals("", ref.path());
assertEquals(-1, ref.line());
@@ -185,7 +195,7 @@ void testEmptyInput() {
}
@Test
- void testGitGrepOutput() {
+ public void testGitGrepOutput() {
FileReference ref = FileReferenceParser
.parse("ecore/app/src/main/java/com/example/CombinaisonTarifaire.java:42:");
assertEquals("ecore/app/src/main/java/com/example/CombinaisonTarifaire.java", ref.path());
@@ -194,30 +204,29 @@ void testGitGrepOutput() {
}
@Test
- void testGitGrepOutputWithColumn() {
- FileReference ref = FileReferenceParser
- .parse("ecore/app/src/main/java/com/example/MyClass.java:42:17");
+ public void testGitGrepOutputWithColumn() {
+ FileReference ref = FileReferenceParser.parse("ecore/app/src/main/java/com/example/MyClass.java:42:17");
assertEquals("ecore/app/src/main/java/com/example/MyClass.java", ref.path());
assertEquals(42, ref.line());
assertEquals(17, ref.column());
}
@Test
- void testMultipleColonsInPath() {
+ public void testMultipleColonsInPath() {
FileReference ref = FileReferenceParser.parse("/path/with:colon/File.java:42");
assertEquals("/path/with:colon/File.java", ref.path());
assertEquals(42, ref.line());
}
@Test
- void testJustFileNameWithLine() {
+ public void testJustFileNameWithLine() {
FileReference ref = FileReferenceParser.parse("MyClass.java:42");
assertEquals("MyClass.java", ref.path());
assertEquals(42, ref.line());
}
@Test
- void testNonFilePathLikeMot123() {
+ public void testNonFilePathLikeMot123() {
FileReference ref = FileReferenceParser.parse("mot:123");
assertEquals("mot", ref.path());
assertEquals(123, ref.line());
diff --git a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceTest.java b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceTest.java
index 7e424c440df..ccd19cd1507 100644
--- a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceTest.java
+++ b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2024 Fabrizio Iannetti and others.
+ * Copyright (c) 2026 Fabrizio Iannetti and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -17,10 +17,10 @@
import org.eclipse.terminal.view.core.internal.FileReference;
import org.junit.jupiter.api.Test;
-class FileReferenceTest {
+public class FileReferenceTest {
@Test
- void testDefaultValues() {
+ public void testDefaultValues() {
FileReference ref = new FileReference("/path/to/file.java", -1, -1);
assertEquals("/path/to/file.java", ref.path());
assertEquals(-1, ref.line());
@@ -30,27 +30,27 @@ void testDefaultValues() {
}
@Test
- void testWithLineOnly() {
+ public void testWithLineOnly() {
FileReference ref = new FileReference("/path/to/file.java", 42, -1);
assertTrue(ref.hasLine());
assertFalse(ref.hasColumn());
}
@Test
- void testWithLineAndColumn() {
+ public void testWithLineAndColumn() {
FileReference ref = new FileReference("/path/to/file.java", 42, 7);
assertTrue(ref.hasLine());
assertTrue(ref.hasColumn());
}
@Test
- void testLineZeroIsAbsent() {
+ public void testLineZeroIsAbsent() {
FileReference ref = new FileReference("file.java", 0, -1);
assertFalse(ref.hasLine());
}
@Test
- void testColumnZeroIsAbsent() {
+ public void testColumnZeroIsAbsent() {
FileReference ref = new FileReference("file.java", 42, 0);
assertTrue(ref.hasLine());
assertFalse(ref.hasColumn());
From c3039896c2e03554f59839931c5f635337464f7b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A9lios=20GILLES?=
Date: Fri, 31 Jul 2026 17:49:04 +0200
Subject: [PATCH 4/6] Fix Javadoc errors: invalid and missing descriptions
- Remove around