Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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 (&ge; 1).
* Windows drive letters ({@code C:\...}) are correctly distinguished
* from the {@code :line} suffix.
* </p>
* <p>
* This class is not intended to be instantiated or subclassed by clients.
* </p>
*/
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}.
* <p>
* 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}.
* </p>
*
* @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);
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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<String> NEEDED_BUNDLES = //
Expand All @@ -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;

/**
Expand All @@ -88,15 +88,13 @@ 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) {
// Only handle Ctrl-click
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
Expand All @@ -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<String> fullPath = Optional.empty();
if (!textToOpen.startsWith("/")) { //$NON-NLS-1$
// relative path: try to append to the working directory
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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$
Expand All @@ -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<Integer> optionalOffset = getRegionFromLineAndCol(textEditor, lineAndCol);
Optional<Integer> 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<Integer> 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<Integer> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
Loading
Loading