Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better change tracking for Semantic highlighting for CSL #8099

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.AttributeSet;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.editor.mimelookup.MimePath;
Expand Down Expand Up @@ -65,10 +63,6 @@ public final class ColoringManager {
private final String mimeType;
private final Map<Set<ColoringAttributes>, String> type2Coloring;

//private static final Font ITALIC = SettingsDefaults.defaultFont.deriveFont(Font.ITALIC);
//private static final Font BOLD = SettingsDefaults.defaultFont.deriveFont(Font.BOLD);


public ColoringManager(String mimeType) {
this.mimeType = mimeType;

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

if (colorings.contains(UNUSED)) {
attribs.add(AttributesUtilities.createImmutable(EditorStyleConstants.Tooltip, new UnusedTooltipResolver()));
attribs.add(AttributesUtilities.createImmutable(EditorStyleConstants.Tooltip, UNUSED_TOOLTIP_RESOLVER));
attribs.add(AttributesUtilities.createImmutable("unused-browseable", Boolean.TRUE));
}

//colorings = colorings.size() > 0 ? EnumSet.copyOf(colorings) : EnumSet.noneOf(ColoringAttributes.class);

for (Entry<Set<ColoringAttributes>, String> attribs2Colorings : type2Coloring.entrySet()) {
if (es.containsAll(attribs2Colorings.getKey())) {
String key = attribs2Colorings.getValue();
Expand Down Expand Up @@ -204,12 +196,8 @@ private static AttributeSet adjustAttributes(AttributeSet as) {

return AttributesUtilities.createImmutable(attrs.toArray());
}

private final class UnusedTooltipResolver implements HighlightAttributeValue<String> {

@Override
public String getValue(JTextComponent component, Document document, Object attributeKey, int startOffset, final int endOffset) {
return NbBundle.getMessage(ColoringManager.class, "LBL_UNUSED");
}
}
private static final HighlightAttributeValue<String> UNUSED_TOOLTIP_RESOLVER =
(component, document, attributeKey, startOffset, endOffset) -> NbBundle.getMessage(ColoringManager.class, "LBL_UNUSED");

}
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,27 @@ 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;
}
}
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 +184,7 @@ 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) {
return mid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ public HighlightsLayer[] createLayers(Context context) {

return new HighlightsLayer[] {
HighlightsLayer.create(SemanticHighlighter.class.getName() + "-1", ZOrder.SYNTAX_RACK.forPosition(1000), false, semantic),
// HighlightsLayer.create(SemanticHighlighter.class.getName() + "-2", ZOrder.SYNTAX_RACK.forPosition(1500), false, SemanticHighlighter.getImportHighlightsBag(context.getDocument())),
//the mark occurrences layer should be "above" current row and "below" the search layers:
HighlightsLayer.create(MarkOccurrencesHighlighter.class.getName(), ZOrder.CARET_RACK.forPosition(50), false, occurrences),
//"above" mark occurrences, "below" search layers:
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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,28 @@
package org.netbeans.modules.csl.editor.semantic;

import java.util.List;
import java.util.TreeSet;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.SimpleAttributeSet;
import org.junit.Test;
import org.netbeans.modules.csl.api.ColoringAttributes;
import org.netbeans.modules.csl.api.OffsetRange;
import org.netbeans.modules.csl.core.Language;

import static org.junit.Assert.assertEquals;
import org.netbeans.spi.editor.highlighting.HighlightsSequence;


public class GsfSemanticLayerTest {

@Test
public void testFirstSequenceElement() {
public void testFirstSequenceElement() throws Exception {
Document doc = new DefaultEditorKit().createDefaultDocument();
doc.insertString(0, "Hello World!/n".repeat(10), SimpleAttributeSet.EMPTY);
List<SequenceElement> elements = List.of(
new SequenceElement(new Language("text/x-dummy"), new OffsetRange(10, 20), ColoringAttributes.empty()),
new SequenceElement(new Language("text/x-dummy"), new OffsetRange(30, 40), ColoringAttributes.empty()),
new SequenceElement(new Language("text/x-dummy"), new OffsetRange(50, 60), ColoringAttributes.empty())
new SequenceElement(new Language("text/x-dummy"), doc.createPosition(10), doc.createPosition(20), ColoringAttributes.empty()),
new SequenceElement(new Language("text/x-dummy"), doc.createPosition(30), doc.createPosition(40), ColoringAttributes.empty()),
new SequenceElement(new Language("text/x-dummy"), doc.createPosition(50), doc.createPosition(60), ColoringAttributes.empty())
);

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

@Test
public void testHighlightSequence() throws Exception {
Document doc = new DefaultEditorKit().createDefaultDocument();
doc.insertString(0, "Hello World!/n".repeat(10), SimpleAttributeSet.EMPTY);

GsfSemanticLayer layer = GsfSemanticLayer.getLayer(GsfSemanticLayer.class, doc);
Language lang = new Language("text/x-dummy");

TreeSet<SequenceElement> highlights = new TreeSet<>(SequenceElement.POSITION_ORDER);

highlights.add(new SequenceElement(lang, doc.createPosition(10), doc.createPosition(20), ColoringAttributes.empty()));
highlights.add(new SequenceElement(lang, doc.createPosition(50), doc.createPosition(60), ColoringAttributes.empty()));
highlights.add(new SequenceElement(lang, doc.createPosition(30), doc.createPosition(40), ColoringAttributes.empty()));
highlights.add(new SequenceElement(lang, doc.createPosition(70), doc.createPosition(80), ColoringAttributes.empty()));

layer.setColorings(highlights);

HighlightsSequence seq = layer.getHighlights(0, doc.getLength());
assertEquals(4, countSequenceElements(seq));

doc.remove(30, 40); //remove thw two highlighted area in the middle
seq = layer.getHighlights(0, doc.getLength());
assertEquals(2, countSequenceElements(seq));
}

private static int countSequenceElements(HighlightsSequence seq) {
int ret = 0;
while (seq.moveNext()) ret++;
return ret;
}
}
Loading