Skip to content

Commit 62f8dbf

Browse files
committed
Better change tracking for Semantic highlighting for CSL
1 parent 59bea23 commit 62f8dbf

File tree

7 files changed

+82
-114
lines changed

7 files changed

+82
-114
lines changed

ide/csl.api/src/org/netbeans/modules/csl/editor/semantic/ColoringManager.java

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@
3434
import java.util.logging.Level;
3535
import java.util.logging.Logger;
3636
import javax.swing.text.AttributeSet;
37-
import javax.swing.text.Document;
38-
import javax.swing.text.JTextComponent;
3937
import org.netbeans.api.annotations.common.NonNull;
4038
import org.netbeans.api.editor.mimelookup.MimeLookup;
4139
import org.netbeans.api.editor.mimelookup.MimePath;
@@ -65,10 +63,6 @@ public final class ColoringManager {
6563
private final String mimeType;
6664
private final Map<Set<ColoringAttributes>, String> type2Coloring;
6765

68-
//private static final Font ITALIC = SettingsDefaults.defaultFont.deriveFont(Font.ITALIC);
69-
//private static final Font BOLD = SettingsDefaults.defaultFont.deriveFont(Font.BOLD);
70-
71-
7266
public ColoringManager(String mimeType) {
7367
this.mimeType = mimeType;
7468

@@ -159,12 +153,10 @@ public AttributeSet getColoringImpl(Coloring colorings) {
159153
es.addAll(colorings);
160154

161155
if (colorings.contains(UNUSED)) {
162-
attribs.add(AttributesUtilities.createImmutable(EditorStyleConstants.Tooltip, new UnusedTooltipResolver()));
156+
attribs.add(AttributesUtilities.createImmutable(EditorStyleConstants.Tooltip, UNUSED_TOOLTIP_RESOLVER));
163157
attribs.add(AttributesUtilities.createImmutable("unused-browseable", Boolean.TRUE));
164158
}
165159

166-
//colorings = colorings.size() > 0 ? EnumSet.copyOf(colorings) : EnumSet.noneOf(ColoringAttributes.class);
167-
168160
for (Entry<Set<ColoringAttributes>, String> attribs2Colorings : type2Coloring.entrySet()) {
169161
if (es.containsAll(attribs2Colorings.getKey())) {
170162
String key = attribs2Colorings.getValue();
@@ -204,12 +196,8 @@ private static AttributeSet adjustAttributes(AttributeSet as) {
204196

205197
return AttributesUtilities.createImmutable(attrs.toArray());
206198
}
207-
208-
private final class UnusedTooltipResolver implements HighlightAttributeValue<String> {
209199

210-
@Override
211-
public String getValue(JTextComponent component, Document document, Object attributeKey, int startOffset, final int endOffset) {
212-
return NbBundle.getMessage(ColoringManager.class, "LBL_UNUSED");
213-
}
214-
}
200+
private static final HighlightAttributeValue<String> UNUSED_TOOLTIP_RESOLVER =
201+
(component, document, attributeKey, startOffset, endOffset) -> NbBundle.getMessage(ColoringManager.class, "LBL_UNUSED");
202+
215203
}

ide/csl.api/src/org/netbeans/modules/csl/editor/semantic/GsfSemanticLayer.java

Lines changed: 16 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,11 @@
2626
import java.util.List;
2727
import java.util.Map;
2828
import java.util.SortedSet;
29-
import javax.swing.event.DocumentEvent;
30-
import javax.swing.event.DocumentListener;
3129
import javax.swing.text.AttributeSet;
3230
import javax.swing.text.Document;
3331
import org.netbeans.api.editor.mimelookup.MimeLookup;
3432
import org.netbeans.api.editor.mimelookup.MimePath;
3533
import org.netbeans.api.editor.settings.FontColorSettings;
36-
import org.netbeans.lib.editor.util.swing.DocumentListenerPriority;
37-
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
3834
import org.netbeans.modules.csl.core.Language;
3935
import org.netbeans.modules.csl.api.ColoringAttributes.Coloring;
4036
import org.netbeans.spi.editor.highlighting.HighlightsSequence;
@@ -49,10 +45,9 @@
4945
*
5046
* @author Tor Norbye
5147
*/
52-
public final class GsfSemanticLayer extends AbstractHighlightsContainer implements DocumentListener {
48+
public final class GsfSemanticLayer extends AbstractHighlightsContainer {
5349

5450
private List<SequenceElement> colorings = List.of();
55-
private List<Edit> edits;
5651
private final Map<Language,Map<Coloring, AttributeSet>> cache = new HashMap<>();
5752
private final Document doc;
5853

@@ -81,12 +76,8 @@ void setColorings(final SortedSet<SequenceElement> colorings) {
8176
doc.render(() -> {
8277
synchronized (GsfSemanticLayer.this) {
8378
GsfSemanticLayer.this.colorings = List.copyOf(colorings);
84-
GsfSemanticLayer.this.edits = new ArrayList<>();
8579

8680
fireHighlightsChange(0, doc.getLength()); //XXX: locking
87-
88-
DocumentUtilities.removeDocumentListener(doc, GsfSemanticLayer.this, DocumentListenerPriority.LEXER);
89-
DocumentUtilities.addDocumentListener(doc, GsfSemanticLayer.this, DocumentListenerPriority.LEXER);
9081
}
9182
});
9283
}
@@ -131,56 +122,7 @@ private void registerColoringChangeListener(Language language) {
131122
);
132123
coloringListeners.add(l);
133124
}
134-
135-
@Override
136-
public void insertUpdate(DocumentEvent e) {
137-
synchronized (GsfSemanticLayer.this) {
138-
edits.add(new Edit(e.getOffset(), e.getLength(), true));
139-
}
140-
}
141-
142-
@Override
143-
public void removeUpdate(DocumentEvent e) {
144-
synchronized (GsfSemanticLayer.this) {
145-
edits.add(new Edit(e.getOffset(), e.getLength(), false));
146-
}
147-
}
148-
149-
@Override
150-
public void changedUpdate(DocumentEvent e) {
151-
}
152-
153-
// Compute an adjusted offset
154-
public int getShiftedPos(int pos) {
155-
int ret = pos;
156-
157-
for (Edit edit: edits) {
158-
if (ret > edit.offset()) {
159-
if (edit.insert()) {
160-
ret += edit.len();
161-
} else if (ret < edit.offset() + edit.len()) {
162-
ret = edit.offset();
163-
} else {
164-
ret -= edit.len();
165-
}
166-
}
167-
}
168-
return ret;
169-
}
170-
171-
/**
172-
* An Edit is a modification (insert/remove) we've been notified about from the document
173-
* since the last time we updated our "colorings" object.
174-
* The list of Edits lets me quickly compute the current position of an original
175-
* position in the "colorings" object. This is typically going to involve only a couple
176-
* of edits (since the colorings object is updated as soon as the user stops typing).
177-
* This is probably going to be more efficient than updating all the colorings offsets
178-
* every time the document is updated, since the colorings object can contain thousands
179-
* of ranges (e.g. for every highlight in the whole document) whereas asking for the
180-
* current positions is typically only done for the highlights visible on the screen.
181-
*/
182-
private record Edit(int offset, int len, boolean insert) {}
183-
125+
184126
/**
185127
* An implementation of a HighlightsSequence which can show OffsetRange
186128
* sections and keep them up to date during edits.
@@ -197,18 +139,27 @@ private final class GsfHighlightSequence implements HighlightsSequence {
197139

198140
@Override
199141
public boolean moveNext() {
200-
element = iterator.hasNext() ? iterator.next() : null;
201-
return element != null;
142+
while (iterator.hasNext()) {
143+
SequenceElement i = iterator.next();
144+
// Skip empty highlights, the editor can handle them, though not happy about it
145+
// this could happen on deleting large portion of code
146+
if (i.start().getOffset() != i.end().getOffset()) {
147+
element = i;
148+
return true;
149+
}
150+
}
151+
element = null;
152+
return false;
202153
}
203154

204155
@Override
205156
public int getStartOffset() {
206-
return getShiftedPos(element.range().getStart());
157+
return element.start().getOffset();
207158
}
208159

209160
@Override
210161
public int getEndOffset() {
211-
return getShiftedPos(element.range().getEnd());
162+
return element.end().getOffset();
212163
}
213164

214165
@Override
@@ -233,7 +184,7 @@ static int firstSequenceElement(List<SequenceElement> l, int offset) {
233184
while (low <= high) {
234185
int mid = (low + high) >>> 1;
235186
SequenceElement midVal = l.get(mid);
236-
int cmp = midVal.range().getStart() - offset;
187+
int cmp = midVal.start().getOffset() - offset;
237188

238189
if (cmp == 0) {
239190
return mid;

ide/csl.api/src/org/netbeans/modules/csl/editor/semantic/HighlightsLayerFactoryImpl.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ public HighlightsLayer[] createLayers(Context context) {
4444

4545
return new HighlightsLayer[] {
4646
HighlightsLayer.create(SemanticHighlighter.class.getName() + "-1", ZOrder.SYNTAX_RACK.forPosition(1000), false, semantic),
47-
// HighlightsLayer.create(SemanticHighlighter.class.getName() + "-2", ZOrder.SYNTAX_RACK.forPosition(1500), false, SemanticHighlighter.getImportHighlightsBag(context.getDocument())),
4847
//the mark occurrences layer should be "above" current row and "below" the search layers:
4948
HighlightsLayer.create(MarkOccurrencesHighlighter.class.getName(), ZOrder.CARET_RACK.forPosition(50), false, occurrences),
5049
//"above" mark occurrences, "below" search layers:

ide/csl.api/src/org/netbeans/modules/csl/editor/semantic/MarkOccurrencesHighlighter.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.TreeSet;
2626
import java.util.logging.Level;
2727
import java.util.logging.Logger;
28+
import javax.swing.text.BadLocationException;
2829
import javax.swing.text.Document;
2930
import org.netbeans.api.annotations.common.NonNull;
3031
import org.netbeans.modules.csl.api.OffsetRange;
@@ -116,10 +117,13 @@ public void run(ParserResult info, SchedulerEvent event) {
116117
GsfSemanticLayer layer = GsfSemanticLayer.getLayer(MarkOccurrencesHighlighter.class, doc);
117118
SortedSet seqs = new TreeSet<SequenceElement>();
118119

119-
bag.stream()
120-
.filter(range -> range != OffsetRange.NONE)
121-
.forEach(range -> seqs.add(new SequenceElement(language, range, MO)));
122-
120+
for (OffsetRange range : bag) {
121+
if (range != OffsetRange.NONE) {
122+
try {
123+
seqs.add(new SequenceElement(language, doc.createPosition(range.getStart()), doc.createPosition(range.getEnd()), MO));
124+
} catch (BadLocationException ex) {}
125+
}
126+
}
123127
layer.setColorings(seqs);
124128

125129
OccurrencesMarkProvider.get(doc).setOccurrences(OccurrencesMarkProvider.createMarks(doc, bag, ES_COLOR, NbBundle.getMessage(MarkOccurrencesHighlighter.class, "LBL_ES_TOOLTIP")));

ide/csl.api/src/org/netbeans/modules/csl/editor/semantic/SemanticHighlighter.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.logging.Level;
2727
import java.util.logging.Logger;
2828
import javax.swing.SwingUtilities;
29+
import javax.swing.text.BadLocationException;
2930
import javax.swing.text.Document;
3031
import org.netbeans.modules.csl.api.ColoringAttributes;
3132
import org.netbeans.modules.csl.api.ColoringAttributes.Coloring;
@@ -89,7 +90,7 @@ public final void cancel() {
8990
long startTime = System.currentTimeMillis();
9091

9192
Source source = info.getSnapshot().getSource();
92-
final SortedSet<SequenceElement> newColoring = new TreeSet<>();
93+
final SortedSet<SequenceElement> newColoring = new TreeSet<>(SequenceElement.POSITION_ORDER);
9394
try {
9495
ParserManager.parse(Collections.singleton(source), (ResultIterator ri) -> processColorings(ri, newColoring));
9596
} catch (ParseException e) {
@@ -157,7 +158,7 @@ private void process(Language language, ParserResult result, Set<SequenceElement
157158
task.cancel();
158159
return;
159160
}
160-
161+
Document doc = result.getSnapshot().getSource().getDocument(false);
161162
Map<OffsetRange,Set<ColoringAttributes>> highlights = task.getHighlights();
162163
for (Map.Entry<OffsetRange, Set<ColoringAttributes>> entry : highlights.entrySet()) {
163164

@@ -168,8 +169,11 @@ private void process(Language language, ParserResult result, Set<SequenceElement
168169
}
169170

170171
Coloring c = manager.getColoring(colors);
172+
try {
173+
newColoring.add(new SequenceElement(language, doc.createPosition(range.getStart()), doc.createPosition(range.getEnd()), c));
174+
} catch (BadLocationException ex) {
171175

172-
newColoring.add(new SequenceElement(language, range, c));
176+
}
173177

174178
if (cancel.isCancelled()) {
175179
return;

ide/csl.api/src/org/netbeans/modules/csl/editor/semantic/SequenceElement.java

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919

2020
package org.netbeans.modules.csl.editor.semantic;
2121

22+
import java.util.Comparator;
23+
import javax.swing.text.Position;
2224
import org.netbeans.modules.csl.core.Language;
2325
import org.netbeans.modules.csl.api.ColoringAttributes.Coloring;
24-
import org.netbeans.modules.csl.api.OffsetRange;
2526

2627
/**
2728
* Each SequeneceElement represents a OffsetRange/Coloring/Language tuple that
@@ -30,24 +31,9 @@
3031
*
3132
* @author Tor Norbye
3233
*/
33-
record SequenceElement(Language language, OffsetRange range, Coloring coloring) implements Comparable<SequenceElement> {
34+
record SequenceElement(Language language, Position start, Position end, Coloring coloring) {
3435

35-
@Override
36-
public int compareTo(SequenceElement o) {
37-
assert o.range() != null;
38-
return range.compareTo(o.range());
39-
}
40-
41-
@Override
42-
public boolean equals(Object obj) {
43-
if (obj instanceof SequenceElement other) {
44-
return range.equals(other.range());
45-
}
46-
return false;
47-
}
48-
49-
@Override
50-
public int hashCode() {
51-
return range.hashCode();
52-
}
36+
public static final Comparator<? super SequenceElement> POSITION_ORDER =
37+
(e1, e2) -> e1.start.getOffset() != e2.start.getOffset() ? e1.start.getOffset() - e2.start.getOffset()
38+
: e1.end.getOffset() - e2.end.getOffset();
5339
}

ide/csl.api/test/unit/src/org/netbeans/modules/csl/editor/semantic/GsfSemanticLayerTest.java

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,28 @@
1919
package org.netbeans.modules.csl.editor.semantic;
2020

2121
import java.util.List;
22+
import java.util.TreeSet;
23+
import javax.swing.text.DefaultEditorKit;
24+
import javax.swing.text.Document;
25+
import javax.swing.text.SimpleAttributeSet;
2226
import org.junit.Test;
2327
import org.netbeans.modules.csl.api.ColoringAttributes;
24-
import org.netbeans.modules.csl.api.OffsetRange;
2528
import org.netbeans.modules.csl.core.Language;
2629

2730
import static org.junit.Assert.assertEquals;
31+
import org.netbeans.spi.editor.highlighting.HighlightsSequence;
2832

2933

3034
public class GsfSemanticLayerTest {
3135

3236
@Test
33-
public void testFirstSequenceElement() {
37+
public void testFirstSequenceElement() throws Exception {
38+
Document doc = new DefaultEditorKit().createDefaultDocument();
39+
doc.insertString(0, "Hello World!/n".repeat(10), SimpleAttributeSet.EMPTY);
3440
List<SequenceElement> elements = List.of(
35-
new SequenceElement(new Language("text/x-dummy"), new OffsetRange(10, 20), ColoringAttributes.empty()),
36-
new SequenceElement(new Language("text/x-dummy"), new OffsetRange(30, 40), ColoringAttributes.empty()),
37-
new SequenceElement(new Language("text/x-dummy"), new OffsetRange(50, 60), ColoringAttributes.empty())
41+
new SequenceElement(new Language("text/x-dummy"), doc.createPosition(10), doc.createPosition(20), ColoringAttributes.empty()),
42+
new SequenceElement(new Language("text/x-dummy"), doc.createPosition(30), doc.createPosition(40), ColoringAttributes.empty()),
43+
new SequenceElement(new Language("text/x-dummy"), doc.createPosition(50), doc.createPosition(60), ColoringAttributes.empty())
3844
);
3945

4046
assertEquals(0, GsfSemanticLayer.firstSequenceElement(elements, -1));
@@ -61,4 +67,34 @@ public void testFirstSequenceElement() {
6167
assertEquals(0, GsfSemanticLayer.firstSequenceElement(List.of(), 120));
6268
}
6369

70+
@Test
71+
public void testHighlightSequence() throws Exception {
72+
Document doc = new DefaultEditorKit().createDefaultDocument();
73+
doc.insertString(0, "Hello World!/n".repeat(10), SimpleAttributeSet.EMPTY);
74+
75+
GsfSemanticLayer layer = GsfSemanticLayer.getLayer(GsfSemanticLayer.class, doc);
76+
Language lang = new Language("text/x-dummy");
77+
78+
TreeSet<SequenceElement> highlights = new TreeSet<>(SequenceElement.POSITION_ORDER);
79+
80+
highlights.add(new SequenceElement(lang, doc.createPosition(10), doc.createPosition(20), ColoringAttributes.empty()));
81+
highlights.add(new SequenceElement(lang, doc.createPosition(50), doc.createPosition(60), ColoringAttributes.empty()));
82+
highlights.add(new SequenceElement(lang, doc.createPosition(30), doc.createPosition(40), ColoringAttributes.empty()));
83+
highlights.add(new SequenceElement(lang, doc.createPosition(70), doc.createPosition(80), ColoringAttributes.empty()));
84+
85+
layer.setColorings(highlights);
86+
87+
HighlightsSequence seq = layer.getHighlights(0, doc.getLength());
88+
assertEquals(4, countSequenceElements(seq));
89+
90+
doc.remove(30, 40); //remove thw two highlighted area in the middle
91+
seq = layer.getHighlights(0, doc.getLength());
92+
assertEquals(2, countSequenceElements(seq));
93+
}
94+
95+
private static int countSequenceElements(HighlightsSequence seq) {
96+
int ret = 0;
97+
while (seq.moveNext()) ret++;
98+
return ret;
99+
}
64100
}

0 commit comments

Comments
 (0)