Skip to content

Commit

Permalink
Better change tracking for Semantic highlighting for CSL
Browse files Browse the repository at this point in the history
  • Loading branch information
lkishalmi committed Dec 29, 2024
1 parent 35b31ef commit 73af1c8
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,11 @@
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.Document;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.api.editor.settings.FontColorSettings;
import org.netbeans.lib.editor.util.swing.DocumentListenerPriority;
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
import org.netbeans.modules.csl.core.Language;
import org.netbeans.modules.csl.api.ColoringAttributes.Coloring;
import org.netbeans.spi.editor.highlighting.HighlightsSequence;
Expand All @@ -49,10 +45,9 @@
*
* @author Tor Norbye
*/
public final class GsfSemanticLayer extends AbstractHighlightsContainer implements DocumentListener {
public final class GsfSemanticLayer extends AbstractHighlightsContainer {

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

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

fireHighlightsChange(0, doc.getLength()); //XXX: locking

DocumentUtilities.removeDocumentListener(doc, GsfSemanticLayer.this, DocumentListenerPriority.LEXER);
DocumentUtilities.addDocumentListener(doc, GsfSemanticLayer.this, DocumentListenerPriority.LEXER);
}
});
}
Expand Down Expand Up @@ -131,56 +122,7 @@ private void registerColoringChangeListener(Language language) {
);
coloringListeners.add(l);
}

@Override
public void insertUpdate(DocumentEvent e) {
synchronized (GsfSemanticLayer.this) {
edits.add(new Edit(e.getOffset(), e.getLength(), true));
}
}

@Override
public void removeUpdate(DocumentEvent e) {
synchronized (GsfSemanticLayer.this) {
edits.add(new Edit(e.getOffset(), e.getLength(), false));
}
}

@Override
public void changedUpdate(DocumentEvent e) {
}

// Compute an adjusted offset
public int getShiftedPos(int pos) {
int ret = pos;

for (Edit edit: edits) {
if (ret > edit.offset()) {
if (edit.insert()) {
ret += edit.len();
} else if (ret < edit.offset() + edit.len()) {
ret = edit.offset();
} else {
ret -= edit.len();
}
}
}
return ret;
}

/**
* An Edit is a modification (insert/remove) we've been notified about from the document
* since the last time we updated our "colorings" object.
* The list of Edits lets me quickly compute the current position of an original
* position in the "colorings" object. This is typically going to involve only a couple
* of edits (since the colorings object is updated as soon as the user stops typing).
* This is probably going to be more efficient than updating all the colorings offsets
* every time the document is updated, since the colorings object can contain thousands
* of ranges (e.g. for every highlight in the whole document) whereas asking for the
* current positions is typically only done for the highlights visible on the screen.
*/
private record Edit(int offset, int len, boolean insert) {}


/**
* An implementation of a HighlightsSequence which can show OffsetRange
* sections and keep them up to date during edits.
Expand All @@ -203,12 +145,12 @@ public boolean moveNext() {

@Override
public int getStartOffset() {
return getShiftedPos(element.range().getStart());
return element.start().getOffset();
}

@Override
public int getEndOffset() {
return getShiftedPos(element.range().getEnd());
return element.end().getOffset();
}

@Override
Expand All @@ -233,7 +175,7 @@ private static int firstSequenceElement(List<SequenceElement> l, int offset) {
while (low <= high) {
int mid = (low + high) >>> 1;
SequenceElement midVal = l.get(mid);
int cmp = midVal.range().getStart() - offset;
int cmp = midVal.start().getOffset() - offset;

if (cmp < 0)
low = mid + 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.modules.csl.api.OffsetRange;
Expand Down Expand Up @@ -116,10 +117,13 @@ public void run(ParserResult info, SchedulerEvent event) {
GsfSemanticLayer layer = GsfSemanticLayer.getLayer(MarkOccurrencesHighlighter.class, doc);
SortedSet seqs = new TreeSet<SequenceElement>();

bag.stream()
.filter(range -> range != OffsetRange.NONE)
.forEach(range -> seqs.add(new SequenceElement(language, range, MO)));

for (OffsetRange range : bag) {
if (range != OffsetRange.NONE) {
try {
seqs.add(new SequenceElement(language, doc.createPosition(range.getStart()), doc.createPosition(range.getEnd()), MO));
} catch (BadLocationException ex) {}
}
}
layer.setColorings(seqs);

OccurrencesMarkProvider.get(doc).setOccurrences(OccurrencesMarkProvider.createMarks(doc, bag, ES_COLOR, NbBundle.getMessage(MarkOccurrencesHighlighter.class, "LBL_ES_TOOLTIP")));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.netbeans.modules.csl.api.ColoringAttributes;
import org.netbeans.modules.csl.api.ColoringAttributes.Coloring;
Expand Down Expand Up @@ -157,7 +158,7 @@ private void process(Language language, ParserResult result, Set<SequenceElement
task.cancel();
return;
}

Document doc = result.getSnapshot().getSource().getDocument(false);
Map<OffsetRange,Set<ColoringAttributes>> highlights = task.getHighlights();
for (Map.Entry<OffsetRange, Set<ColoringAttributes>> entry : highlights.entrySet()) {

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

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

newColoring.add(new SequenceElement(language, range, c));
}

if (cancel.isCancelled()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@

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

import javax.swing.text.Position;
import org.netbeans.modules.csl.core.Language;
import org.netbeans.modules.csl.api.ColoringAttributes.Coloring;
import org.netbeans.modules.csl.api.OffsetRange;

/**
* Each SequeneceElement represents a OffsetRange/Coloring/Language tuple that
Expand All @@ -30,24 +30,11 @@
*
* @author Tor Norbye
*/
record SequenceElement(Language language, OffsetRange range, Coloring coloring) implements Comparable<SequenceElement> {
record SequenceElement(Language language, Position start, Position end, Coloring coloring) implements Comparable<SequenceElement> {

@Override
public int compareTo(SequenceElement o) {
assert o.range() != null;
return range.compareTo(o.range());
}

@Override
public boolean equals(Object obj) {
if (obj instanceof SequenceElement other) {
return range.equals(other.range());
}
return false;
}

@Override
public int hashCode() {
return range.hashCode();
return start.getOffset() != o.start.getOffset() ? start.getOffset() - o.start.getOffset()
: end.getOffset() - o.end.getOffset();
}
}

0 comments on commit 73af1c8

Please sign in to comment.