You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by jl...@apache.org on 2019/12/17 22:01:39 UTC
[netbeans] branch master updated: A first version of mark
occurrences via the language server protocol.
This is an automated email from the ASF dual-hosted git repository.
jlahoda pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push:
new 6debcdd A first version of mark occurrences via the language server protocol.
6debcdd is described below
commit 6debcdd72693aa4c5a10d6f99a2389a51d311b8f
Author: Jan Lahoda <jl...@netbeans.org>
AuthorDate: Fri Nov 22 08:47:03 2019 +0100
A first version of mark occurrences via the language server protocol.
---
.../netbeans/modules/lsp/client/LSPBindings.java | 14 ++
.../client/bindings/GoToMarkOccurrencesAction.java | 139 ++++++++++++++++
.../lsp/client/bindings/MarkOccurrences.java | 182 +++++++++++++++++++++
.../TextDocumentSyncServerCapabilityHandler.java | 5 +
4 files changed, 340 insertions(+)
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java
index 5a4d60a..206b9b9 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java
@@ -24,6 +24,7 @@ import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
@@ -235,6 +236,19 @@ public class LSPBindings {
WORKER.post(req, DELAY);
}
+ public static void rescheduleBackgroundTask(FileObject file, BackgroundTask task) {
+ LSPBindings bindings = getBindings(file);
+
+ if (bindings == null)
+ return ;
+
+ RequestProcessor.Task req = bindings.backgroundTasks.computeIfAbsent(file, f -> Collections.emptyMap()).get(task);
+
+ if (req != null) {
+ WORKER.post(req, DELAY);
+ }
+ }
+
public void scheduleBackgroundTasks(FileObject file) {
backgroundTasks.computeIfAbsent(file, f -> new IdentityHashMap<>()).values().stream().forEach(this::scheduleBackgroundTask);
}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/GoToMarkOccurrencesAction.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/GoToMarkOccurrencesAction.java
new file mode 100644
index 0000000..4db9dd7
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/GoToMarkOccurrencesAction.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.netbeans.modules.lsp.client.bindings;
+
+import java.awt.event.ActionEvent;
+import javax.swing.text.Document;
+import javax.swing.text.JTextComponent;
+import org.netbeans.api.editor.EditorActionNames;
+import org.netbeans.api.editor.EditorActionRegistration;
+import org.netbeans.editor.BaseAction;
+import org.netbeans.lib.editor.util.swing.DocumentUtilities;
+import org.netbeans.spi.editor.highlighting.HighlightsSequence;
+import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
+import org.openide.awt.StatusDisplayer;
+import org.openide.util.NbBundle;
+
+/**
+ *
+ * @author Vladimir Voskresensky
+ */
+@NbBundle.Messages({
+ "next-marked-occurrence=Navigate to Next Occurrence",
+ "prev-marked-occurrence=Navigate to Previous Occurrence",
+ "no-marked-occurrence=There is no occurrence to navigate to."
+})
+public class GoToMarkOccurrencesAction extends BaseAction {
+
+ private static final String prevActionName = EditorActionNames.gotoPrevOccurrence;
+ private static final String nextActionName = EditorActionNames.gotoNextOccurrence;
+
+ static final String markedOccurence = "marked-occurrence"; // NOI18N
+
+ private final boolean next;
+
+ public GoToMarkOccurrencesAction(boolean nextOccurrence) {
+ super(getNameString(nextOccurrence));
+ this.next = nextOccurrence;
+ putValue(SHORT_DESCRIPTION, getDefaultShortDescription());
+ }
+
+ public void actionPerformed(ActionEvent evt, JTextComponent txt) {
+ navigateToOccurence(next, txt);
+ }
+
+ @Override
+ protected Object getDefaultShortDescription() {
+ return NbBundle.getMessage(GoToMarkOccurrencesAction.class, getNameString(next));
+ }
+
+ private static String getNameString(boolean nextOccurrence) {
+ return nextOccurrence ? nextActionName : prevActionName;
+ }
+
+ @SuppressWarnings("empty-statement")
+ private static int findOccurrencePosition(boolean directionForward, Document doc, int curPos) {
+ OffsetsBag bag = MarkOccurrences.getHighlightsBag(doc);
+ HighlightsSequence hs = bag.getHighlights(0, doc.getLength());
+
+ if (hs.moveNext()) {
+ if (directionForward) {
+ int firstStart = hs.getStartOffset(), firstEnd = hs.getEndOffset();
+
+ while (hs.getStartOffset() <= curPos && hs.moveNext());
+
+ if (hs.getStartOffset() > curPos) {
+ // we found next occurrence
+ return hs.getStartOffset();
+ } else if (!(firstEnd >= curPos && firstStart <= curPos)) {
+ // cyclic jump to first occurrence unless we already there
+ return firstStart;
+ }
+ } else {
+ int current = hs.getStartOffset(), last;
+ boolean stuck = false;
+ do {
+ last = current;
+ current = hs.getStartOffset();
+ } while (hs.getEndOffset() < curPos && (stuck = hs.moveNext()));
+
+ if (last == current) {
+ // we got no options to jump, cyclic jump to last in file unless we already there
+ while (hs.moveNext());
+ if (!(hs.getEndOffset() >= curPos && hs.getStartOffset() <= curPos)) {
+ return hs.getStartOffset();
+ }
+ } else if (stuck) {
+ // just move to previous occurence
+ return last;
+ } else {
+ // it was last occurence in the file
+ return current;
+ }
+ }
+ }
+ return -1;
+ }
+
+ private static void navigateToOccurence(boolean next, JTextComponent txt) {
+ if (txt != null && txt.getDocument() != null) {
+ Document doc = txt.getDocument();
+ int position = txt.getCaretPosition();
+ int goTo = findOccurrencePosition(next, doc, position);
+ if (goTo > 0) {
+ txt.setCaretPosition(goTo);
+ doc.putProperty(markedOccurence, new long[] {DocumentUtilities.getDocumentVersion(doc), goTo});
+ } else {
+ StatusDisplayer.getDefault().setStatusText(NbBundle.getMessage(GoToMarkOccurrencesAction.class, "no-marked-occurrence"));
+ doc.putProperty(markedOccurence, null);
+ }
+ }
+ }
+
+ @EditorActionRegistration(name=nextActionName)
+ public static GoToMarkOccurrencesAction createNextAction() {
+ return new GoToMarkOccurrencesAction(true);
+ }
+
+ @EditorActionRegistration(name=prevActionName)
+ public static GoToMarkOccurrencesAction createPrevAction() {
+ return new GoToMarkOccurrencesAction(false);
+ }
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/MarkOccurrences.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/MarkOccurrences.java
new file mode 100644
index 0000000..4e83557
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/MarkOccurrences.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.lsp.client.bindings;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.JTextComponent;
+import org.eclipse.lsp4j.DocumentHighlight;
+import org.eclipse.lsp4j.TextDocumentIdentifier;
+import org.eclipse.lsp4j.TextDocumentPositionParams;
+import org.netbeans.api.editor.mimelookup.MimeLookup;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.api.editor.settings.AttributesUtilities;
+import org.netbeans.api.editor.settings.FontColorSettings;
+import org.netbeans.modules.editor.NbEditorUtilities;
+import org.netbeans.modules.lsp.client.LSPBindings;
+import org.netbeans.modules.lsp.client.LSPBindings.BackgroundTask;
+import org.netbeans.modules.lsp.client.Utils;
+import org.netbeans.spi.editor.highlighting.HighlightsLayer;
+import org.netbeans.spi.editor.highlighting.HighlightsLayerFactory;
+import org.netbeans.spi.editor.highlighting.ZOrder;
+import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
+import org.openide.filesystems.FileObject;
+import org.openide.loaders.DataObject;
+import org.openide.util.Exceptions;
+import org.openide.util.RequestProcessor;
+
+/**
+ *
+ * @author lahvac
+ */
+public class MarkOccurrences implements BackgroundTask, CaretListener, PropertyChangeListener {
+
+ private static final RequestProcessor WORKER = new RequestProcessor(MarkOccurrences.class.getName(), 1, false, false);
+ private final JTextComponent component;
+ private Document doc;
+ private int caretPos;
+
+ public MarkOccurrences(JTextComponent component) {
+ this.component = component;
+ component.addCaretListener(this);
+ component.addPropertyChangeListener(this);
+ doc = component.getDocument();
+ caretPos = component.getCaretPosition();
+ }
+
+ @Override
+ public void run(LSPBindings bindings, FileObject file) {
+ Document doc;
+ int caretPos;
+ synchronized (this) {
+ doc = this.doc;
+ caretPos = this.caretPos;
+ }
+ getHighlightsBag(doc).setHighlights(computeHighlights(doc, caretPos));
+ }
+
+ private OffsetsBag computeHighlights(Document doc, int caretPos) {
+ AttributeSet attr = getColoring(doc);
+ OffsetsBag result = new OffsetsBag(doc);
+ FileObject file = NbEditorUtilities.getFileObject(doc);
+ if (file == null) {
+ return result;
+ }
+ LSPBindings server = LSPBindings.getBindings(file);
+ if (server == null) {
+ return result;
+ }
+ String uri = Utils.toURI(file);
+ try {
+ List<? extends DocumentHighlight> highlights =
+ server.getTextDocumentService().documentHighlight(new TextDocumentPositionParams(new TextDocumentIdentifier(uri), Utils.createPosition(doc, caretPos))).get();
+ for (DocumentHighlight h : highlights) {
+ result.addHighlight(Utils.getOffset(doc, h.getRange().getStart()), Utils.getOffset(doc, h.getRange().getEnd()), attr);
+ }
+ return result;
+ } catch (BadLocationException | InterruptedException | ExecutionException ex) {
+ Exceptions.printStackTrace(ex);
+ return result;
+ }
+ }
+
+ private AttributeSet getColoring(Document doc) {
+ FontColorSettings fcs = MimeLookup.getLookup(NbEditorUtilities.getMimeType(doc)).lookup(FontColorSettings.class);
+
+ if (fcs == null) {
+ //in tests:
+ return AttributesUtilities.createImmutable();
+ }
+
+ assert fcs != null;
+
+ return fcs.getTokenFontColors("mark-occurrences");
+ }
+
+ @Override
+ public synchronized void caretUpdate(CaretEvent e) {
+ caretPos = e.getDot();
+ WORKER.post(() -> {
+ FileObject file = NbEditorUtilities.getFileObject(doc);
+
+ if (file != null) {
+ LSPBindings.rescheduleBackgroundTask(file, this);
+ }
+ });
+ }
+
+ @Override
+ public synchronized void propertyChange(PropertyChangeEvent evt) {
+ if (evt.getPropertyName() == null || "document".equals(evt.getPropertyName())) {
+ doc = component.getDocument();
+ }
+ }
+
+ static OffsetsBag getHighlightsBag(Document doc) {
+ OffsetsBag bag = (OffsetsBag) doc.getProperty(MarkOccurrences.class);
+
+ if (bag == null) {
+ doc.putProperty(MarkOccurrences.class, bag = new OffsetsBag(doc, false));
+
+ Object stream = doc.getProperty(Document.StreamDescriptionProperty);
+ final OffsetsBag bagFin = bag;
+ DocumentListener l = new DocumentListener() {
+ public void insertUpdate(DocumentEvent e) {
+ bagFin.removeHighlights(e.getOffset(), e.getOffset(), false);
+ }
+ public void removeUpdate(DocumentEvent e) {
+ bagFin.removeHighlights(e.getOffset(), e.getOffset(), false);
+ }
+ public void changedUpdate(DocumentEvent e) {}
+ };
+
+ doc.addDocumentListener(l);
+
+ if (stream instanceof DataObject) {
+ Logger.getLogger("TIMER").log(Level.FINE, "LSP Client MarkOccurrences Highlights Bag", new Object[] {((DataObject) stream).getPrimaryFile(), bag}); //NOI18N
+ Logger.getLogger("TIMER").log(Level.FINE, "LSP Client MarkOccurrences Highlights Bag Listener", new Object[] {((DataObject) stream).getPrimaryFile(), l}); //NOI18N
+ }
+ }
+
+ return bag;
+ }
+
+ @MimeRegistration(mimeType="", service=HighlightsLayerFactory.class)
+ public static class HighlightsLayerFactoryImpl implements HighlightsLayerFactory {
+
+ public HighlightsLayer[] createLayers(HighlightsLayerFactory.Context context) {
+ return new HighlightsLayer[] {
+ //the mark occurrences layer should be "above" current row and "below" the search layers:
+ HighlightsLayer.create(MarkOccurrences.class.getName(), ZOrder.SHOW_OFF_RACK.forPosition(20), true, MarkOccurrences.getHighlightsBag(context.getDocument())),
+ };
+ }
+
+ }
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java
index c4af00f..bab8e44 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TextDocumentSyncServerCapabilityHandler.java
@@ -112,6 +112,11 @@ public class TextDocumentSyncServerCapabilityHandler {
text[0]);
server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(textDocumentItem));
+ if (opened.getClientProperty(MarkOccurrences.class) == null) {
+ MarkOccurrences mo = new MarkOccurrences(opened);
+ LSPBindings.addBackgroundTask(file, mo);
+ opened.putClientProperty(MarkOccurrences.class, mo);
+ }
server.scheduleBackgroundTasks(file);
});
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org
For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists