blob: 365e3475ef519a372b752654241930bf93508e6e [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2007, 2019 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.r.launching.ui;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.console.IHyperlink;
import org.eclipse.ui.console.IPatternMatchListener;
import org.eclipse.ui.console.PatternMatchEvent;
import org.eclipse.ui.console.TextConsole;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.statet.ecommons.io.FileValidator;
import org.eclipse.statet.ecommons.runtime.core.util.PathUtils;
import org.eclipse.statet.ecommons.ui.util.UIAccess;
import org.eclipse.statet.internal.r.debug.ui.RLaunchingMessages;
import org.eclipse.statet.internal.r.ui.RUIPlugin;
import org.eclipse.statet.nico.core.runtime.ToolProcess;
import org.eclipse.statet.r.ui.RUI;
public class RErrorLineTracker implements IPatternMatchListener {
private static class SourceLink implements IHyperlink {
private final IFileStore pathBaseFolder;
private final IPath path;
private final int line;
public SourceLink(final IFileStore pathBaseFolder, final IPath path, final int line) {
this.pathBaseFolder= pathBaseFolder;
this.path= path;
this.line= line;
}
@Override
public void linkEntered() {
}
@Override
public void linkExited() {
}
@Override
public void linkActivated() {
final FileValidator fileValidator= new FileValidator(true);
fileValidator.setOnDirectory(IStatus.ERROR);
fileValidator.setResourceLabel(RLaunchingMessages.RErrorLineTracker_File_name);
if (this.path.isAbsolute()) {
fileValidator.setExplicit(this.path);
}
else {
fileValidator.setExplicit(URIUtil.toPath(this.pathBaseFolder.toURI()).append(this.path).makeAbsolute());
}
final IStatus status= fileValidator.validate(null);
if (status.getSeverity() == IStatus.ERROR) {
StatusManager.getManager().handle(new Status(Status.ERROR, RUI.BUNDLE_ID, -1,
NLS.bind(RLaunchingMessages.RErrorLineTracker_error_GetFile_message, this.path),
new CoreException(status) ),
StatusManager.LOG | StatusManager.SHOW );
return;
}
final IFile wsFile= (IFile) fileValidator.getWorkspaceResource();
try {
IEditorPart editor;
if (wsFile != null) {
editor= IDE.openEditor(UIAccess.getActiveWorkbenchPage(true), wsFile, RUI.R_EDITOR_ID, true);
}
else {
editor= IDE.openEditor(UIAccess.getActiveWorkbenchPage(true), fileValidator.getFileStore().toURI(), RUI.R_EDITOR_ID, true);
}
final AbstractTextEditor textEditor= (AbstractTextEditor) editor;
final IDocumentProvider documentProvider= textEditor.getDocumentProvider();
if (documentProvider != null) {
final IDocument doc= documentProvider.getDocument(textEditor.getEditorInput());
final IRegion lineInfo= doc.getLineInformation(this.line);
textEditor.selectAndReveal(lineInfo.getOffset(), lineInfo.getLength());
}
}
catch (final PartInitException | BadLocationException e) {
StatusManager.getManager().handle(new Status(Status.ERROR, RUI.BUNDLE_ID, -1,
NLS.bind(RLaunchingMessages.RErrorLineTracker_error_OpeningFile_message,
this.path),
e ), StatusManager.LOG | StatusManager.SHOW );
}
}
}
private static final String NUM_LINE_REGEX= "^\\d++\\:\\ .*"; //$NON-NLS-1$
private static final String ROUND_LINE_REGEX= "^[ \\t]*\\(.+\\:\\d+(?:\\-\\d+)?\\).*"; //$NON-NLS-1$
private static final String COMBINED_REGEX= "(?:" + NUM_LINE_REGEX + ")|(?:" + ROUND_LINE_REGEX + ")"; //$NON-NLS-1$
private static final Pattern NUM_LINE_PATTERN= Pattern.compile(NUM_LINE_REGEX, Pattern.DOTALL);
private static final Pattern NUM_LINE_GROUP_PATTERN= Pattern.compile("((\\d++)\\:)\\ .*", Pattern.DOTALL); //$NON-NLS-1$
private static final Pattern ROUND_LINE_GROUP_PATTERN= Pattern.compile("[ \\t]*\\(((.+)\\:(\\d+)(?:\\-\\d+)?)\\).*", Pattern.DOTALL); //$NON-NLS-1$
private TextConsole console;
private IFileStore workingDirectory;
private ToolProcess tool;
private final Matcher numLineMatcher= NUM_LINE_PATTERN.matcher(""); //$NON-NLS-1$
private final Matcher numLineGroupMatcher= NUM_LINE_GROUP_PATTERN.matcher(""); //$NON-NLS-1$
private final Matcher checkLineGroupMatcher= ROUND_LINE_GROUP_PATTERN.matcher(""); //$NON-NLS-1$
/**
* @param working directory
*/
public RErrorLineTracker(final IFileStore workingDirectory) {
this.workingDirectory= workingDirectory;
}
public RErrorLineTracker(final ToolProcess tool) {
this.tool= tool;
}
@Override
public int getCompilerFlags() {
return Pattern.MULTILINE;
}
@Override
public String getLineQualifier() {
return null;
}
@Override
public String getPattern() {
return COMBINED_REGEX;
}
@Override
public void connect(final TextConsole console) {
this.console= console;
}
@Override
public void disconnect() {
this.console= null;
}
@Override
public void matchFound(final PatternMatchEvent event) {
try {
final IDocument document= this.console.getDocument();
final String eventLine= document.get(event.getOffset(), event.getLength());
if (this.numLineGroupMatcher.reset(eventLine).matches()) {
final int srcLineNum= Integer.parseInt(this.numLineGroupMatcher.group(2));
final IPath srcPath= createPath(searchPath(document, event));
if (srcPath != null) {
final int begin= this.numLineGroupMatcher.start(1);
final int length= this.numLineGroupMatcher.end(1) - begin;
this.console.addHyperlink(
new SourceLink(getWorkingDirectory(), srcPath, srcLineNum - 1),
event.getOffset() + begin, length );
}
}
else if (this.checkLineGroupMatcher.reset(eventLine).matches()) {
final IPath srcPath= createPath(this.checkLineGroupMatcher.group(2));
final int srcLineNum= Integer.parseInt(this.checkLineGroupMatcher.group(3));
if (srcPath != null) {
final int begin= this.checkLineGroupMatcher.start(1);
final int length= this.checkLineGroupMatcher.end(1) - begin;
this.console.addHyperlink(
new SourceLink(getWorkingDirectory(), srcPath, srcLineNum - 1),
event.getOffset() + begin, length );
}
}
else {
throw new IllegalStateException("match= " + eventLine);
}
}
catch (final Exception e) {
RUIPlugin.logError(-1, "Error while searching error line informations.", e); //$NON-NLS-1$
}
}
private IPath createPath(String path) {
if (path == null || (path= path.trim()).isEmpty()) {
return null;
}
if (this.tool != null) {
return this.tool.getWorkspaceData().createToolPath(path);
}
return PathUtils.check(new Path(path));
}
protected IFileStore getWorkingDirectory() {
if (this.tool != null) {
return this.tool.getWorkspaceData().getWorkspaceDir();
}
else {
return this.workingDirectory;
}
}
private String searchPath(final IDocument document, final PatternMatchEvent event)
throws BadLocationException {
int line= document.getLineOfOffset(event.getOffset());
LINE_BACK: while (--line >= 0) {
final IRegion lineInfo= document.getLineInformation(line);
final int result= checkLine(document, lineInfo);
switch (result) {
case -2:
break LINE_BACK;
case -1:
continue LINE_BACK;
default:
return document.get(lineInfo.getOffset(), result - lineInfo.getOffset());
}
}
return null;
}
/**
* @return
* = -2 wrong
* = -1 number line
* >= 0 index of ": "
*/
private int checkLine(final IDocument doc, final IRegion lineInfo) throws BadLocationException {
final int offset= lineInfo.getOffset();
final int end= offset + Math.min(lineInfo.getLength(), 500);
if (end - offset <= 2) {
return -2;
}
final char char0= doc.getChar(offset);
if (char0 >= 48 && char0 <= 57) {
final String s= doc.get(offset, Math.min(end - offset, 10));
if (this.numLineMatcher.reset(s).matches()) {
return -1;
}
return -2;
}
if (offset >= 5) {
final IRegion prevLineInfo= doc.getLineInformationOfOffset(offset - 1);
final int prevLineEnd= prevLineInfo.getOffset() + prevLineInfo.getLength();
// Line starts with Error, but can be translated, so test only the end
if (!doc.get(prevLineEnd-3, 3).equals(" : ")) { //$NON-NLS-1$
return -2;
}
}
if (char0 == ' ') {
final String s= doc.get(offset, end - offset);
if (s.charAt(1) != ' ') {
return -2;
}
final int found= s.indexOf(":", 4); //$NON-NLS-1$
if (found >= 0) {
return offset + found;
}
return -2;
}
if (char0 == '\t') {
final String s= doc.get(offset, end - offset);
final int found= s.indexOf(":", 3); //$NON-NLS-1$
if (found >= 0) {
return offset + found;
}
return -2;
}
return -2;
}
}