diff --git a/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/BinaryReader.java b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/BinaryReader.java index 983d3c7beed3..300ac259a2db 100644 --- a/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/BinaryReader.java +++ b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/BinaryReader.java @@ -246,6 +246,8 @@ public abstract static class Member implements LengthToString { public final String name; private Member(Klass holder, String name, int accessFlags) { + assert holder != null : "GraphElements.methodDeclaringClass must not return null!"; + assert name != null; this.holder = holder; this.accessFlags = accessFlags; this.name = name; diff --git a/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/LocationStackFrame.java b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/LocationStackFrame.java index a428ea88e401..5859c845c1aa 100644 --- a/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/LocationStackFrame.java +++ b/visualizer/IdealGraphVisualizer/Data/src/main/java/jdk/graal/compiler/graphio/parsing/LocationStackFrame.java @@ -49,7 +49,7 @@ public String toString() { String sep = ""; for (LocationStackFrame t = this; t != null; t = t.parent) { sb.append(sep); - sb.append(methodHolderName(t)).append(".").append(t.method.name); + sb.append(methodHolderName(t)).append(".").append(methodName(t)); for (LocationStratum s : t.strata) { if (s.file != null) { sb.append("(").append(s.file).append(":").append(s.line).append(")"); @@ -63,6 +63,9 @@ public String toString() { return sb.toString(); } + private static String methodName(LocationStackFrame t) { + return t != null && t.method != null ? t.method.name : null; + } private static String methodHolderName(LocationStackFrame t) { if (t != null && t.method != null && t.method.holder != null) { return t.method.holder.name; diff --git a/visualizer/IdealGraphVisualizer/JavaSources/src/main/java/org/graalvm/visualizer/source/java/impl/JavaStackProcessor2.java b/visualizer/IdealGraphVisualizer/JavaSources/src/main/java/org/graalvm/visualizer/source/java/impl/JavaStackProcessor2.java index 1f9d2fc5ec27..04ce429cd8ac 100644 --- a/visualizer/IdealGraphVisualizer/JavaSources/src/main/java/org/graalvm/visualizer/source/java/impl/JavaStackProcessor2.java +++ b/visualizer/IdealGraphVisualizer/JavaSources/src/main/java/org/graalvm/visualizer/source/java/impl/JavaStackProcessor2.java @@ -271,7 +271,7 @@ private void addResolvedLocation(String filename, FileObject source) { } FileKey fk = ctx.file(filename, source); - Location newLoc = new Location(spec, fk, lineno, lastloc, depth, -1); + Location newLoc = new Location(spec, fk, lineno, -1, -1, lastloc, depth, -1); ctx.attachInfo(newLoc, javaInfo); javaInfo.withLocation(newLoc); result.add(newLoc); diff --git a/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/Location.java b/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/Location.java index 80044eaf3448..ac0165b7672f 100644 --- a/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/Location.java +++ b/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/Location.java @@ -48,6 +48,9 @@ public final class Location { */ private final int originLine; + private final int startOffset; + private final int endOffset; + /** * Additional data/services, like OpenCookie, Node to present the data etc. */ @@ -62,10 +65,12 @@ public final class Location { private short frameFrom = -1, frameTo = -1; - public Location(String originSpec, FileKey originFile, int originLine, Location nested, int frame, int frameTo) { + public Location(String originSpec, FileKey originFile, int originLine, int startOffset, int endOffset, Location nested, int frame, int frameTo) { this.file = originFile; this.originSpec = originSpec; this.originLine = originLine; + this.startOffset = startOffset; + this.endOffset = endOffset; this.frameFrom = (short) frame; this.frameTo = (short) (frameTo == -1 ? frame : frameTo); } @@ -103,6 +108,20 @@ public int getLine() { return originLine; } + /** A pair of offsets. + * + * @return either {@code null} when there are no offsets or an {code @int} array + * of size two. 0th element representing the start and 1st the end offset + * in the document. + */ + public int[] getOffsetsOrNull() { + if (startOffset >= 0 && endOffset > startOffset) { + return new int[] { startOffset, endOffset }; + } else { + return null; + } + } + Lookup getLookup() { return lookup; } @@ -119,6 +138,8 @@ public int hashCode() { hash = 89 * hash + this.originLine; hash = 89 * hash + Objects.hashCode(this.file); hash = 89 * hash + Objects.hashCode(this.parent); + hash = 37 * hash + startOffset; + hash = 37 * hash + endOffset; cachedHash = hash == -1 ? 7 : hash; return cachedHash; } @@ -138,6 +159,12 @@ public boolean equals(Object obj) { if (this.originLine != other.originLine) { return false; } + if (this.startOffset != other.startOffset) { + return false; + } + if (this.endOffset != other.endOffset) { + return false; + } if (this.parent != other.parent) { return false; } diff --git a/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/SourceLocationUtils.java b/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/SourceLocationUtils.java index abd2d75ca761..652c26f3b4cc 100644 --- a/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/SourceLocationUtils.java +++ b/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/SourceLocationUtils.java @@ -42,9 +42,9 @@ public final class SourceLocationUtils { * @param line the line number * @return location object */ - public static Location createLocation(FileObject f, int line) { + private static Location createLocation(FileObject f, int line) { String spec = f.getPath() + ":" + line; - return new Location(spec, FileKey.fromFile(f), line, null, -1, -1); + return new Location(spec, FileKey.fromFile(f), line, -1, -1, null, -1, -1); } public static Collection atLine(List searchIn, int line) { diff --git a/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/impl/ui/LocationOpener.java b/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/impl/ui/LocationOpener.java index 585befa8aad3..3bef9dbee584 100644 --- a/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/impl/ui/LocationOpener.java +++ b/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/impl/ui/LocationOpener.java @@ -23,6 +23,9 @@ package org.graalvm.visualizer.source.impl.ui; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import org.graalvm.visualizer.source.Location; import org.graalvm.visualizer.source.ui.Trackable; import org.netbeans.api.actions.Openable; @@ -34,14 +37,20 @@ import org.openide.text.Line; import org.openide.util.NbBundle; -import javax.swing.SwingUtilities; import javax.swing.text.Document; import javax.swing.text.JTextComponent; +import javax.swing.text.StyledDocument; +import org.openide.text.Annotatable; +import org.openide.text.Annotation; +import org.openide.text.NbDocument; +import org.openide.util.Mutex; +import org.openide.util.Task; +import org.openide.util.TaskListener; /** * */ -public class LocationOpener implements Openable, Trackable { +public final class LocationOpener implements Openable, Trackable { private final Location location; public LocationOpener(Location location) { @@ -57,31 +66,48 @@ public void open() { } private void openOrView(boolean focus) { - FileObject toOpen; - toOpen = location.getOriginFile(); + FileObject toOpen = location.getOriginFile(); if (toOpen == null) { return; } - int line = location.getLine(); EditorCookie cake = toOpen.getLookup().lookup(EditorCookie.class); - - Line l; - try { - l = cake.getLineSet().getOriginal(line - 1); - } catch (IndexOutOfBoundsException ex) { - // expected, the source has changed + if (cake == null) { return; } - if (l == null) { - cake.open(); - StatusDisplayer.getDefault().setStatusText(Bundle.ERR_LineNotFound()); - return; - } - if (SwingUtilities.isEventDispatchThread()) { - l.show(Line.ShowOpenType.REUSE, focus ? Line.ShowVisibilityType.FRONT : Line.ShowVisibilityType.FRONT); - } else { - SwingUtilities.invokeLater(() -> l.show(Line.ShowOpenType.REUSE, focus ? Line.ShowVisibilityType.FRONT : Line.ShowVisibilityType.FRONT)); + + Task task = cake.prepareDocument(); + class WhenShowing implements TaskListener, Runnable { + @Override + public void taskFinished(Task task) { + task.removeTaskListener(this); + Mutex.EVENT.postReadRequest(this); + } + + @Override + public void run() { + final StyledDocument doc = cake.getDocument(); + if (doc == null) { + return; + } + List select = findLinesOrParts(cake.getLineSet(), doc, location.getLine() - 1, location.getOffsetsOrNull()); + if (select.size() == 1 && select.get(0) instanceof Line) { + Line line = (Line) select.get(0); + line.show(Line.ShowOpenType.REUSE, focus ? Line.ShowVisibilityType.FRONT : Line.ShowVisibilityType.FRONT); + CurrentNodeAnnotation.highlight(select); + } else if (select.size() >= 1 && select.get(0) instanceof Line.Part) { + Line.Part part = (Line.Part) select.get(0); + part.getLine().show(Line.ShowOpenType.REUSE, focus ? Line.ShowVisibilityType.FRONT : Line.ShowVisibilityType.FRONT, part.getColumn()); + CurrentNodeAnnotation.highlight(select); + } else { + // neither line nor offsets + cake.open(); + StatusDisplayer.getDefault().setStatusText(Bundle.ERR_LineNotFound()); + } + } + } + WhenShowing select = new WhenShowing(); + task.addTaskListener(select); } @Override @@ -104,4 +130,103 @@ public void viewIfOpened() { public void view() { openOrView(false); } + + /** + * Find {@link Line} or {@link Line.Part} to select. + * + * @param lines lines of the document to search in + * @param doc document to search in + * @param lineNumber line number (counting from zero} or value less then zero when there is no line info + * @param offsetsOrNull {@code int[] { startOffset, endOffset }} or {@code null} + * @return found {@link Line} or {@link Line.Part} or {@code null} + */ + static List findLinesOrParts(Line.Set lines, StyledDocument doc, int lineNumber, int[] offsetsOrNull) { + assert doc != null; + + Line exactLine = findLine(lines, lineNumber); + Line startLine = null; + Line endLine = null; + if (offsetsOrNull != null) { + int startOffsetLineNumber = NbDocument.findLineNumber(doc, offsetsOrNull[0]); + startLine = findLine(lines, startOffsetLineNumber); + int endOffsetLineNumber = NbDocument.findLineNumber(doc, offsetsOrNull[1]); + endLine = findLine(lines, endOffsetLineNumber); + } + + if (startLine == null || (exactLine != null && startLine != exactLine)) { + // prefer exact line + return Collections.singletonList(exactLine); + } else { + // use offset line + int startLineOffset = NbDocument.findLineOffset(doc, startLine.getLineNumber()); + int startColumn = offsetsOrNull[0] - startLineOffset; + if (startLine == endLine || endLine == null) { + int len = offsetsOrNull[1] - offsetsOrNull[0]; + Line.Part linePart = startLine.createPart(startColumn, len); + return Collections.singletonList(linePart); + } else { + var multiple = new ArrayList(); + Line.Part firstLinePart = startLine.createPart(startColumn, startLine.getText().length() - startColumn); + multiple.add(firstLinePart); + for (var between = startLine.getLineNumber() + 1; between < endLine.getLineNumber(); between++) { + var lineBetween = findLine(lines, between); + if (lineBetween != null) { + multiple.add(lineBetween); + } + } + int endLineOffset = NbDocument.findLineOffset(doc, endLine.getLineNumber()); + int endColumn = offsetsOrNull[1] - endLineOffset; + Line.Part lastLinePart = endLine.createPart(0, endColumn); + multiple.add(lastLinePart); + return multiple; + } + } + } + + private static Line findLine(Line.Set lines, int line) { + try { + return lines.getOriginal(line); + } catch (IndexOutOfBoundsException ex) { + // expected, the source has changed + // just open the file + return null; + } + } + + @NbBundle.Messages({ + "CTL_CurrentNode=Current node" + }) + private static final class CurrentNodeAnnotation extends Annotation { + private static List previous = Collections.emptyList(); + + private CurrentNodeAnnotation() { + } + + private static void highlight(List select) { + List newOnes = new ArrayList<>(); + for (var l : select) { + var a = new CurrentNodeAnnotation(); + newOnes.add(a); + a.attach(l); + } + List toClear; + synchronized (CurrentNodeAnnotation.class) { + toClear = previous; + previous = newOnes; + } + for (var a : toClear) { + a.detach(); + } + } + + @Override + public String getAnnotationType() { + return "NodePositionOffset"; + } + + @Override + public String getShortDescription() { + return Bundle.CTL_CurrentNode(); + } + } } diff --git a/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/lang/FileStackProcessor.java b/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/lang/FileStackProcessor.java index 13b2eac57729..dad0ee045757 100644 --- a/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/lang/FileStackProcessor.java +++ b/visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/lang/FileStackProcessor.java @@ -139,7 +139,7 @@ private Location processFrame() throws IOException, URISyntaxException { fk = new FileKey(langMime, langStratum.uri); } Location newLoc = new Location(langStratum.uri, - fk, langStratum.line, lastloc, lastDepth, depth); + fk, langStratum.line, langStratum.startOffset, langStratum.endOffset, lastloc, lastDepth, depth); if (newLoc.equals(lastloc)) { replaceTop(newLoc); return null; diff --git a/visualizer/IdealGraphVisualizer/SourceRepository/src/main/resources/org/graalvm/visualizer/source/resources/NodePositionOffset.xml b/visualizer/IdealGraphVisualizer/SourceRepository/src/main/resources/org/graalvm/visualizer/source/resources/NodePositionOffset.xml new file mode 100644 index 000000000000..24b848faefd4 --- /dev/null +++ b/visualizer/IdealGraphVisualizer/SourceRepository/src/main/resources/org/graalvm/visualizer/source/resources/NodePositionOffset.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/visualizer/IdealGraphVisualizer/SourceRepository/src/main/resources/org/graalvm/visualizer/source/resources/layer.xml b/visualizer/IdealGraphVisualizer/SourceRepository/src/main/resources/org/graalvm/visualizer/source/resources/layer.xml index fec723f80700..cb4ff0b88fbd 100644 --- a/visualizer/IdealGraphVisualizer/SourceRepository/src/main/resources/org/graalvm/visualizer/source/resources/layer.xml +++ b/visualizer/IdealGraphVisualizer/SourceRepository/src/main/resources/org/graalvm/visualizer/source/resources/layer.xml @@ -84,6 +84,7 @@ + diff --git a/visualizer/IdealGraphVisualizer/SourceRepository/src/test/java/org/graalvm/visualizer/source/impl/ui/LocationOpenerTest.java b/visualizer/IdealGraphVisualizer/SourceRepository/src/test/java/org/graalvm/visualizer/source/impl/ui/LocationOpenerTest.java new file mode 100644 index 000000000000..dec3e2d60dc3 --- /dev/null +++ b/visualizer/IdealGraphVisualizer/SourceRepository/src/test/java/org/graalvm/visualizer/source/impl/ui/LocationOpenerTest.java @@ -0,0 +1,85 @@ +package org.graalvm.visualizer.source.impl.ui; + +import java.io.OutputStream; +import java.util.List; +import javax.swing.text.StyledDocument; +import org.netbeans.junit.NbTestCase; +import org.openide.cookies.EditorCookie; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.loaders.DataObject; +import org.openide.text.Annotatable; +import org.openide.text.Line; + +public class LocationOpenerTest extends NbTestCase { + + private DataObject data; + private EditorCookie ec; + private StyledDocument doc; + + public LocationOpenerTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + clearWorkDir(); + FileObject root = FileUtil.toFileObject(getWorkDir()); + assertNotNull("Found fs root", root); + FileObject fo = root.createData("sample", "txt"); + String txt = "First line\n" + + "Second line\n" + + "Third line\n"; + try (OutputStream os = fo.getOutputStream()) { + os.write(txt.getBytes()); + } + data = DataObject.find(fo); + ec = data.getLookup().lookup(EditorCookie.class); + assertNotNull("Has editor cookie", ec); + doc = ec.openDocument(); + assertNotNull("Document found", doc); + } + + public void testFindsALineWhenNoOffset() { + Annotatable line = findSingle(ec.getLineSet(), doc, 1, null); + assertTrue("It is line: " + line, line instanceof Line); + assertEquals("Second line\n", line.getText()); + } + + public void testIgnoresOffsetOnWrongLine() { + Annotatable line = findSingle(ec.getLineSet(), doc, 1, new int[] { 3, 5 }); + assertTrue("It is line: " + line, line instanceof Line); + assertEquals("Second line\n", line.getText()); + } + + public void testUsesOffsetInsideOfTheSameLine() { + Annotatable part = findSingle(ec.getLineSet(), doc, 1, new int[] { 11, 17 }); + assertTrue("It is line part: " + part, part instanceof Line.Part); + assertEquals("Second", part.getText()); + } + + public void testUsesOffsetWhenNoLine() { + Annotatable part = findSingle(ec.getLineSet(), doc, -1, new int[] { 11, 17 }); + assertTrue("It is line part: " + part, part instanceof Line.Part); + assertEquals("Second", part.getText()); + } + + public void testNoLineAndNoOffsetsYieldsNull() { + Annotatable part = findSingle(ec.getLineSet(), doc, -1, null); + assertNull("Nothing found", part); + } + + public void testMultipleLines() { + List parts = LocationOpener.findLinesOrParts(ec.getLineSet(), doc, -1, new int[] { 6, 28 }); + assertEquals("Three elements selected", 3, parts.size()); + assertEquals("line\n", parts.get(0).getText()); + assertEquals("Second line\n", parts.get(1).getText()); + assertEquals("Third", parts.get(2).getText()); + } + + private static Annotatable findSingle(Line.Set set, StyledDocument doc, int lineNumber, int[] offsetPair) { + List select = LocationOpener.findLinesOrParts(set, doc, lineNumber, offsetPair); + assertEquals("Expecting single result: " + select, 1, select.size()); + return select.get(0); + } +}