diff --git a/ide/editor.lib/src/org/netbeans/editor/GlyphGutter.java b/ide/editor.lib/src/org/netbeans/editor/GlyphGutter.java
index 87d5d5608cd4..626cd0a0df3f 100644
--- a/ide/editor.lib/src/org/netbeans/editor/GlyphGutter.java
+++ b/ide/editor.lib/src/org/netbeans/editor/GlyphGutter.java
@@ -531,6 +531,8 @@ public void run() {
Shape pViewAlloc = pViewDesc.getAllocation();
Rectangle pViewRect = pViewAlloc.getBounds();
pViewRect.width = repaintWidth;
+ pViewRect.y += pViewDesc.getShadowHeight();
+ pViewRect.height -= pViewDesc.getShadowHeight();
if (pViewRect.y >= endRepaintY) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("GlyphGutter: pViewRect.y=" + pViewRect.y + " >= endRepaintY=" + // NOI18N
diff --git a/ide/editor.lib2/nbproject/project.properties b/ide/editor.lib2/nbproject/project.properties
index a539fb759979..c7277bfe7d63 100644
--- a/ide/editor.lib2/nbproject/project.properties
+++ b/ide/editor.lib2/nbproject/project.properties
@@ -16,7 +16,8 @@
# under the License.
is.autoload=true
-javac.source=1.8
+javac.source=17
+javac.target=17
javac.compilerargs=-Xlint:unchecked
spec.version.base=2.50.0
diff --git a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/highlighting/HighlightsList.java b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/highlighting/HighlightsList.java
index 578e9bf0b88a..23a62b88b7d1 100644
--- a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/highlighting/HighlightsList.java
+++ b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/highlighting/HighlightsList.java
@@ -160,13 +160,15 @@ public AttributeSet cutSameFont(Font defaultFont, int maxEndOffset, int wsEndOff
// Extends beyond first highlight
Font firstFont = ViewUtils.getFont(firstAttrs, defaultFont);
Object firstPrependText = firstAttrs != null ? firstAttrs.getAttribute(ViewUtils.KEY_VIRTUAL_TEXT_PREPEND) : null;
+ Object firstShadowPrependText = firstAttrs != null ? firstAttrs.getAttribute(ViewUtils.KEY_SHADOW_TEXT_PREPEND) : null;
int index = 1;
while (true) {
item = get(index);
AttributeSet attrs = item.getAttributes();
Font font = ViewUtils.getFont(attrs, defaultFont);
Object prependText = attrs != null ? attrs.getAttribute(ViewUtils.KEY_VIRTUAL_TEXT_PREPEND) : null;
- if (!font.equals(firstFont) || !Objects.equals(firstPrependText, prependText)) { // Stop at itemEndOffset
+ Object shadowPrependText = attrs != null ? attrs.getAttribute(ViewUtils.KEY_SHADOW_TEXT_PREPEND) : null;
+ if (!font.equals(firstFont) || !Objects.equals(firstPrependText, prependText) || !Objects.equals(firstShadowPrependText, shadowPrependText)) { // Stop at itemEndOffset
if (index == 1) { // Just single attribute set
cutStartItems(1);
startOffset = itemEndOffset; // end offset of first item
diff --git a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/DocumentViewChildren.java b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/DocumentViewChildren.java
index 7bc3eb535220..3c9f461d1fcb 100644
--- a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/DocumentViewChildren.java
+++ b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/DocumentViewChildren.java
@@ -350,7 +350,7 @@ int getNextVisualPositionY(DocumentView docView, int offset, Bias bias, Shape do
pAlloc = getChildAllocation(docView, pIndex, docViewAlloc);
}
docView.op.getTextLayoutCache().activate(pView);
- retOffset = pView.children.getNextVisualPositionY(pView, offset, bias, pAlloc, southDirection, biasRet, x);
+ retOffset = pView.getNextVisualPositionY(pView, offset, bias, pAlloc, southDirection, biasRet, x);
if (retOffset == -1) {
offset = -1; // Continue by entering the paragraph from outside
}
diff --git a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/DocumentViewOp.java b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/DocumentViewOp.java
index cfe34feb58e6..a1e32bf33a76 100644
--- a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/DocumentViewOp.java
+++ b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/DocumentViewOp.java
@@ -257,6 +257,7 @@ public final class DocumentViewOp
private int defaultRowHeightInt;
private int defaultAscentInt;
+ private float defaultDescent;
private float defaultCharWidth;
@@ -1087,6 +1088,7 @@ private void updateRowHeight(FontInfo fontInfo, boolean force) {
": defaultAscentInt from " + defaultAscentInt + " to " + fontInfo.ascentInt + "\n"); // NOI18N
}
defaultAscentInt = fontInfo.ascentInt;
+ defaultDescent = fontInfo.descent;
}
if (force || defaultRowHeightInt < fontInfo.rowHeightInt) {
if (LOG.isLoggable(Level.FINE)) {
@@ -1276,6 +1278,11 @@ public float getDefaultAscent() {
return defaultAscentInt;
}
+ public float getDefaultDescent() {
+ checkSettingsInfo();
+ return defaultDescent;
+ }
+
/**
* Return array of default:
*
diff --git a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/HighlightsViewFactory.java b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/HighlightsViewFactory.java
index 8d0c0b8631f2..55379ba94a85 100644
--- a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/HighlightsViewFactory.java
+++ b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/HighlightsViewFactory.java
@@ -254,7 +254,7 @@ public EditorView createView(int startOffset, int limitOffset, boolean forcedLim
}
private @NonNull EditorView wrapWithPrependedText(@NonNull EditorView origView, @NullAllowed AttributeSet attrs) {
- if (attrs != null && attrs.getAttribute(ViewUtils.KEY_VIRTUAL_TEXT_PREPEND) instanceof String) {
+ if (attrs != null && (attrs.getAttribute(ViewUtils.KEY_VIRTUAL_TEXT_PREPEND) instanceof String || attrs.getAttribute(ViewUtils.KEY_SHADOW_TEXT_PREPEND) instanceof String)) {
return new PrependedTextView(documentView().op, attrs, origView);
}
diff --git a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ParagraphView.java b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ParagraphView.java
index 1362f25277b7..fb37327c4bff 100644
--- a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ParagraphView.java
+++ b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ParagraphView.java
@@ -21,11 +21,15 @@
import java.awt.BasicStroke;
import java.awt.Color;
+import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
+import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.List;
import java.util.logging.Logger;
import javax.swing.JComponent;
import javax.swing.SwingConstants;
@@ -113,6 +117,8 @@ public final class ParagraphView extends EditorView implements EditorView.Parent
private int statusBits; // 44 + 4 = 48 bytes
+ private List shadowText; // 44 + 4 = 52 bytes
+
public ParagraphView(Position startPos) {
super(null);
setStartPosition(startPos);
@@ -225,6 +231,39 @@ public void replace(int index, int removeLength, View[] addViews) {
assert (removeLength == 0) : "Attempt to remove from null children length=" + removeLength; // NOI18N
children = new ParagraphViewChildren(addViews.length);
}
+ if (index == 0 && addViews != null) {
+ if (shadowText != null && !shadowText.isEmpty()) {
+ shadowText.clear();
+ }
+
+ AttributeSet attrs = addViews[0].getAttributes();
+ Object shadowTextDataAttr = attrs != null ? attrs.getAttribute(ViewUtils.KEY_VERTICAL_SHADOW_TEXT_PREPEND) : null;
+ DocumentView docView = getDocumentView();
+ DocumentViewOp op = docView.op;
+ float shadowTextHeight = 0;
+ float shadowTextWidth = 0;
+
+ if (shadowTextDataAttr instanceof String shadowTextData) {
+ if (shadowText == null) {
+ shadowText = new ArrayList<>();
+ }
+ Font shadowTextFont = ViewUtils.getFont(getAttributes(), op.getDefaultFont());
+ for (String line : shadowTextData.split("\n")) { //XXX: performance!
+ //duplicated code:
+ if (line.isEmpty()) {
+ line = " ";
+ }
+ final TextLayout l = op.createTextLayout(line, shadowTextFont);
+ shadowText.add(l);
+ shadowTextHeight += height(l); //???
+ shadowTextWidth = Math.max(shadowTextWidth, l.getAdvance()); //???
+ }
+ }
+
+ children.shadowHeight = shadowTextHeight;
+ children.shadowWidth = shadowTextWidth;
+ }
+
children.replace(this, index, removeLength, addViews);
}
@@ -356,7 +395,18 @@ public int getViewIndexChecked(double x, double y, Shape alloc) {
@Override
public Shape modelToViewChecked(int offset, Shape alloc, Bias bias) {
checkChildrenNotNull();
- return children.modelToViewChecked(this, offset, alloc, bias);
+ Shape ret = children.modelToViewChecked(this, offset, alloc, bias);
+ if (shadowText != null) {
+ //XXX: cached shadowText height
+ Rectangle2D bounds = ret.getBounds2D();
+ float shadowHeight = 0;
+ for (TextLayout l : shadowText) {
+ shadowHeight += height(l);
+ }
+ bounds.setRect(bounds.getX(), bounds.getY() + shadowHeight, bounds.getWidth(), bounds.getHeight());
+ ret = bounds;
+ }
+ return ret;
}
@Override
@@ -369,6 +419,24 @@ public int viewToModelChecked(double x, double y, Shape alloc, Bias[] biasReturn
public void paint(Graphics2D g, Shape alloc, Rectangle clipBounds) {
// The background is already cleared by BasicTextUI.paintBackground() which uses component.getBackground()
checkChildrenNotNull();
+ if (shadowText != null && !shadowText.isEmpty()) {
+ Rectangle2D.Double bounds = ViewUtils.shape2Bounds(alloc);
+ Rectangle2D.Double span = new Rectangle2D.Double(0, 0, 0, 0);
+ double y = bounds.y;
+ for (TextLayout l : shadowText) {
+ g.setColor(Color.gray);
+ float h = height(l);
+ span.setRect(bounds.x, y, l.getAdvance(), h);
+ HighlightsViewUtils.paintTextLayout(g, span, l, getDocumentView());
+ y += h;
+ }
+
+ bounds.height -= y - bounds.y;
+ bounds.y = y;
+
+ alloc = bounds;
+ }
+
children.paint(this, g, alloc, clipBounds);
if (getDocumentView().op.isGuideLinesEnable()) {
@@ -507,6 +575,12 @@ public int getNextVisualPositionFromChecked(int offset, Bias bias, Shape alloc,
retOffset = children.getNextVisualPositionY(this, offset, bias, alloc,
direction == SwingConstants.SOUTH, biasRet,
HighlightsViewUtils.getMagicX(docView, this, offset, bias, alloc));
+ if (shadowText != null && direction == SwingConstants.SOUTH) {
+ //TODO: reuse computed height
+ for (TextLayout l : shadowText) {
+ retOffset += height(l);
+ }
+ }
} else {
retOffset = offset;
}
@@ -516,7 +590,19 @@ public int getNextVisualPositionFromChecked(int offset, Bias bias, Shape alloc,
}
return retOffset;
}
-
+
+ //TODO: investigate:
+ int getNextVisualPositionY(ParagraphView pView,
+ int offset, Bias bias, Shape pAlloc, boolean southDirection, Bias[] biasRet, double x) {
+ int retOffset = children.getNextVisualPositionY(pView, offset, bias, pAlloc, southDirection, biasRet, x);
+// if (shadowText != null && southDirection) {
+// for (TextLayout l : shadowText) {
+// retOffset += l.getAscent() + l.getDescent();
+// }
+// }
+ return retOffset;
+ }
+
void releaseTextLayouts() {
children = null;
markChildrenInvalid();
@@ -629,4 +715,7 @@ protected String getDumpName() {
return "PV";
}
+ private float height(TextLayout l) {
+ return (float) Math.ceil(l.getAscent() + l.getDescent());
+ }
}
diff --git a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ParagraphViewChildren.java b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ParagraphViewChildren.java
index 3275a682472f..4bfb6133d9e2 100644
--- a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ParagraphViewChildren.java
+++ b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ParagraphViewChildren.java
@@ -75,17 +75,21 @@ public ParagraphViewChildren(int capacity) {
boolean isWrapped() {
return (wrapInfo != null);
}
-
+
+ //TODO: move to ParagraphView:
+ float shadowHeight;
+ float shadowWidth;
+
/**
* Height of
* @return
*/
float height() {
- return (wrapInfo == null) ? childrenHeight : wrapInfo.height(this);
+ return ((wrapInfo == null) ? childrenHeight : wrapInfo.height(this)) + shadowHeight;
}
float width() {
- return (wrapInfo == null) ? (float) childrenWidth() : wrapInfo.width();
+ return Math.max((wrapInfo == null) ? (float) childrenWidth() : wrapInfo.width(), shadowWidth);
}
double childrenWidth() {
diff --git a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ParagraphViewDescriptor.java b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ParagraphViewDescriptor.java
index 2e0b4b67de27..f9131153ed9a 100644
--- a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ParagraphViewDescriptor.java
+++ b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ParagraphViewDescriptor.java
@@ -90,5 +90,9 @@ public float getAscent() {
return docView.op.getDefaultAscent(); // Currently the ascent is global
}
+ public float getShadowHeight() {
+ ParagraphViewChildren pViewChildren = docView.getParagraphView(pViewIndex).children;
+ return pViewChildren != null ? pViewChildren.shadowHeight : 0;
+ }
}
diff --git a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/PrependedTextView.java b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/PrependedTextView.java
index e1b8b4649ab0..888a50aa8bc7 100644
--- a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/PrependedTextView.java
+++ b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/PrependedTextView.java
@@ -39,27 +39,50 @@ public final class PrependedTextView extends EditorView {
private final EditorView delegate;
private final TextLayout prependedTextLayout;
private final double prependedTextWidth;
+ private final TextLayout shadowPrependedTextLayout;
+ private final double shadowPrependedTextWidth;
public PrependedTextView(DocumentViewOp op, AttributeSet attributes, EditorView delegate) {
super(null);
this.attributes = attributes;
this.delegate = delegate;
- Font font = ViewUtils.getFont(attributes, op.getDefaultHintFont());
- prependedTextLayout = op.createTextLayout((String) attributes.getAttribute(ViewUtils.KEY_VIRTUAL_TEXT_PREPEND), font);
- // Advance represents the width of the full string, including leading
- // and trailing spaces
- float width = prependedTextLayout.getAdvance();
- // The prependTextWidth is rounded to full char widths, so that layout
- // is not destroyed too much
- double em = op.getDefaultCharWidth();
- prependedTextWidth = Math.ceil(width / em) * em;
+
+ if (attributes.getAttribute(ViewUtils.KEY_SHADOW_TEXT_PREPEND) instanceof String shadowText) {
+ Font shadowTextFont = ViewUtils.getFont(attributes, op.getDefaultFont());
+ shadowPrependedTextLayout = op.createTextLayout(shadowText, shadowTextFont);
+ // Advance represents the width of the full string, including leading
+ // and trailing spaces
+ float width = shadowPrependedTextLayout.getAdvance();
+ // The prependTextWidth is rounded to full char widths, so that layout
+ // is not destroyed too much
+ double em = op.getDefaultCharWidth();
+ shadowPrependedTextWidth = Math.ceil(width / em) * em;
+ } else {
+ shadowPrependedTextLayout = null;
+ shadowPrependedTextWidth = 0;
+ }
+
+ if (attributes.getAttribute(ViewUtils.KEY_VIRTUAL_TEXT_PREPEND) instanceof String virtualText) {
+ Font font = ViewUtils.getFont(attributes, op.getDefaultHintFont());
+ prependedTextLayout = op.createTextLayout(virtualText, font);
+ // Advance represents the width of the full string, including leading
+ // and trailing spaces
+ float width = prependedTextLayout.getAdvance();
+ // The prependTextWidth is rounded to full char widths, so that layout
+ // is not destroyed too much
+ double em = op.getDefaultCharWidth();
+ prependedTextWidth = Math.ceil(width / em) * em;
+ } else {
+ prependedTextLayout = null;
+ prependedTextWidth = 0;
+ }
}
@Override
public float getPreferredSpan(int axis) {
float superSpan = delegate.getPreferredSpan(axis);
if (axis == View.X_AXIS) {
- superSpan += prependedTextWidth;
+ superSpan += prependedTextWidth + shadowPrependedTextWidth;
}
return superSpan;
}
@@ -73,16 +96,16 @@ public AttributeSet getAttributes() {
public Shape modelToViewChecked(int offset, Shape alloc, Bias bias) {
Shape res = delegate.modelToViewChecked(offset, alloc, bias);
Rectangle2D rect = ViewUtils.shapeAsRect(res);
- rect.setRect(rect.getX() + prependedTextWidth, rect.getY(), rect.getWidth(), rect.getHeight());
+ rect.setRect(rect.getX(), rect.getY(), rect.getWidth() + prependedTextWidth + shadowPrependedTextWidth, rect.getHeight());
return rect;
}
@Override
public void paint(Graphics2D g, Shape hViewAlloc, Rectangle clipBounds) {
Rectangle2D span = ViewUtils.shapeAsRect(hViewAlloc);
- span.setRect(span.getX() + prependedTextWidth, span.getY(), span.getWidth() - prependedTextWidth, span.getHeight());
+ span.setRect(span.getX() + prependedTextWidth + shadowPrependedTextWidth, span.getY(), span.getWidth() - prependedTextWidth - shadowPrependedTextWidth, span.getHeight());
delegate.paint(g, span, clipBounds);
- span.setRect(span.getX() - prependedTextWidth, span.getY(), prependedTextWidth, span.getHeight());
+ span.setRect(span.getX() - prependedTextWidth - shadowPrependedTextWidth, span.getY(), prependedTextWidth + shadowPrependedTextWidth, span.getHeight());
HighlightsSequence highlights = getDocumentView().getPaintHighlights(this, 0);
@@ -92,9 +115,17 @@ public void paint(Graphics2D g, Shape hViewAlloc, Rectangle clipBounds) {
HighlightsViewUtils.paintBackgroundHighlights(g, span, attrs, getDocumentView()); //TODO: clear some attributes (like boxes)???
}
- g.setColor(Color.gray);
- span.setRect(span.getX(), span.getY(), prependedTextWidth, span.getHeight());
- HighlightsViewUtils.paintTextLayout(g, span, prependedTextLayout, getDocumentView());
+ if (shadowPrependedTextLayout != null) {
+ g.setColor(Color.gray);
+ span.setRect(span.getX(), span.getY(), shadowPrependedTextWidth, span.getHeight());
+ HighlightsViewUtils.paintTextLayout(g, span, shadowPrependedTextLayout, getDocumentView());
+ }
+
+ if (prependedTextLayout != null) {
+ g.setColor(Color.gray);
+ span.setRect(span.getX() + shadowPrependedTextWidth, span.getY(), prependedTextWidth, span.getHeight());
+ HighlightsViewUtils.paintTextLayout(g, span, prependedTextLayout, getDocumentView());
+ }
}
ParagraphView getParagraphView() {
@@ -119,8 +150,8 @@ public void setRawEndOffset(int offset) {
@Override
public int viewToModelChecked(double x, double y, Shape alloc, Position.Bias[] biasReturn) {
Rectangle2D bounds = ViewUtils.shapeAsRect(alloc);
- bounds.setRect(bounds.getX() + prependedTextWidth, bounds.getY(),
- bounds.getWidth() - prependedTextWidth, bounds.getHeight());
+ bounds.setRect(bounds.getX() + prependedTextWidth + shadowPrependedTextWidth, bounds.getY(),
+ bounds.getWidth() - prependedTextWidth + shadowPrependedTextWidth, bounds.getHeight());
if (x <= bounds.getX()) {
return getStartOffset();
}
diff --git a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ViewUtils.java b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ViewUtils.java
index a4c97adfd493..21098c782cec 100644
--- a/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ViewUtils.java
+++ b/ide/editor.lib2/src/org/netbeans/modules/editor/lib2/view/ViewUtils.java
@@ -45,6 +45,8 @@
public final class ViewUtils {
public static final String KEY_VIRTUAL_TEXT_PREPEND = "virtual-text-prepend"; //NOI18N
+ public static final String KEY_SHADOW_TEXT_PREPEND = "shadow-text-prepend"; //NOI18N
+ public static final String KEY_VERTICAL_SHADOW_TEXT_PREPEND = "vertical-shadow-text-prepend"; //NOI18N
private ViewUtils() { // No instances
}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/EnhancedLanguageServer.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/EnhancedLanguageServer.java
new file mode 100644
index 000000000000..7cfd418d5ee3
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/EnhancedLanguageServer.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+import org.eclipse.lsp4j.jsonrpc.services.JsonDelegate;
+import org.eclipse.lsp4j.services.LanguageServer;
+
+public interface EnhancedLanguageServer extends LanguageServer {
+
+ @JsonDelegate
+ public EnhancedTextDocumentService getEnhancedTextDocumentService();
+
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/EnhancedTextDocumentService.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/EnhancedTextDocumentService.java
new file mode 100644
index 000000000000..a2a3d860abea
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/EnhancedTextDocumentService.java
@@ -0,0 +1,114 @@
+/*
+ * 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;
+
+import java.util.concurrent.CompletableFuture;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.TextDocumentIdentifier;
+import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
+import org.eclipse.lsp4j.services.TextDocumentService;
+
+public interface EnhancedTextDocumentService extends TextDocumentService {
+ @JsonRequest("textDocument/inlineCompletion")
+ public default CompletableFuture inlineCompletion(InlineCompletionParams params) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static class InlineCompletionParams {
+ private TextDocumentIdentifier textDocument;
+ private Position position;
+ private InlineCompletionContext context;
+
+ public InlineCompletionParams() {
+ }
+
+ public InlineCompletionParams(TextDocumentIdentifier textDocument, Position position, InlineCompletionContext context) {
+ this.textDocument = textDocument;
+ this.position = position;
+ this.context = context;
+ }
+
+ public TextDocumentIdentifier getTextDocument() {
+ return textDocument;
+ }
+
+ public void setTextDocument(TextDocumentIdentifier textDocument) {
+ this.textDocument = textDocument;
+ }
+
+ public Position getPosition() {
+ return position;
+ }
+
+ public void setPosition(Position position) {
+ this.position = position;
+ }
+
+ public InlineCompletionContext getContext() {
+ return context;
+ }
+
+ public void setContext(InlineCompletionContext context) {
+ this.context = context;
+ }
+
+ }
+
+ public static class InlineCompletionContext {
+ private Object triggerKind;
+ private Object selectedCompletionInfo;
+
+ public Object getTriggerKind() {
+ return triggerKind;
+ }
+
+ public void setTriggerKind(Object triggerKind) {
+ this.triggerKind = triggerKind;
+ }
+
+ public Object getSelectedCompletionInfo() {
+ return selectedCompletionInfo;
+ }
+
+ public void setSelectedCompletionInfo(Object selectedCompletionInfo) {
+ this.selectedCompletionInfo = selectedCompletionInfo;
+ }
+
+ }
+
+ public static class InlineCompletionItem {
+ private String insertText;
+
+ public InlineCompletionItem() {
+ }
+
+ public InlineCompletionItem(String insertText) {
+ this.insertText = insertText;
+ }
+
+ public String getInsertText() {
+ return insertText;
+ }
+
+ public void setInsertText(String insertText) {
+ this.insertText = insertText;
+ }
+
+ }
+}
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 13a646a235c2..b9a1028594d7 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
@@ -313,15 +313,15 @@ private static void startFailed(ServerDescription description, String mimeType)
try {
LanguageClientImpl lci = new LanguageClientImpl();
- LanguageServer server = LanguageServerProviderAccessor.getINSTANCE().getServer(desc);
+ EnhancedLanguageServer server = LanguageServerProviderAccessor.getINSTANCE().getServer(desc);
Process process;
if (server == null) {
InputStream in = LanguageServerProviderAccessor.getINSTANCE().getInputStream(desc);
OutputStream out = LanguageServerProviderAccessor.getINSTANCE().getOutputStream(desc);
process = LanguageServerProviderAccessor.getINSTANCE().getProcess(desc);
- Launcher.Builder launcherBuilder = new LSPLauncher.Builder()
+ Launcher.Builder launcherBuilder = new LSPLauncher.Builder()
.setLocalService(lci)
- .setRemoteInterface(LanguageServer.class)
+ .setRemoteInterface(EnhancedLanguageServer.class)
.setInput(in)
.setOutput(out)
.configureGson(gson -> {
@@ -361,7 +361,7 @@ public void close() throws IOException {
});
launcherBuilder.traceMessages(pw);
}
- Launcher launcher = launcherBuilder.create();
+ Launcher launcher = launcherBuilder.create();
launcher.startListening();
server = launcher.getRemoteProxy();
} else {
@@ -414,16 +414,23 @@ public static void addBindings(FileObject root, int port, String... extensions)
LanguageClientImpl lc = new LanguageClientImpl();
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
- Launcher launcher = LSPLauncher.createClientLauncher(lc, in, new OutputStream() {
+ final OutputStream wrappedOut = new OutputStream() {
@Override
public void write(int w) throws IOException {
out.write(w);
if (w == '\n')
out.flush();
}
- });
+ };
+// Launcher launcher = LSPLauncher.createClientLauncher(lc, in, wrappedOut);
+ Launcher launcher = new Launcher.Builder()
+ .setLocalService(lc)
+ .setRemoteInterface(EnhancedLanguageServer.class)
+ .setInput(in)
+ .setOutput(out)
+ .create();
launcher.startListening();
- LanguageServer server = launcher.getRemoteProxy();
+ EnhancedLanguageServer server = launcher.getRemoteProxy();
InitializeResult result = initServer(null, server, root);
server.initialized(new InitializedParams());
LSPBindings bindings = new LSPBindings(server, result, null);
@@ -518,18 +525,18 @@ public static synchronized Set getAllBindings() {
return allBindings;
}
- private final LanguageServer server;
+ private final EnhancedLanguageServer server;
private final InitializeResult initResult;
private final Process process;
- private LSPBindings(LanguageServer server, InitializeResult initResult, Process process) {
+ private LSPBindings(EnhancedLanguageServer server, InitializeResult initResult, Process process) {
this.server = server;
this.initResult = initResult;
this.process = process;
}
- public TextDocumentService getTextDocumentService() {
- return server.getTextDocumentService();
+ public EnhancedTextDocumentService getTextDocumentService() {
+ return server.getEnhancedTextDocumentService();
}
public WorkspaceService getWorkspaceService() {
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/LanguageServerProviderAccessor.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/LanguageServerProviderAccessor.java
index 9fab198d7a19..7961ab5990ec 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/LanguageServerProviderAccessor.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/LanguageServerProviderAccessor.java
@@ -53,8 +53,8 @@ public static void setINSTANCE (LanguageServerProviderAccessor instance) {
public abstract InputStream getInputStream(LanguageServerDescription desc);
public abstract OutputStream getOutputStream(LanguageServerDescription desc);
public abstract Process getProcess(LanguageServerDescription desc);
- public abstract LanguageServer getServer(LanguageServerDescription desc);
+ public abstract EnhancedLanguageServer getServer(LanguageServerDescription desc);
public abstract LSPBindings getBindings(LanguageServerDescription desc);
public abstract void setBindings(LanguageServerDescription desc, LSPBindings bindings);
- public abstract LanguageServerDescription createLanguageServerDescription(@NonNull LanguageServer server);
+ public abstract LanguageServerDescription createLanguageServerDescription(@NonNull EnhancedLanguageServer server);
}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java
index 7bd4b2b8abda..89f87a393ce4 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java
@@ -31,6 +31,7 @@
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
@@ -435,4 +436,45 @@ public interface Environment {
public void handleCancellationException(CancellationException ex);
public void handleException(Exception ex);
}
+
+ public static class CancelableBaseEnvironment implements Environment {
+ private final AtomicBoolean cancel = new AtomicBoolean();
+ private final List cancelCallbacks = new ArrayList<>();
+
+ public void cancelRequest() {
+ cancel.set(true);
+ List localCancelCallbacks;
+ synchronized (cancelCallbacks) {
+ localCancelCallbacks = new ArrayList<>(cancelCallbacks);
+ }
+ localCancelCallbacks.forEach(Runnable::run);
+ }
+
+ @Override
+ public boolean isCanceled() {
+ return cancel.get();
+ }
+
+ @Override
+ public void registerCancelCallback(Runnable callback) {
+ synchronized (cancelCallbacks) {
+ cancelCallbacks.add(callback);
+ }
+
+ if (cancel.get()) {
+ callback.run();
+ }
+ }
+
+ @Override
+ public void handleCancellationException(CancellationException ex) {
+ //ignore cancels
+ }
+
+ @Override
+ public void handleException(Exception ex) {
+ Exceptions.printStackTrace(ex);
+ }
+
+ }
}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/InlineCompletion.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/InlineCompletion.java
new file mode 100644
index 000000000000..52256325aa8d
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/InlineCompletion.java
@@ -0,0 +1,352 @@
+/*
+ * 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 java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.JTextComponent;
+import javax.swing.text.Position;
+import org.eclipse.lsp4j.TextDocumentIdentifier;
+import org.netbeans.api.editor.*;
+import org.netbeans.api.editor.document.LineDocument;
+import org.netbeans.api.editor.document.LineDocumentUtils;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.api.editor.settings.AttributesUtilities;
+import org.netbeans.editor.BaseKit.InsertTabAction;
+import org.netbeans.lib.editor.util.swing.DocumentUtilities;
+import org.netbeans.modules.editor.NbEditorUtilities;
+import org.netbeans.modules.editor.indent.api.IndentUtils;
+import org.netbeans.modules.lsp.client.EnhancedTextDocumentService.InlineCompletionParams;
+import org.netbeans.modules.lsp.client.LSPBindings;
+import org.netbeans.modules.lsp.client.Utils;
+import org.netbeans.modules.lsp.client.Utils.CancelableBaseEnvironment;
+import org.netbeans.spi.editor.AbstractEditorAction;
+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.modules.OnStart;
+import org.openide.util.Exceptions;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.RequestProcessor;
+import org.openide.util.RequestProcessor.Task;
+
+//TODO: shutdown
+public class InlineCompletion {
+ private static final RequestProcessor PREPARE_WORKER = new RequestProcessor(InlineCompletion.class.getName() + "-prepare", 1, false, false);
+ private static final RequestProcessor RUN_WORKER = new RequestProcessor(InlineCompletion.class.getName() + "-run", 1, false, false);
+
+ @OnStart
+ public static class Start implements Runnable {
+
+ @Override
+ public void run() {
+ EditorRegistry.addPropertyChangeListener(new EditorListener());
+ }
+
+ }
+
+ private static class EditorListener implements PropertyChangeListener, CaretListener {
+
+ public EditorListener() {
+ }
+
+ private final Set openComponents = new HashSet<>();
+ private JTextComponent lastComponent;
+ private AtomicReference currentFile = new AtomicReference<>();
+ private AtomicReference currentDocument = new AtomicReference<>();
+ private AtomicInteger currentCaretPos = new AtomicInteger();
+ private AtomicReference currentQuery = new AtomicReference<>();
+ private final Task query = PREPARE_WORKER.create(this::doQuery);
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ JTextComponent c = EditorRegistry.lastFocusedComponent();
+ if (lastComponent != c) {
+ if (lastComponent != null) {
+ lastComponent.removeCaretListener(this);
+ }
+ lastComponent = c;
+ if (c != null) {
+ FileObject file = NbEditorUtilities.getFileObject(c.getDocument());
+ currentFile.set(file);
+ currentDocument.set(c.getDocument());
+ currentCaretPos.set(-1);
+ boolean wasOpen = openComponents.contains(c);
+ openComponents.clear();
+ openComponents.addAll(EditorRegistry.componentList());
+ c.addCaretListener(this);
+ } else {
+ currentFile.set(null);
+ currentDocument.set(null);
+ }
+ }
+ }
+
+ @Override
+ public void caretUpdate(CaretEvent e) {
+ currentCaretPos.set(e.getDot());
+ scheduleQuery();
+ }
+
+ private void scheduleQuery() {
+ query.schedule(500);
+ }
+
+ private void doQuery() {
+ //TODO: only if the rest of the line is empty??
+ FileObject file = currentFile.get();
+ Document doc = currentDocument.get();
+ int caretPos = currentCaretPos.get();
+
+ if (file != null && doc != null && caretPos != (-1)) {
+ ProposalItem existingProposal = ProposalItem.get(doc);
+ long thisVersion = DocumentUtilities.getDocumentVersion(doc);
+ if (existingProposal != null) {
+ //TODO: tracking typing modifications:
+ if (existingProposal.documentVersion == thisVersion &&
+ caretPos == existingProposal.location.getOffset()) {
+ //skip
+ return ;
+ }
+ ProposalItem.clearProposal(doc);
+ }
+
+ CancelableBaseEnvironment currentRunningQuery = currentQuery.get();
+
+ if (currentRunningQuery != null) {
+ currentRunningQuery.cancelRequest();
+ currentQuery.compareAndSet(currentRunningQuery, null);
+ }
+
+ CancelableBaseEnvironment env = new CancelableBaseEnvironment();
+ currentQuery.set(env);
+
+ RUN_WORKER.post(() -> {
+ if (env.isCanceled()) {
+ return ;
+ }
+
+ List bindings = LSPBindings.getBindings(file);
+ AtomicBoolean wasProposal = new AtomicBoolean();
+
+ Utils.handleBindings(bindings,
+ capa -> true, //TODO
+ () -> new InlineCompletionParams(new TextDocumentIdentifier(Utils.toURI(file)), Utils.createPosition(doc, caretPos), null),
+ (server, params) -> server.getTextDocumentService()
+ .inlineCompletion(params),
+ (server, proposals) -> {
+ if (wasProposal.getAndSet(true)) {
+ //only first:
+ return ;
+ }
+ if (proposals != null && proposals.length > 0 && thisVersion == DocumentUtilities.getDocumentVersion(doc)) {
+ //TODO: more proper re-indent possible?
+ try {
+ int indent = IndentUtils.lineIndent(doc, IndentUtils.lineStartOffset(doc, caretPos));
+ String proposalText = proposals[0].getInsertText().replaceAll("\n", "\n" + IndentUtils.createIndentString(doc, indent));
+ ProposalItem.putProposal(server, doc, caretPos, proposalText);
+ } catch (BadLocationException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+ },
+ env
+ );
+ });
+ }
+ }
+ }
+
+ private static class ProposalItem implements DocumentListener {
+ private final LSPBindings bindings;
+ public final Position location;
+ public final long documentVersion;
+ public final String text;
+
+ private ProposalItem(LSPBindings bindings, Position location, long documentVersion, String text) {
+ this.bindings = bindings;
+ this.location = location;
+ this.documentVersion = documentVersion;
+ this.text = text;
+ }
+
+ public static void putProposal(LSPBindings bindings, Document doc, int pos, String text) {
+ doc.render(() -> {
+ try {
+ LineDocument ldoc = LineDocumentUtils.asRequired(doc, LineDocument.class);
+ //TODO: should take the version at the time the proposal was triggered?
+ ProposalItem i = new ProposalItem(bindings, ldoc.createPosition(pos, /*XXX*/Position.Bias.Forward), DocumentUtilities.getDocumentVersion(doc), text);
+ doc.putProperty(ProposalItem.class, i);
+ i.setShadowText(doc, pos);
+ doc.addDocumentListener(i);
+ } catch (BadLocationException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ });
+ }
+
+ public static void clearProposal(Document doc) {
+ ProposalItem i = (ProposalItem) doc.getProperty(ProposalItem.class);
+ doc.putProperty(ProposalItem.class, null);
+ if (i != null) {
+ i.clearShadowText(doc);
+ doc.removeDocumentListener(i);
+ }
+ }
+
+ public static ProposalItem get(Document doc) {
+ return (ProposalItem) doc.getProperty(ProposalItem.class);
+ }
+
+ private void setShadowText(Document doc, int caretPos) {
+ OffsetsBag bag = new OffsetsBag(doc);
+ int common = caretPos - location.getOffset();
+ try {
+ if (common < 0 || !text.startsWith(doc.getText(location.getOffset(), common))) {
+ clearProposal(doc);
+ return ;
+ }
+ } catch (BadLocationException ex) {
+ Exceptions.printStackTrace(ex);
+ return ;
+ }
+ String reducedText = text.substring(common);
+ String[] firstLineAndTheRest = reducedText.split("\n", 2);
+
+ bag.addHighlight(caretPos, caretPos + 1, AttributesUtilities.createImmutable("shadow-text-prepend", firstLineAndTheRest[0]));
+
+ if (firstLineAndTheRest.length > 1) {
+ bag.addHighlight(caretPos + 1, caretPos + 2, AttributesUtilities.createImmutable("vertical-shadow-text-prepend", firstLineAndTheRest[1]));
+ }
+
+ getShadowTextBag(doc).setHighlights(bag);
+ }
+
+ private void clearShadowText(Document doc) {
+ OffsetsBag bag = new OffsetsBag(doc);
+
+ getShadowTextBag(doc).setHighlights(bag);
+ }
+
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ Document doc = e.getDocument();
+ ProposalItem i = get(doc);
+
+ if (i == null) {
+ return ;
+ }
+
+ if (!DocumentUtilities.isTypingModification(doc)) {
+ clearProposal(doc);
+ }
+
+ i.setShadowText(doc, e.getOffset());
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ Document doc = e.getDocument();
+ ProposalItem i = get(doc);
+
+ if (i == null) {
+ return ;
+ }
+
+ if (!DocumentUtilities.isTypingModification(doc)) {
+ clearProposal(doc);
+ }
+
+ i.setShadowText(doc, e.getOffset());
+ }
+
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ }
+
+ }
+
+ private static final Object KEY_SHADOW_TEXT = new Object();
+ static OffsetsBag getShadowTextBag(Document doc) {
+ OffsetsBag bag = (OffsetsBag) doc.getProperty(KEY_SHADOW_TEXT);
+
+ if (bag == null) {
+ doc.putProperty(KEY_SHADOW_TEXT, bag = new OffsetsBag(doc));
+ }
+
+ return bag;
+ }
+
+ @MimeRegistration(mimeType="", service=HighlightsLayerFactory.class)
+ public static class HighlightsLayerFactoryImpl implements HighlightsLayerFactory {
+
+ public HighlightsLayer[] createLayers(HighlightsLayerFactory.Context context) {
+ return new HighlightsLayer[] {
+ HighlightsLayer.create(InlineCompletion.class.getName(), ZOrder.SYNTAX_RACK.forPosition(1700), false, getShadowTextBag(context.getDocument())),
+ };
+ }
+
+ }
+
+ @EditorActionRegistration(name = "lsp-inline-completion-proposal-accept")
+ @Messages("lsp-inline-completion-proposal-accept=Inline Completion Proposal Accept")
+ public static class ConfimAction extends AbstractEditorAction {
+
+ private final InsertTabAction delegate = new InsertTabAction();
+
+ @Override
+ protected void actionPerformed(ActionEvent evt, final JTextComponent target) {
+ if (target != null) {
+ ProposalItem i = ProposalItem.get(target.getDocument());
+
+ if (i != null) {
+ int caret = target.getCaret().getDot();
+ Document doc = target.getDocument();
+
+ try {
+ doc.insertString(caret, i.text, null);
+ } catch (BadLocationException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+
+ ProposalItem.clearProposal(doc);
+ } else {
+ //XXx:
+ delegate.actionPerformed(evt, target);
+ }
+ }
+ }
+
+ }
+}
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java
index 6c521df6e630..4f545ef0013a 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/refactoring/Refactoring.java
@@ -53,6 +53,7 @@
import org.netbeans.api.queries.FileEncodingQuery;
import org.netbeans.modules.lsp.client.LSPBindings;
import org.netbeans.modules.lsp.client.Utils;
+import org.netbeans.modules.lsp.client.Utils.CancelableBaseEnvironment;
import org.netbeans.modules.lsp.client.Utils.Environment;
import org.netbeans.modules.lsp.client.bindings.refactoring.ModificationResult.Difference;
import org.netbeans.modules.lsp.client.bindings.refactoring.tree.DiffElement;
@@ -333,32 +334,9 @@ private static String uri2SimpleName(String uri) {
return uri.substring(dot + 1);
}
- protected static class RefactoringBase implements Environment {
- private final AtomicBoolean cancel = new AtomicBoolean();
- private final List cancelCallbacks = new ArrayList<>();
+ protected static class RefactoringBase extends CancelableBaseEnvironment {
private Problem problem;
- public void cancelRequest() {
- cancel.set(true);
- List localCancelCallbacks;
- synchronized (cancelCallbacks) {
- localCancelCallbacks = new ArrayList<>(cancelCallbacks);
- }
- localCancelCallbacks.forEach(Runnable::run);
- }
-
- @Override
- public boolean isCanceled() {
- return cancel.get();
- }
-
- @Override
- public void registerCancelCallback(Runnable callback) {
- synchronized (cancelCallbacks) {
- cancelCallbacks.add(callback);
- }
- }
-
@Override
public void handleCancellationException(CancellationException ex) {
addProblem(new Problem(false, Bundle.TXT_Canceled()));
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bridge/BridgingLanguageServer.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bridge/BridgingLanguageServer.java
index 700d723228b6..4b5d7591acff 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bridge/BridgingLanguageServer.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bridge/BridgingLanguageServer.java
@@ -74,6 +74,8 @@
import org.netbeans.api.lsp.Completion.Context;
import org.netbeans.api.lsp.Completion.TriggerKind;
import org.netbeans.api.lsp.StructureElement;
+import org.netbeans.modules.lsp.client.EnhancedLanguageServer;
+import org.netbeans.modules.lsp.client.EnhancedTextDocumentService;
import org.netbeans.modules.lsp.client.Utils;
import org.netbeans.spi.lsp.ErrorProvider;
import org.netbeans.spi.lsp.StructureProvider;
@@ -82,7 +84,7 @@
import org.openide.util.RequestProcessor;
import org.openide.util.RequestProcessor.Task;
-public class BridgingLanguageServer implements LanguageServer, LanguageClientAware {
+public class BridgingLanguageServer implements EnhancedLanguageServer, LanguageClientAware {
private static final int DIAGNOSTIC_DELAY = 500;
private static final RequestProcessor WORKER = new RequestProcessor(BridgingLanguageServer.class.getName() + "-worker", 1, false, false);
@@ -147,8 +149,8 @@ private void reRunDiagnostics(FileObject file) {
}
@Override
- public TextDocumentService getTextDocumentService() {
- return new TextDocumentService() {
+ public EnhancedTextDocumentService getTextDocumentService() {
+ return new EnhancedTextDocumentService() {
@Override
public void didOpen(DidOpenTextDocumentParams params) {
FileObject file = Utils.fromURI(params.getTextDocument().getUri());
@@ -262,6 +264,11 @@ public CompletableFuture>> docume
};
}
+ @Override
+ public EnhancedTextDocumentService getEnhancedTextDocumentService() {
+ return getTextDocumentService();
+ }
+
private CompletionItem convertCompletionItem(Document doc, Completion completion) {
CompletionItem item = new CompletionItem(completion.getLabel());
if (completion.getLabelDetail() != null || completion.getLabelDescription() != null) {
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/layer.xml b/ide/lsp.client/src/org/netbeans/modules/lsp/client/layer.xml
index 564f6b5c8478..d64f58826294 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/layer.xml
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/layer.xml
@@ -22,6 +22,13 @@
+
+
+
+
+
+
+
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/resources/NetBeans-keybindings.xml b/ide/lsp.client/src/org/netbeans/modules/lsp/client/resources/NetBeans-keybindings.xml
new file mode 100644
index 000000000000..7cbda61b8017
--- /dev/null
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/resources/NetBeans-keybindings.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/spi/LanguageServerProvider.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/spi/LanguageServerProvider.java
index eea786f3fe34..2b0eee84f906 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/spi/LanguageServerProvider.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/spi/LanguageServerProvider.java
@@ -24,6 +24,7 @@
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
+import org.netbeans.modules.lsp.client.EnhancedLanguageServer;
import org.netbeans.modules.lsp.client.LSPBindings;
import org.netbeans.modules.lsp.client.LanguageServerProviderAccessor;
import org.openide.util.Lookup;
@@ -61,17 +62,17 @@ public static final class LanguageServerDescription {
return new LanguageServerDescription(in, out, process, null);
}
- static @NonNull LanguageServerDescription create(@NonNull LanguageServer server) {
+ static @NonNull LanguageServerDescription create(@NonNull EnhancedLanguageServer server) {
return new LanguageServerDescription(null, null, null, server);
}
private final InputStream in;
private final OutputStream out;
private final Process process;
- private final LanguageServer server;
+ private final EnhancedLanguageServer server;
private LSPBindings bindings;
- private LanguageServerDescription(InputStream in, OutputStream out, Process process, LanguageServer server) {
+ private LanguageServerDescription(InputStream in, OutputStream out, Process process, EnhancedLanguageServer server) {
this.in = in;
this.out = out;
this.process = process;
@@ -96,7 +97,7 @@ public Process getProcess(LanguageServerDescription desc) {
}
@Override
- public LanguageServer getServer(LanguageServerDescription desc) {
+ public EnhancedLanguageServer getServer(LanguageServerDescription desc) {
return desc.server;
}
@@ -111,7 +112,7 @@ public void setBindings(LanguageServerDescription desc, LSPBindings bindings) {
}
@Override
- public LanguageServerDescription createLanguageServerDescription(LanguageServer server) {
+ public LanguageServerDescription createLanguageServerDescription(EnhancedLanguageServer server) {
return LanguageServerDescription.create(server);
}
});