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 30, 2024
1 parent 35b31ef commit b9a60d7
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 92 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 @@ -197,18 +139,28 @@ private final class GsfHighlightSequence implements HighlightsSequence {

@Override
public boolean moveNext() {
element = iterator.hasNext() ? iterator.next() : null;
return element != null;
while (iterator.hasNext()) {
SequenceElement i = iterator.next();
// Skip empty highlights, the editor can handle them, though not happy about it
// this could happen on deleting large portion of code
if (i.start().getOffset() != i.end().getOffset()) {
element = i;
return true;
}
iterator.remove();
}
element = null;
return false;
}

@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 +185,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 @@ -89,7 +90,7 @@ public final void cancel() {
long startTime = System.currentTimeMillis();

Source source = info.getSnapshot().getSource();
final SortedSet<SequenceElement> newColoring = new TreeSet<>();
final SortedSet<SequenceElement> newColoring = new TreeSet<>(SequenceElement.POSITION_ORDER);
try {
ParserManager.parse(Collections.singleton(source), (ResultIterator ri) -> processColorings(ri, newColoring));
} catch (ParseException e) {
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,10 @@

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

import java.util.Comparator;
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 +31,9 @@
*
* @author Tor Norbye
*/
record SequenceElement(Language language, OffsetRange range, Coloring coloring) implements Comparable<SequenceElement> {
record SequenceElement(Language language, Position start, Position end, Coloring coloring) {

@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();
}
public static final Comparator<? super SequenceElement> POSITION_ORDER =
(e1, e2) -> e1.start.getOffset() != e2.start.getOffset() ? e1.start.getOffset() - e2.start.getOffset()
: e1.end.getOffset() - e2.end.getOffset();
}

0 comments on commit b9a60d7

Please sign in to comment.