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)",
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..c55f856643a
--- /dev/null
+++ b/terminal/bundles/org.eclipse.terminal.view.core/src/org/eclipse/terminal/view/core/internal/FileReference.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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) {
+
+ /**
+ * Returns whether this reference contains a valid line number.
+ *
+ * @return {@code true} if this reference contains a valid line number
+ */
+ public boolean hasLine() {
+ return line > 0;
+ }
+
+ /**
+ * Returns whether this reference contains a valid column number.
+ *
+ * @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..044a941669a
--- /dev/null
+++ b/terminal/bundles/org.eclipse.terminal.view.core/src/org/eclipse/terminal/view/core/internal/FileReferenceParser.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * 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
+ * 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 are {@code path}, {@code path:}, {@code path:line},
+ * {@code path:line:}, {@code path:line:column} and {@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 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() {
+ }
+
+ /**
+ * 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 = PATH_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 = PATH_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);
+ }
+ }
+ 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 700e22a2191..7fac2e044ee 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
@@ -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;
@@ -52,6 +52,7 @@
/**
* @noreference This class is not intended to be referenced by clients.
*/
+@SuppressWarnings("restriction")
public class OpenFileMouseHandler implements ITerminalMouseListener {
private static final boolean DEBUG_HOVER = Platform.getDebugBoolean(Logger.TRACE_DEBUG_LOG_HOVER);
private static final List NEEDED_BUNDLES = //
@@ -61,7 +62,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;
/**
@@ -88,7 +88,6 @@ public class OpenFileMouseHandler implements ITerminalMouseListener {
}
}
- @SuppressWarnings("restriction")
@Override
public void mouseUp(ITerminalTextDataReadOnly terminalText, int line, int column, int button, int stateMask) {
if ((stateMask & SWT.MODIFIER_MASK) != SWT.MOD1) {
@@ -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() - 1, 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..0bc804f73c5
--- /dev/null
+++ b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceParserTest.java
@@ -0,0 +1,234 @@
+/*******************************************************************************
+ * 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
+ * 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;
+
+public class FileReferenceParserTest {
+
+ @Test
+ public 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
+ 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());
+ assertEquals(-1, ref.column());
+ assertTrue(ref.hasLine());
+ assertFalse(ref.hasColumn());
+ }
+
+ @Test
+ public 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
+ public 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
+ public 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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());
+ assertEquals(8, ref.column());
+ }
+
+ @Test
+ public void testWindowsRelativePath() {
+ FileReference ref = FileReferenceParser.parse("..\\module\\Test.java:15");
+ assertEquals("..\\module\\Test.java", ref.path());
+ assertEquals(15, ref.line());
+ }
+
+ @Test
+ 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
+ public 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
+ public 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
+ public void testNonNumericSuffix() {
+ FileReference ref = FileReferenceParser.parse("src/Test.java:abc");
+ assertEquals("src/Test.java:abc", ref.path());
+ assertEquals(-1, ref.line());
+ }
+
+ @Test
+ public 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
+ public void testTimeLikeText() {
+ FileReference ref = FileReferenceParser.parse("12:30:45");
+ assertEquals("12", ref.path());
+ assertEquals(30, ref.line());
+ assertEquals(45, ref.column());
+ }
+
+ @Test
+ public void testMavenCoordinates() {
+ FileReference ref = FileReferenceParser.parse("groupId:artifactId:version");
+ assertEquals("groupId:artifactId:version", ref.path());
+ assertEquals(-1, ref.line());
+ }
+
+ @Test
+ public void testNullInput() {
+ FileReference ref = FileReferenceParser.parse(null);
+ assertEquals(null, ref.path());
+ assertEquals(-1, ref.line());
+ assertFalse(ref.hasLine());
+ }
+
+ @Test
+ public void testEmptyInput() {
+ FileReference ref = FileReferenceParser.parse("");
+ assertEquals("", ref.path());
+ assertEquals(-1, ref.line());
+ assertFalse(ref.hasLine());
+ }
+
+ @Test
+ 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());
+ assertEquals(42, ref.line());
+ assertEquals(-1, ref.column());
+ }
+
+ @Test
+ 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
+ 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
+ public void testJustFileNameWithLine() {
+ FileReference ref = FileReferenceParser.parse("MyClass.java:42");
+ assertEquals("MyClass.java", ref.path());
+ assertEquals(42, ref.line());
+ }
+
+ @Test
+ 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
new file mode 100644
index 00000000000..ccd19cd1507
--- /dev/null
+++ b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/view/core/tests/FileReferenceTest.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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
+ * 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;
+
+public class FileReferenceTest {
+
+ @Test
+ public 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
+ public void testWithLineOnly() {
+ FileReference ref = new FileReference("/path/to/file.java", 42, -1);
+ assertTrue(ref.hasLine());
+ assertFalse(ref.hasColumn());
+ }
+
+ @Test
+ public void testWithLineAndColumn() {
+ FileReference ref = new FileReference("/path/to/file.java", 42, 7);
+ assertTrue(ref.hasLine());
+ assertTrue(ref.hasColumn());
+ }
+
+ @Test
+ public void testLineZeroIsAbsent() {
+ FileReference ref = new FileReference("file.java", 0, -1);
+ assertFalse(ref.hasLine());
+ }
+
+ @Test
+ public void testColumnZeroIsAbsent() {
+ FileReference ref = new FileReference("file.java", 42, 0);
+ assertTrue(ref.hasLine());
+ assertFalse(ref.hasColumn());
+ }
+}