Skip to content

Commit

Permalink
.NET C# stacktrace support #5489 (#5534)
Browse files Browse the repository at this point in the history
* .NET C# stacktrace support #5489

Fixes: Issue #5489

Signed-off-by: Victor Rubezhny <vrubezhny@redhat.com>
  • Loading branch information
vrubezhny authored and benoitf committed Jul 10, 2017
1 parent 0fff648 commit 1693b2b
Show file tree
Hide file tree
Showing 14 changed files with 1,001 additions and 334 deletions.
10 changes: 8 additions & 2 deletions ide/che-core-ide-app/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,14 @@
<exclude>**/*.gif</exclude>
<exclude>**/*.jpg</exclude>
<exclude>**/OutputCustomizer.java</exclude>
<exclude>**/DefaultOutputCustomizer.java</exclude>
<exclude>**/DefaultOutputCustomizerTest.java</exclude>
<exclude>**/AbstractOutputCustomizer.java</exclude>
<exclude>**/BaseOutputCustomizerTest.java</exclude>
<exclude>**/JavaOutputCustomizer.java</exclude>
<exclude>**/JavaOutputCustomizerTest.java</exclude>
<exclude>**/CompoundOutputCustomizer.java</exclude>
<exclude>**/CompoundOutputCustomizerTest.java</exclude>
<exclude>**/CSharpOutputCustomizer.java</exclude>
<exclude>**/CSharpOutputCustomizerTest.java</exclude>
</excludes>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*******************************************************************************
* Copyright (c) 2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.console;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.nullToEmpty;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.che.api.promises.client.Function;
import org.eclipse.che.api.promises.client.FunctionException;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.editor.EditorAgent;
import org.eclipse.che.ide.api.editor.EditorPartPresenter;
import org.eclipse.che.ide.api.editor.OpenEditorCallbackImpl;
import org.eclipse.che.ide.api.editor.document.Document;
import org.eclipse.che.ide.api.editor.text.LinearRange;
import org.eclipse.che.ide.api.editor.text.TextPosition;
import org.eclipse.che.ide.api.editor.texteditor.TextEditor;
import org.eclipse.che.ide.api.resources.Container;
import org.eclipse.che.ide.api.resources.File;
import org.eclipse.che.ide.api.resources.Resource;
import org.eclipse.che.ide.resource.Path;

import com.google.gwt.user.client.Timer;

/**
* Default customizer adds an anchor link to the lines that match a stack trace
* line pattern and installs a handler function for the link. The handler parses
* the stack trace line, searches for the candidate Java files to navigate to,
* opens the first file (of the found candidates) in editor and reveals it to
* the required line according to the stack trace line information
*
* @author Victor Rubezhny
*/
abstract public class AbstractOutputCustomizer implements OutputCustomizer {

protected AppContext appContext;
protected EditorAgent editorAgent;

public AbstractOutputCustomizer(AppContext appContext, EditorAgent editorAgent) {
this.appContext = appContext;
this.editorAgent = editorAgent;
}

/*
* (non-Javadoc)
*
* @see org.eclipse.che.ide.extension.machine.client.outputspanel.console.
* OutputCustomizer#canCustomize(java.lang.String)
*/
@Override
abstract public boolean canCustomize(String text);

/*
* (non-Javadoc)
*
* @see org.eclipse.che.ide.extension.machine.client.outputspanel.console.
* OutputCustomizer#customize(java.lang.String)
*/
@Override
abstract public String customize(String text);

/*
* Returns the list of workspace files filtered by a relative path
*/
protected Promise<List<File>> collectChildren(Container root, Path relativeFilePath) {
return root.getTree(-1).then(new Function<Resource[], List<File>>() {
@Override
public List<File> apply(Resource[] children) throws FunctionException {
return Stream.of(children)
.filter(child -> child.isFile() && endsWith(child.asFile().getLocation(), relativeFilePath))
.map(Resource::asFile).collect(Collectors.toList());
}
});
}

/*
* Checks if a path's last segments are equal to the provided relative path
*/
protected boolean endsWith(Path path, Path relativePath) {
checkNotNull(path);
checkNotNull(relativePath);

if (path.segmentCount() < relativePath.segmentCount())
return false;

for (int i = relativePath.segmentCount() - 1, j = path.segmentCount() - 1; i >= 0; i--, j--) {
if (!nullToEmpty(relativePath.segment(i)).equals(path.segment(j))) {
return false;
}
}

return true;
}

/**
* Finds a file by its path, opens it in editor and sets the text selection and
* reveals according to the specified line and column numbers
*
* @param file
* @param lineNumber
* @param columnNumber
*/
protected void openFileInEditorAndReveal(AppContext appContext, EditorAgent editorAgent, Path file,
final int lineNumber, final int columnNumber) {
appContext.getWorkspaceRoot().getFile(file).then(optional -> {
if (optional.isPresent()) {
editorAgent.openEditor(optional.get(), new OpenEditorCallbackImpl() {
@Override
public void onEditorOpened(EditorPartPresenter editor) {
Timer t = new Timer() {
@Override
public void run() {
EditorPartPresenter editorPart = editorAgent.getActiveEditor();
selectRange(editorPart, lineNumber, columnNumber);
}
};
t.schedule(500);
}

@Override
public void onEditorActivated(EditorPartPresenter editor) {
selectRange(editor, lineNumber, columnNumber);
}
});
}
});
}

/**
* Selects and shows the specified line and column of text in editor
*
* @param editor
* @param line
* @param column
*/
protected void selectRange(EditorPartPresenter editor, int line, int column) {
line--;
column--;
if (line < 0)
line = 0;
if (editor instanceof TextEditor) {
Document document = ((TextEditor) editor).getDocument();
LinearRange selectionRange = document.getLinearRangeForLine(line);
if (column >= 0) {
selectionRange = LinearRange.createWithStart(selectionRange.getStartOffset() + column).andLength(0);
}
document.setSelectedRange(selectionRange, true);
document.setCursorPosition(new TextPosition(line, column >= 0 ? column : 0));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*******************************************************************************
* Copyright (c) 2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.console;

import static com.google.gwt.regexp.shared.RegExp.compile;

import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.editor.EditorAgent;
import org.eclipse.che.ide.resource.Path;

import com.google.gwt.regexp.shared.RegExp;

/**
* Output customizer adds an anchor link to the lines that match a .NET C#
* compilation error or warning message and a stack trace line pattern and
* installs the handler functions for the links. The handler parses the stack
* trace line, searches for a candidate C# file to navigate to, opens the found
* file in editor and reveals it to the required line and column (if available)
* according to the line information
*
* @author Victor Rubezhny
*/
public class CSharpOutputCustomizer extends AbstractOutputCustomizer {

private static final RegExp COMPILATION_ERROR_OR_WARNING = compile(
"(.+\\.(cs|CS)\\(\\d+,\\d+\\): (error|warning) .+: .+ \\[.+\\])");
private static final RegExp LINE_AT = compile("(\\s+at .+ in .+\\.(cs|CS):line \\d+)");

/**
* Constructs Compound Output Customizer Object
*
* @param appContext
* @param editorAgent
*/
public CSharpOutputCustomizer(AppContext appContext, EditorAgent editorAgent) {
super(appContext, editorAgent);

exportCompilationMessageAnchorClickHandlerFunction();
exportStacktraceLineAnchorClickHandlerFunction();
}

/*
* (non-Javadoc)
*
* @see org.eclipse.che.ide.extension.machine.client.outputspanel.console.
* OutputCustomizer#canCustomize(java.lang.String)
*/
@Override
public boolean canCustomize(String text) {
return (COMPILATION_ERROR_OR_WARNING.exec(text) != null || LINE_AT.exec(text) != null);
}

/*
* (non-Javadoc)
*
* @see org.eclipse.che.ide.extension.machine.client.outputspanel.console.
* OutputCustomizer#customize(java.lang.String)
*/
@Override
public String customize(String text) {
if (COMPILATION_ERROR_OR_WARNING.exec(text) != null)
return customizeCompilationErrorOrWarning(text);

if (LINE_AT.exec(text) != null)
return customizeStacktraceLine(text);

return text;
}

/*
* Customizes a Compilation Error/Warning line
*/
private String customizeCompilationErrorOrWarning(String text) {
try {
int end = text.indexOf("):"), openBracket = text.lastIndexOf("(", end), comma = text.lastIndexOf(",", end),
closeSBracket = text.lastIndexOf("]"), openSBracket = text.lastIndexOf("[", closeSBracket);
String fileName = text.substring(0, openBracket).trim();
String projectFileName = text.substring(openSBracket + "[".length(), closeSBracket).trim();
int lineNumber = Integer.valueOf(text.substring(openBracket + "(".length(), comma).trim());
int columnNumber = Integer.valueOf(text.substring(comma + ",".length(), end).trim());

String customText = "<a href='javascript:openCSCM(\"" + fileName + "\",\"" + projectFileName + "\","
+ lineNumber + "," + columnNumber + ");'>";
customText += text.substring(0, end + ")".length());
customText += "</a>";
customText += text.substring(end + ")".length());
text = customText;
} catch (IndexOutOfBoundsException ex) {
// ignore
} catch (NumberFormatException ex) {
// ignore
}

return text;
}

/*
* Customizes a Stacktrace line
*/
private String customizeStacktraceLine(String text) {
try {
int start = text.lastIndexOf(" in ") + " in ".length(), end = text.indexOf(":line ", start);

String fileName = text.substring(start, end).trim();
int lineNumber = Integer.valueOf(text.substring(end + ":line ".length()).trim());

String customText = text.substring(0, start);
customText += "<a href='javascript:openCSSTL(\"" + fileName + "\"," + lineNumber + ");'>";
customText += text.substring(start);
customText += "</a>";
text = customText;
} catch (IndexOutOfBoundsException ex) {
// ignore
} catch (NumberFormatException ex) {
// ignore
}

return text;
}

/**
* A callback that is to be called for an anchor for C# Compilation
* Error/Warning Message
*
* @param fileName
* @param projectFile
* @param lineNumber
* @param columnNumber
*/
public void handleCompilationMessageAnchorClick(String fileName, String projectFile, final int lineNumber,
final int columnNumber) {
if (fileName == null || projectFile == null) {
return;
}

openFileInEditorAndReveal(appContext, editorAgent,
Path.valueOf(projectFile).removeFirstSegments(1).parent().append(fileName), lineNumber, columnNumber);
}

/**
* A callback that is to be called for an anchor for C# Runtime Exception
* Stacktrace line
*
* @param fileName
* @param lineNumber
*/
public void handleStacktraceLineAnchorClick(String fileName, int lineNumber) {
if (fileName == null) {
return;
}

openFileInEditorAndReveal(appContext, editorAgent,
Path.valueOf(fileName).removeFirstSegments(1), lineNumber, 0);
}

/**
* Sets up a java callback to be called for an anchor for C# Compilation
* Error/Warning Message
*/
public native void exportCompilationMessageAnchorClickHandlerFunction() /*-{
var that = this;
$wnd.openCSCM = $entry(function(fileName,projectFile,lineNumber,columnNumber) {
that.@org.eclipse.che.ide.console.CSharpOutputCustomizer::handleCompilationMessageAnchorClick(*)(fileName,projectFile,lineNumber,columnNumber);
});
}-*/;

/**
* Sets up a java callback to be called for an anchor for C# Runtime Exception
* Stacktrace line
*/
public native void exportStacktraceLineAnchorClickHandlerFunction() /*-{
var that = this;
$wnd.openCSSTL = $entry(function(fileName,lineNumber) {
that.@org.eclipse.che.ide.console.CSharpOutputCustomizer::handleStacktraceLineAnchorClick(*)(fileName,lineNumber);
});
}-*/;
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ public CommandOutputConsolePresenter(final OutputConsoleView view,
this.eventBus = eventBus;
this.commandExecutor = commandExecutor;

setCustomizer(new DefaultOutputCustomizer(appContext, editorAgent));
setCustomizer(new CompoundOutputCustomizer(
new JavaOutputCustomizer(appContext, editorAgent),
new CSharpOutputCustomizer(appContext, editorAgent)));

view.setDelegate(this);

final String previewUrl = command.getAttributes().get(COMMAND_PREVIEW_URL_ATTRIBUTE_NAME);
Expand Down
Loading

0 comments on commit 1693b2b

Please sign in to comment.