From 8a1fd9e838b666db0845b48850cba169cb99f7c7 Mon Sep 17 00:00:00 2001 From: undisclosed Date: Sat, 1 Feb 2025 05:07:39 +0100 Subject: [PATCH 1/6] An attempt to add inline completion to the LSP client. --- .../src/org/netbeans/editor/GlyphGutter.java | 2 + ide/editor.lib2/nbproject/project.properties | 3 +- .../lib2/highlighting/HighlightsList.java | 6 +- .../lib2/view/DocumentViewChildren.java | 2 +- .../editor/lib2/view/DocumentViewOp.java | 7 + .../lib2/view/HighlightsViewFactory.java | 2 +- .../editor/lib2/view/ParagraphView.java | 93 ++++- .../lib2/view/ParagraphViewChildren.java | 10 +- .../lib2/view/ParagraphViewDescriptor.java | 4 + .../editor/lib2/view/PrependedTextView.java | 67 +++- .../modules/editor/lib2/view/ViewUtils.java | 2 + .../editor/resources/NetBeans-keybindings.xml | 2 +- .../lsp/client/EnhancedLanguageServer.java | 29 ++ .../client/EnhancedTextDocumentService.java | 124 +++++++ .../modules/lsp/client/LSPBindings.java | 29 +- .../LanguageServerProviderAccessor.java | 4 +- .../lsp/client/bindings/InlineCompletion.java | 339 ++++++++++++++++++ .../client/bridge/BridgingLanguageServer.java | 13 +- .../org/netbeans/modules/lsp/client/layer.xml | 10 + .../client/resources/NetBeans-keybindings.xml | 31 ++ .../client/spi/LanguageServerProvider.java | 11 +- 21 files changed, 741 insertions(+), 49 deletions(-) create mode 100644 ide/lsp.client/src/org/netbeans/modules/lsp/client/EnhancedLanguageServer.java create mode 100644 ide/lsp.client/src/org/netbeans/modules/lsp/client/EnhancedTextDocumentService.java create mode 100644 ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/InlineCompletion.java create mode 100644 ide/lsp.client/src/org/netbeans/modules/lsp/client/resources/NetBeans-keybindings.xml diff --git a/ide/editor.lib/src/org/netbeans/editor/GlyphGutter.java b/ide/editor.lib/src/org/netbeans/editor/GlyphGutter.java index b96300a97dad..a5a1240fe115 100644 --- a/ide/editor.lib/src/org/netbeans/editor/GlyphGutter.java +++ b/ide/editor.lib/src/org/netbeans/editor/GlyphGutter.java @@ -530,6 +530,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 58761cc0ad3c..3caa7a11b94f 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.48.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 a97a9598ed37..cf87d464e67d 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,17 @@ public AttributeSet cutSameFont(Font defaultFont, int maxEndOffset, int wsEndOff // Extends beyond first highlight Font firstFont = ViewUtils.getFont(firstAttrs, defaultFont); Object firstPrependText = usePrependText && firstAttrs != null ? firstAttrs.getAttribute(ViewUtils.KEY_VIRTUAL_TEXT_PREPEND) : null; + Object firstShadowPrependText = firstAttrs != null ? firstAttrs.getAttribute(ViewUtils.KEY_SHADOW_TEXT_PREPEND) : null; + Object firstVerticalShadowPrependText = firstAttrs != null ? firstAttrs.getAttribute(ViewUtils.KEY_VERTICAL_SHADOW_TEXT_PREPEND) : null; int index = 1; while (true) { item = get(index); AttributeSet attrs = item.getAttributes(); Font font = ViewUtils.getFont(attrs, defaultFont); Object prependText = usePrependText && 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; + Object verticalShadowPrependText = attrs != null ? attrs.getAttribute(ViewUtils.KEY_VERTICAL_SHADOW_TEXT_PREPEND) : null; + if (!font.equals(firstFont) || !Objects.equals(firstPrependText, prependText) || !Objects.equals(firstShadowPrependText, shadowPrependText) || !Objects.equals(firstShadowPrependText, verticalShadowPrependText)) { // 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 2bad2bfe09fc..98811400aa52 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; @@ -1091,6 +1092,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)) { @@ -1284,6 +1286,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 fce916dd5bba..51af63b24a81 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 @@ -257,7 +257,7 @@ public EditorView createView(int startOffset, int limitOffset, boolean forcedLim private @NonNull EditorView wrapWithPrependedText(@NonNull EditorView origView, @NullAllowed AttributeSet attrs) { boolean inlineHints = documentView().op.isInlineHintsEnable(); - if (attrs != null && inlineHints && attrs.getAttribute(ViewUtils.KEY_VIRTUAL_TEXT_PREPEND) instanceof String) { + if (attrs != null && ((inlineHints && 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/editor/src/org/netbeans/modules/editor/resources/NetBeans-keybindings.xml b/ide/editor/src/org/netbeans/modules/editor/resources/NetBeans-keybindings.xml index fcb9bc4caad6..3923617babbc 100644 --- a/ide/editor/src/org/netbeans/modules/editor/resources/NetBeans-keybindings.xml +++ b/ide/editor/src/org/netbeans/modules/editor/resources/NetBeans-keybindings.xml @@ -92,7 +92,7 @@ - + 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..9b5db0e040e2 --- /dev/null +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/EnhancedTextDocumentService.java @@ -0,0 +1,124 @@ +/* + * 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 String text; //TODO: remove text + private InlineCompletionContext context; + + public InlineCompletionParams() { + } + + public InlineCompletionParams(TextDocumentIdentifier textDocument, Position position, String text, InlineCompletionContext context) { + this.textDocument = textDocument; + this.position = position; + this.text = text; + 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 String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + 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 5794f2d46505..14c3aa9995a9 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 @@ -306,15 +306,15 @@ private static LSPBindings buildBindings(ServerDescription inDescription, Projec foundServer = true; 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 -> { @@ -354,7 +354,7 @@ public void close() throws IOException { }); launcherBuilder.traceMessages(pw); } - Launcher launcher = launcherBuilder.create(); + Launcher launcher = launcherBuilder.create(); launcher.startListening(); server = launcher.getRemoteProxy(); } else { @@ -390,16 +390,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); @@ -482,18 +489,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/bindings/InlineCompletion.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/InlineCompletion.java new file mode 100644 index 000000000000..3d989b0bcb9c --- /dev/null +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/InlineCompletion.java @@ -0,0 +1,339 @@ +/* + * 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.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +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.api.progress.ProgressHandle; +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; +import org.netbeans.modules.lsp.client.LSPBindings; +import org.netbeans.modules.lsp.client.Utils; +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 WORKER = new RequestProcessor(InlineCompletion.class.getName(), 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 final Task query = 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); + } + + LSPBindings bindings = LSPBindings.getBindings(file); + + if (bindings != null) { + //TODO: check if the server has inline completion + boolean hasInlineCompletion = true; + if (hasInlineCompletion) { + ProgressHandle handle = ProgressHandle.createHandle("Running inline completion."); //TODO: progress should be handle by the server/protocol, forcing a progress here seems very intrusive + boolean proposalFound = false; + try { + handle.start(); + //TODO: cancel + CompletableFuture futureProposals = bindings.getTextDocumentService().inlineCompletion(new EnhancedTextDocumentService.InlineCompletionParams(new TextDocumentIdentifier(Utils.toURI(file)), Utils.createPosition(doc, caretPos), doc.getText(0, doc.getLength()), null)); + EnhancedTextDocumentService.InlineCompletionItem[] proposals = futureProposals.get(); + if (proposals != null && proposals.length > 0 && thisVersion == DocumentUtilities.getDocumentVersion(doc)) { + //TODO: more proper re-indent possible? + int indent = IndentUtils.lineIndent(doc, IndentUtils.lineStartOffset(doc, caretPos)); + String proposalText = proposals[0].getInsertText().replaceAll("\n", "\n" + IndentUtils.createIndentString(doc, indent)); + ProposalItem.putProposal(bindings, doc, caretPos, proposalText); + proposalFound = true; + } + } catch (BadLocationException | ExecutionException | InterruptedException ex) { + Exceptions.printStackTrace(ex); + } finally { + if (!proposalFound) { + ProposalItem.clearProposal(doc); + } + handle.finish(); + } + } + } + } + } + } + + 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); + // i.agent.server.autocomplete_clearLastCandidate(null); + } + } + + 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="text/x-java", 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/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..52aeb5d3f026 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,16 @@ + + + + + + + + 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..e397d920d585 --- /dev/null +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/resources/NetBeans-keybindings.xml @@ -0,0 +1,31 @@ + + + + + + + + + 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); } }); From 09979ab71c34477b281517d51f34ce3d5de0f612 Mon Sep 17 00:00:00 2001 From: undisclosed Date: Mon, 3 Feb 2025 21:39:32 +0100 Subject: [PATCH 2/6] Do not send the full text in inline completion request. --- .../lsp/client/EnhancedTextDocumentService.java | 12 +----------- .../lsp/client/bindings/InlineCompletion.java | 3 +-- 2 files changed, 2 insertions(+), 13 deletions(-) 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 index 9b5db0e040e2..a2a3d860abea 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/EnhancedTextDocumentService.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/EnhancedTextDocumentService.java @@ -33,16 +33,14 @@ public default CompletableFuture inlineCompletion(Inline public static class InlineCompletionParams { private TextDocumentIdentifier textDocument; private Position position; - private String text; //TODO: remove text private InlineCompletionContext context; public InlineCompletionParams() { } - public InlineCompletionParams(TextDocumentIdentifier textDocument, Position position, String text, InlineCompletionContext context) { + public InlineCompletionParams(TextDocumentIdentifier textDocument, Position position, InlineCompletionContext context) { this.textDocument = textDocument; this.position = position; - this.text = text; this.context = context; } @@ -62,14 +60,6 @@ public void setPosition(Position position) { this.position = position; } - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } - public InlineCompletionContext getContext() { return context; } 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 index 3d989b0bcb9c..7d1d823ec254 100644 --- 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 @@ -151,7 +151,7 @@ private void doQuery() { try { handle.start(); //TODO: cancel - CompletableFuture futureProposals = bindings.getTextDocumentService().inlineCompletion(new EnhancedTextDocumentService.InlineCompletionParams(new TextDocumentIdentifier(Utils.toURI(file)), Utils.createPosition(doc, caretPos), doc.getText(0, doc.getLength()), null)); + CompletableFuture futureProposals = bindings.getTextDocumentService().inlineCompletion(new EnhancedTextDocumentService.InlineCompletionParams(new TextDocumentIdentifier(Utils.toURI(file)), Utils.createPosition(doc, caretPos), null)); EnhancedTextDocumentService.InlineCompletionItem[] proposals = futureProposals.get(); if (proposals != null && proposals.length > 0 && thisVersion == DocumentUtilities.getDocumentVersion(doc)) { //TODO: more proper re-indent possible? @@ -208,7 +208,6 @@ public static void clearProposal(Document doc) { if (i != null) { i.clearShadowText(doc); doc.removeDocumentListener(i); - // i.agent.server.autocomplete_clearLastCandidate(null); } } From 9bcae2deaed44d060a628d93c3a025a578e616a4 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Sun, 30 Mar 2025 11:20:03 +0200 Subject: [PATCH 3/6] Improving inline completion. --- .../lsp/client/bindings/InlineCompletion.java | 64 ++++++++++++------- 1 file changed, 42 insertions(+), 22 deletions(-) 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 index 7d1d823ec254..9ececd18ea15 100644 --- 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 @@ -24,7 +24,6 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import javax.swing.event.CaretEvent; @@ -41,12 +40,12 @@ import org.netbeans.api.editor.document.LineDocumentUtils; import org.netbeans.api.editor.mimelookup.MimeRegistration; import org.netbeans.api.editor.settings.AttributesUtilities; -import org.netbeans.api.progress.ProgressHandle; 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; +import org.netbeans.modules.lsp.client.EnhancedTextDocumentService.InlineCompletionItem; +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.spi.editor.AbstractEditorAction; @@ -85,6 +84,7 @@ public EditorListener() { private AtomicReference currentFile = new AtomicReference<>(); private AtomicReference currentDocument = new AtomicReference<>(); private AtomicInteger currentCaretPos = new AtomicInteger(); + private AtomicReference> currentQuery = new AtomicReference<>(); private final Task query = WORKER.create(this::doQuery); @Override @@ -140,33 +140,53 @@ private void doQuery() { ProposalItem.clearProposal(doc); } + CompletableFuture currentRunningQuery = currentQuery.get(); + + if (currentRunningQuery != null) { + if (!currentRunningQuery.isDone()) { + currentRunningQuery.cancel(true); + } + + currentQuery.compareAndSet(currentRunningQuery, null); + } + LSPBindings bindings = LSPBindings.getBindings(file); if (bindings != null) { //TODO: check if the server has inline completion boolean hasInlineCompletion = true; if (hasInlineCompletion) { - ProgressHandle handle = ProgressHandle.createHandle("Running inline completion."); //TODO: progress should be handle by the server/protocol, forcing a progress here seems very intrusive - boolean proposalFound = false; + ProposalItem.clearProposal(doc); //TODO: is this appropriate time? + try { - handle.start(); - //TODO: cancel - CompletableFuture futureProposals = bindings.getTextDocumentService().inlineCompletion(new EnhancedTextDocumentService.InlineCompletionParams(new TextDocumentIdentifier(Utils.toURI(file)), Utils.createPosition(doc, caretPos), null)); - EnhancedTextDocumentService.InlineCompletionItem[] proposals = futureProposals.get(); - if (proposals != null && proposals.length > 0 && thisVersion == DocumentUtilities.getDocumentVersion(doc)) { - //TODO: more proper re-indent possible? - int indent = IndentUtils.lineIndent(doc, IndentUtils.lineStartOffset(doc, caretPos)); - String proposalText = proposals[0].getInsertText().replaceAll("\n", "\n" + IndentUtils.createIndentString(doc, indent)); - ProposalItem.putProposal(bindings, doc, caretPos, proposalText); - proposalFound = true; - } - } catch (BadLocationException | ExecutionException | InterruptedException ex) { + InlineCompletionParams inlineCompletionParams = new InlineCompletionParams(new TextDocumentIdentifier(Utils.toURI(file)), Utils.createPosition(doc, caretPos), null); + CompletableFuture inlineCompletion = + bindings.getTextDocumentService() + .inlineCompletion(inlineCompletionParams); + + currentQuery.set(inlineCompletion); + + inlineCompletion.handle((proposals, exc) -> { + currentQuery.compareAndSet(inlineCompletion, null); + + if (exc != null) { + exc.printStackTrace(); + return null; + } + 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(bindings, doc, caretPos, proposalText); + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + } + return null; + }); + } catch (BadLocationException ex) { Exceptions.printStackTrace(ex); - } finally { - if (!proposalFound) { - ProposalItem.clearProposal(doc); - } - handle.finish(); } } } From 24eaa6a5a524823215f1bc88d5210ceb5b0f6761 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Sun, 3 Aug 2025 22:34:51 +0200 Subject: [PATCH 4/6] Cleanup. --- .../netbeans/modules/editor/resources/NetBeans-keybindings.xml | 2 +- .../netbeans/modules/lsp/client/bindings/InlineCompletion.java | 2 +- ide/lsp.client/src/org/netbeans/modules/lsp/client/layer.xml | 3 --- .../modules/lsp/client/resources/NetBeans-keybindings.xml | 1 + 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ide/editor/src/org/netbeans/modules/editor/resources/NetBeans-keybindings.xml b/ide/editor/src/org/netbeans/modules/editor/resources/NetBeans-keybindings.xml index 3923617babbc..fcb9bc4caad6 100644 --- a/ide/editor/src/org/netbeans/modules/editor/resources/NetBeans-keybindings.xml +++ b/ide/editor/src/org/netbeans/modules/editor/resources/NetBeans-keybindings.xml @@ -92,7 +92,7 @@ - + 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 index 9ececd18ea15..973ea9337ba3 100644 --- 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 @@ -314,7 +314,7 @@ static OffsetsBag getShadowTextBag(Document doc) { return bag; } - @MimeRegistration(mimeType="text/x-java", service=HighlightsLayerFactory.class) + @MimeRegistration(mimeType="", service=HighlightsLayerFactory.class) public static class HighlightsLayerFactoryImpl implements HighlightsLayerFactory { public HighlightsLayer[] createLayers(HighlightsLayerFactory.Context context) { 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 52aeb5d3f026..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 @@ -26,9 +26,6 @@ - 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 index e397d920d585..7cbda61b8017 100644 --- 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 @@ -27,5 +27,6 @@ --> + From c9e1f4c8a703d75fafb2124a19852a28d635e242 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Sat, 13 Sep 2025 09:48:54 +0200 Subject: [PATCH 5/6] Merging current master into inline completion --- .../netbeans/modules/lsp/client/Utils.java | 42 +++++++++ .../lsp/client/bindings/InlineCompletion.java | 92 +++++++++---------- .../bindings/refactoring/Refactoring.java | 26 +----- 3 files changed, 87 insertions(+), 73 deletions(-) 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..7076aedf2b86 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) { + handleException(ex); + } + + @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 index 973ea9337ba3..619e80772a88 100644 --- 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 @@ -22,8 +22,9 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.HashSet; +import java.util.List; import java.util.Set; -import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import javax.swing.event.CaretEvent; @@ -44,10 +45,10 @@ 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.InlineCompletionItem; 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; @@ -62,7 +63,8 @@ //TODO: shutdown public class InlineCompletion { - private static final RequestProcessor WORKER = new RequestProcessor(InlineCompletion.class.getName(), 1, false, false); + 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 { @@ -84,8 +86,8 @@ public EditorListener() { private AtomicReference currentFile = new AtomicReference<>(); private AtomicReference currentDocument = new AtomicReference<>(); private AtomicInteger currentCaretPos = new AtomicInteger(); - private AtomicReference> currentQuery = new AtomicReference<>(); - private final Task query = WORKER.create(this::doQuery); + private AtomicReference currentQuery = new AtomicReference<>(); + private final Task query = PREPARE_WORKER.create(this::doQuery); @Override public void propertyChange(PropertyChangeEvent evt) { @@ -140,56 +142,48 @@ private void doQuery() { ProposalItem.clearProposal(doc); } - CompletableFuture currentRunningQuery = currentQuery.get(); + CancelableBaseEnvironment currentRunningQuery = currentQuery.get(); if (currentRunningQuery != null) { - if (!currentRunningQuery.isDone()) { - currentRunningQuery.cancel(true); - } - + currentRunningQuery.cancelRequest(); currentQuery.compareAndSet(currentRunningQuery, null); } - LSPBindings bindings = LSPBindings.getBindings(file); - - if (bindings != null) { - //TODO: check if the server has inline completion - boolean hasInlineCompletion = true; - if (hasInlineCompletion) { - ProposalItem.clearProposal(doc); //TODO: is this appropriate time? - - try { - InlineCompletionParams inlineCompletionParams = new InlineCompletionParams(new TextDocumentIdentifier(Utils.toURI(file)), Utils.createPosition(doc, caretPos), null); - CompletableFuture inlineCompletion = - bindings.getTextDocumentService() - .inlineCompletion(inlineCompletionParams); - - currentQuery.set(inlineCompletion); - - inlineCompletion.handle((proposals, exc) -> { - currentQuery.compareAndSet(inlineCompletion, null); - - if (exc != null) { - exc.printStackTrace(); - return null; - } - 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(bindings, doc, caretPos, proposalText); - } catch (BadLocationException ex) { - Exceptions.printStackTrace(ex); - } - } - return null; - }); - } catch (BadLocationException ex) { - Exceptions.printStackTrace(ex); - } + 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 (!wasProposal.getAndSet(true) && 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 + ); + }); } } } 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())); From 1a0a449d9e929866cae5c88cd2862719df9cac11 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Sun, 28 Sep 2025 13:28:09 +0200 Subject: [PATCH 6/6] Fixes for inline completion in LSP client. --- ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java | 2 +- .../netbeans/modules/lsp/client/bindings/InlineCompletion.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 7076aedf2b86..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 @@ -468,7 +468,7 @@ public void registerCancelCallback(Runnable callback) { @Override public void handleCancellationException(CancellationException ex) { - handleException(ex); + //ignore cancels } @Override 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 index 619e80772a88..52256325aa8d 100644 --- 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 @@ -170,7 +170,7 @@ private void doQuery() { //only first: return ; } - if (!wasProposal.getAndSet(true) && proposals != null && proposals.length > 0 && thisVersion == DocumentUtilities.getDocumentVersion(doc)) { + 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));