Skip to content

Commit a32bff9

Browse files
author
Jaroslav Tulach
committed
Hightlight source based on offsets when present
1 parent 75d41ee commit a32bff9

File tree

6 files changed

+260
-26
lines changed

6 files changed

+260
-26
lines changed

visualizer/IdealGraphVisualizer/JavaSources/src/main/java/org/graalvm/visualizer/source/java/impl/JavaStackProcessor2.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ private void addResolvedLocation(String filename, FileObject source) {
271271
}
272272

273273
FileKey fk = ctx.file(filename, source);
274-
Location newLoc = new Location(spec, fk, lineno, lastloc, depth, -1);
274+
Location newLoc = new Location(spec, fk, lineno, -1, -1, lastloc, depth, -1);
275275
ctx.attachInfo(newLoc, javaInfo);
276276
javaInfo.withLocation(newLoc);
277277
result.add(newLoc);

visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/Location.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ public final class Location {
4848
*/
4949
private final int originLine;
5050

51+
private final int startOffset;
52+
private final int endOffset;
53+
5154
/**
5255
* Additional data/services, like OpenCookie, Node to present the data etc.
5356
*/
@@ -62,10 +65,12 @@ public final class Location {
6265

6366
private short frameFrom = -1, frameTo = -1;
6467

65-
public Location(String originSpec, FileKey originFile, int originLine, Location nested, int frame, int frameTo) {
68+
public Location(String originSpec, FileKey originFile, int originLine, int startOffset, int endOffset, Location nested, int frame, int frameTo) {
6669
this.file = originFile;
6770
this.originSpec = originSpec;
6871
this.originLine = originLine;
72+
this.startOffset = startOffset;
73+
this.endOffset = endOffset;
6974
this.frameFrom = (short) frame;
7075
this.frameTo = (short) (frameTo == -1 ? frame : frameTo);
7176
}
@@ -103,6 +108,20 @@ public int getLine() {
103108
return originLine;
104109
}
105110

111+
/** A pair of offsets.
112+
*
113+
* @return either {@code null} when there are no offsets or an {code @int} array
114+
* of size two. 0th element representing the start and 1st the end offset
115+
* in the document.
116+
*/
117+
public int[] getOffsetsOrNull() {
118+
if (startOffset >= 0 && endOffset > startOffset) {
119+
return new int[] { startOffset, endOffset };
120+
} else {
121+
return null;
122+
}
123+
}
124+
106125
Lookup getLookup() {
107126
return lookup;
108127
}
@@ -119,6 +138,8 @@ public int hashCode() {
119138
hash = 89 * hash + this.originLine;
120139
hash = 89 * hash + Objects.hashCode(this.file);
121140
hash = 89 * hash + Objects.hashCode(this.parent);
141+
hash = 37 * hash + startOffset;
142+
hash = 37 * hash + endOffset;
122143
cachedHash = hash == -1 ? 7 : hash;
123144
return cachedHash;
124145
}
@@ -138,6 +159,12 @@ public boolean equals(Object obj) {
138159
if (this.originLine != other.originLine) {
139160
return false;
140161
}
162+
if (this.startOffset != other.startOffset) {
163+
return false;
164+
}
165+
if (this.endOffset != other.endOffset) {
166+
return false;
167+
}
141168
if (this.parent != other.parent) {
142169
return false;
143170
}

visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/SourceLocationUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ public final class SourceLocationUtils {
4242
* @param line the line number
4343
* @return location object
4444
*/
45-
public static Location createLocation(FileObject f, int line) {
45+
private static Location createLocation(FileObject f, int line) {
4646
String spec = f.getPath() + ":" + line;
47-
return new Location(spec, FileKey.fromFile(f), line, null, -1, -1);
47+
return new Location(spec, FileKey.fromFile(f), line, -1, -1, null, -1, -1);
4848
}
4949

5050
public static Collection<Location> atLine(List<Location> searchIn, int line) {

visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/impl/ui/LocationOpener.java

Lines changed: 143 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323

2424
package org.graalvm.visualizer.source.impl.ui;
2525

26+
import java.util.ArrayList;
27+
import java.util.Collections;
28+
import java.util.List;
2629
import org.graalvm.visualizer.source.Location;
2730
import org.graalvm.visualizer.source.ui.Trackable;
2831
import org.netbeans.api.actions.Openable;
@@ -34,15 +37,20 @@
3437
import org.openide.text.Line;
3538
import org.openide.util.NbBundle;
3639

37-
import javax.swing.SwingUtilities;
3840
import javax.swing.text.Document;
3941
import javax.swing.text.JTextComponent;
42+
import javax.swing.text.StyledDocument;
43+
import org.openide.text.Annotatable;
44+
import org.openide.text.Annotation;
45+
import org.openide.text.NbDocument;
4046
import org.openide.util.Mutex;
47+
import org.openide.util.Task;
48+
import org.openide.util.TaskListener;
4149

4250
/**
4351
*
4452
*/
45-
public class LocationOpener implements Openable, Trackable {
53+
public final class LocationOpener implements Openable, Trackable {
4654
private final Location location;
4755

4856
public LocationOpener(Location location) {
@@ -58,33 +66,48 @@ public void open() {
5866
}
5967

6068
private void openOrView(boolean focus) {
61-
FileObject toOpen;
62-
toOpen = location.getOriginFile();
69+
FileObject toOpen = location.getOriginFile();
6370
if (toOpen == null) {
6471
return;
6572
}
66-
int line = location.getLine();
6773
EditorCookie cake = toOpen.getLookup().lookup(EditorCookie.class);
68-
69-
Line l = findLine(cake, line);
70-
if (l == null) {
71-
cake.open();
72-
StatusDisplayer.getDefault().setStatusText(Bundle.ERR_LineNotFound());
74+
if (cake == null) {
7375
return;
7476
}
75-
Mutex.EVENT.readAccess(() -> {
76-
l.show(Line.ShowOpenType.REUSE, focus ? Line.ShowVisibilityType.FRONT : Line.ShowVisibilityType.FRONT);
77-
});
78-
}
7977

80-
private Line findLine(EditorCookie cake, int line) {
81-
try {
82-
return cake.getLineSet().getOriginal(line - 1);
83-
} catch (IndexOutOfBoundsException ex) {
84-
// expected, the source has changed
85-
// just open the file
86-
return null;
78+
Task task = cake.prepareDocument();
79+
class WhenShowing implements TaskListener, Runnable {
80+
@Override
81+
public void taskFinished(Task task) {
82+
task.removeTaskListener(this);
83+
Mutex.EVENT.postReadRequest(this);
84+
}
85+
86+
@Override
87+
public void run() {
88+
final StyledDocument doc = cake.getDocument();
89+
if (doc == null) {
90+
return;
91+
}
92+
List<Annotatable> select = findLinesOrParts(cake.getLineSet(), doc, location.getLine() - 1, location.getOffsetsOrNull());
93+
if (select.size() == 1 && select.get(0) instanceof Line) {
94+
Line line = (Line) select.get(0);
95+
line.show(Line.ShowOpenType.REUSE, focus ? Line.ShowVisibilityType.FRONT : Line.ShowVisibilityType.FRONT);
96+
CurrentNodeAnnotation.highlight(select);
97+
} else if (select.size() >= 1 && select.get(0) instanceof Line.Part) {
98+
Line.Part part = (Line.Part) select.get(0);
99+
part.getLine().show(Line.ShowOpenType.REUSE, focus ? Line.ShowVisibilityType.FRONT : Line.ShowVisibilityType.FRONT);
100+
CurrentNodeAnnotation.highlight(select);
101+
} else {
102+
// neither line nor offsets
103+
cake.open();
104+
StatusDisplayer.getDefault().setStatusText(Bundle.ERR_LineNotFound());
105+
}
106+
}
107+
87108
}
109+
WhenShowing select = new WhenShowing();
110+
task.addTaskListener(select);
88111
}
89112

90113
@Override
@@ -107,4 +130,103 @@ public void viewIfOpened() {
107130
public void view() {
108131
openOrView(false);
109132
}
133+
134+
/**
135+
* Find {@link Line} or {@link Line.Part} to select.
136+
*
137+
* @param lines lines of the document to search in
138+
* @param doc document to search in
139+
* @param lineNumber line number (counting from zero} or value less then zero when there is no line info
140+
* @param offsetsOrNull {@code int[] { startOffset, endOffset }} or {@code null}
141+
* @return found {@link Line} or {@link Line.Part} or {@code null}
142+
*/
143+
static List<Annotatable> findLinesOrParts(Line.Set lines, StyledDocument doc, int lineNumber, int[] offsetsOrNull) {
144+
assert doc != null;
145+
146+
Line exactLine = findLine(lines, lineNumber);
147+
Line startLine = null;
148+
Line endLine = null;
149+
if (offsetsOrNull != null) {
150+
int startOffsetLineNumber = NbDocument.findLineNumber(doc, offsetsOrNull[0]);
151+
startLine = findLine(lines, startOffsetLineNumber);
152+
int endOffsetLineNumber = NbDocument.findLineNumber(doc, offsetsOrNull[1]);
153+
endLine = findLine(lines, endOffsetLineNumber);
154+
}
155+
156+
if (startLine == null || (exactLine != null && startLine != exactLine)) {
157+
// prefer exact line
158+
return Collections.singletonList(exactLine);
159+
} else {
160+
// use offset line
161+
int startLineOffset = NbDocument.findLineOffset(doc, startLine.getLineNumber());
162+
int startColumn = offsetsOrNull[0] - startLineOffset;
163+
if (startLine == endLine || endLine == null) {
164+
int len = offsetsOrNull[1] - offsetsOrNull[0];
165+
Line.Part linePart = startLine.createPart(startColumn, len);
166+
return Collections.singletonList(linePart);
167+
} else {
168+
var multiple = new ArrayList<Annotatable>();
169+
Line.Part firstLinePart = startLine.createPart(startColumn, startLine.getText().length() - startColumn);
170+
multiple.add(firstLinePart);
171+
for (var between = startLine.getLineNumber() + 1; between < endLine.getLineNumber(); between++) {
172+
var lineBetween = findLine(lines, between);
173+
if (lineBetween != null) {
174+
multiple.add(lineBetween);
175+
}
176+
}
177+
int endLineOffset = NbDocument.findLineOffset(doc, endLine.getLineNumber());
178+
int endColumn = offsetsOrNull[1] - endLineOffset;
179+
Line.Part lastLinePart = endLine.createPart(0, endColumn);
180+
multiple.add(lastLinePart);
181+
return multiple;
182+
}
183+
}
184+
}
185+
186+
private static Line findLine(Line.Set lines, int line) {
187+
try {
188+
return lines.getOriginal(line);
189+
} catch (IndexOutOfBoundsException ex) {
190+
// expected, the source has changed
191+
// just open the file
192+
return null;
193+
}
194+
}
195+
196+
@NbBundle.Messages({
197+
"CTL_CurrentNode=Current node"
198+
})
199+
private static final class CurrentNodeAnnotation extends Annotation {
200+
private static List<CurrentNodeAnnotation> previous = Collections.emptyList();
201+
202+
private CurrentNodeAnnotation() {
203+
}
204+
205+
private static void highlight(List<Annotatable> select) {
206+
List<CurrentNodeAnnotation> newOnes = new ArrayList<>();
207+
for (var l : select) {
208+
var a = new CurrentNodeAnnotation();
209+
newOnes.add(a);
210+
a.attach(l);
211+
}
212+
List<CurrentNodeAnnotation> toClear;
213+
synchronized (CurrentNodeAnnotation.class) {
214+
toClear = previous;
215+
previous = newOnes;
216+
}
217+
for (var a : toClear) {
218+
a.detach();
219+
}
220+
}
221+
222+
@Override
223+
public String getAnnotationType() {
224+
return "NodePositionCurrent";
225+
}
226+
227+
@Override
228+
public String getShortDescription() {
229+
return Bundle.CTL_CurrentNode();
230+
}
231+
}
110232
}

visualizer/IdealGraphVisualizer/SourceRepository/src/main/java/org/graalvm/visualizer/source/lang/FileStackProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ private Location processFrame() throws IOException, URISyntaxException {
139139
fk = new FileKey(langMime, langStratum.uri);
140140
}
141141
Location newLoc = new Location(langStratum.uri,
142-
fk, langStratum.line, lastloc, lastDepth, depth);
142+
fk, langStratum.line, langStratum.startOffset, langStratum.endOffset, lastloc, lastDepth, depth);
143143
if (newLoc.equals(lastloc)) {
144144
replaceTop(newLoc);
145145
return null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package org.graalvm.visualizer.source.impl.ui;
2+
3+
import java.io.OutputStream;
4+
import java.util.List;
5+
import javax.swing.text.StyledDocument;
6+
import org.netbeans.junit.NbTestCase;
7+
import org.openide.cookies.EditorCookie;
8+
import org.openide.filesystems.FileObject;
9+
import org.openide.filesystems.FileUtil;
10+
import org.openide.loaders.DataObject;
11+
import org.openide.text.Annotatable;
12+
import org.openide.text.Line;
13+
14+
public class LocationOpenerTest extends NbTestCase {
15+
16+
private DataObject data;
17+
private EditorCookie ec;
18+
private StyledDocument doc;
19+
20+
public LocationOpenerTest(String name) {
21+
super(name);
22+
}
23+
24+
@Override
25+
protected void setUp() throws Exception {
26+
clearWorkDir();
27+
FileObject root = FileUtil.toFileObject(getWorkDir());
28+
assertNotNull("Found fs root", root);
29+
FileObject fo = root.createData("sample", "txt");
30+
String txt = "First line\n"
31+
+ "Second line\n"
32+
+ "Third line\n";
33+
try (OutputStream os = fo.getOutputStream()) {
34+
os.write(txt.getBytes());
35+
}
36+
data = DataObject.find(fo);
37+
ec = data.getLookup().lookup(EditorCookie.class);
38+
assertNotNull("Has editor cookie", ec);
39+
doc = ec.openDocument();
40+
assertNotNull("Document found", doc);
41+
}
42+
43+
public void testFindsALineWhenNoOffset() {
44+
Annotatable line = findSingle(ec.getLineSet(), doc, 1, null);
45+
assertTrue("It is line: " + line, line instanceof Line);
46+
assertEquals("Second line\n", line.getText());
47+
}
48+
49+
public void testIgnoresOffsetOnWrongLine() {
50+
Annotatable line = findSingle(ec.getLineSet(), doc, 1, new int[] { 3, 5 });
51+
assertTrue("It is line: " + line, line instanceof Line);
52+
assertEquals("Second line\n", line.getText());
53+
}
54+
55+
public void testUsesOffsetInsideOfTheSameLine() {
56+
Annotatable part = findSingle(ec.getLineSet(), doc, 1, new int[] { 11, 17 });
57+
assertTrue("It is line part: " + part, part instanceof Line.Part);
58+
assertEquals("Second", part.getText());
59+
}
60+
61+
public void testUsesOffsetWhenNoLine() {
62+
Annotatable part = findSingle(ec.getLineSet(), doc, -1, new int[] { 11, 17 });
63+
assertTrue("It is line part: " + part, part instanceof Line.Part);
64+
assertEquals("Second", part.getText());
65+
}
66+
67+
public void testNoLineAndNoOffsetsYieldsNull() {
68+
Annotatable part = findSingle(ec.getLineSet(), doc, -1, null);
69+
assertNull("Nothing found", part);
70+
}
71+
72+
public void testMultipleLines() {
73+
List<Annotatable> parts = LocationOpener.findLinesOrParts(ec.getLineSet(), doc, -1, new int[] { 6, 28 });
74+
assertEquals("Three elements selected", 3, parts.size());
75+
assertEquals("line\n", parts.get(0).getText());
76+
assertEquals("Second line\n", parts.get(1).getText());
77+
assertEquals("Third", parts.get(2).getText());
78+
}
79+
80+
private static Annotatable findSingle(Line.Set set, StyledDocument doc, int lineNumber, int[] offsetPair) {
81+
List<Annotatable> select = LocationOpener.findLinesOrParts(set, doc, lineNumber, offsetPair);
82+
assertEquals("Expecting single result: " + select, 1, select.size());
83+
return select.get(0);
84+
}
85+
}

0 commit comments

Comments
 (0)