diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/AbstractCompletionItem.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/AbstractCompletionItem.java new file mode 100644 index 000000000000..09bacb648747 --- /dev/null +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/AbstractCompletionItem.java @@ -0,0 +1,220 @@ +/* + * 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.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.event.KeyEvent; +import java.net.URL; +import java.util.List; +import java.util.function.Supplier; +import javax.swing.Action; +import javax.swing.ImageIcon; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import javax.swing.text.StyledDocument; +import org.netbeans.api.editor.completion.Completion; +import org.netbeans.editor.BaseDocument; +import org.netbeans.editor.Utilities; +import org.netbeans.spi.editor.completion.CompletionDocumentation; +import org.netbeans.spi.editor.completion.CompletionItem; +import org.netbeans.spi.editor.completion.CompletionResultSet; +import org.netbeans.spi.editor.completion.CompletionTask; +import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery; +import org.netbeans.spi.editor.completion.support.AsyncCompletionTask; +import org.netbeans.spi.editor.completion.support.CompletionUtilities; +import org.openide.text.NbDocument; +import org.openide.util.Exceptions; + +abstract class AbstractCompletionItem implements CompletionItem { + private final Document doc; + private final int caretOffset; + private final String leftLabel; + private final String rightLabel; + private final ImageIcon icon; + private final String sortText; + private final String insert; + + AbstractCompletionItem( + Document doc, int caretOffset, String leftLabel, String rightLabel, + ImageIcon icon, String sortText, String insert + ) { + this.doc = doc; + this.caretOffset = caretOffset; + this.leftLabel = leftLabel; + this.rightLabel = rightLabel; + this.icon = icon; + this.sortText = sortText; + this.insert = insert; + } + + abstract Edit findEdit(boolean[] hideImmediately); + abstract int findStart(Document doc, Edit te); + abstract int findEnd(Document doc, Edit te); + abstract String findNewText(Edit te); + + abstract boolean isTextEdit(Edit te); + + abstract List getCommitCharacters(); + abstract boolean isTriggerCharacter(String ch); + abstract Supplier resolveDocumentation(Document doc, int caretOffset); + + @Override + public void defaultAction(JTextComponent jtc) { + commit(""); + } + + private void commit(String appendText) { + boolean[] hideNow = { false }; + Edit te = findEdit(hideNow); + if (hideNow[0]) { + Completion.get().hideDocumentation(); + Completion.get().hideCompletion(); + return; + } + NbDocument.runAtomic((StyledDocument) doc, () -> { + try { + int endPos; + if (isTextEdit(te)) { + int start = findStart(doc, te); + int end = findEnd(doc, te); + doc.remove(start, end - start); + String newText = findNewText(te); + doc.insertString(start, newText, null); + endPos = start + newText.length(); + } else { + String toAdd = insert; + int[] identSpan = Utilities.getIdentifierBlock((BaseDocument) doc, caretOffset); + if (identSpan != null) { + doc.remove(identSpan[0], identSpan[1] - identSpan[0]); + doc.insertString(identSpan[0], toAdd, null); + endPos = identSpan[0] + toAdd.length(); + } else { + doc.insertString(caretOffset, toAdd, null); + endPos = caretOffset + toAdd.length(); + } + } + doc.insertString(endPos, appendText, null); + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + }); + Completion.get().hideDocumentation(); + Completion.get().hideCompletion(); + } + + @Override + public void processKeyEvent(KeyEvent ke) { + if (ke.getID() == KeyEvent.KEY_TYPED) { + String commitText = String.valueOf(ke.getKeyChar()); + List commitCharacters = getCommitCharacters(); + if (commitCharacters != null && commitCharacters.contains(commitText)) { + commit(commitText); + ke.consume(); + if (isTriggerCharacter(commitText)) { + Completion.get().showCompletion(); + } + } + } + } + + @Override + public int getPreferredWidth(Graphics grphcs, Font font) { + return CompletionUtilities.getPreferredWidth(leftLabel, rightLabel, grphcs, font); + } + + @Override + public void render(Graphics grphcs, Font font, Color color, Color color1, int i, int i1, boolean bln) { + CompletionUtilities.renderHtml(icon, leftLabel, rightLabel, grphcs, font, color, i, i1, bln); + } + + @Override + public CompletionTask createDocumentationTask() { + return new AsyncCompletionTask(new AsyncCompletionQuery() { + @Override + protected void query(CompletionResultSet resultSet, Document doc, int caretOffset) { + Supplier resolved = resolveDocumentation(doc, caretOffset); + if (resolved != null) { + resultSet.setDocumentation(new CompletionDocumentation() { + @Override + public String getText() { + String[] both = resolved.get(); + String detail = both[0]; + String content = both[1]; + + StringBuilder documentation = new StringBuilder(); + documentation.append("\n"); + if (detail != null) { + documentation.append("").append(CompletionProviderImpl.escape(detail)).append(""); + documentation.append("\n

"); + } + if (content != null) { + documentation.append(content); + } + return documentation.toString(); + } + + @Override + public URL getURL() { + return null; + } + + @Override + public CompletionDocumentation resolveLink(String link) { + return null; + } + + @Override + public Action getGotoSourceAction() { + return null; + } + }); + } + resultSet.finish(); + } + }); + } + + @Override + public CompletionTask createToolTipTask() { + return null; + } + + @Override + public boolean instantSubstitution(JTextComponent jtc) { + return false; + } + + @Override + public int getSortPriority() { + return 100; + } + + @Override + public CharSequence getSortText() { + return sortText; + } + + @Override + public CharSequence getInsertPrefix() { + return insert; + } +} diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CompletionProviderImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CompletionProviderImpl.java index d4eb07445fc7..8a6066485cb6 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CompletionProviderImpl.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CompletionProviderImpl.java @@ -18,56 +18,38 @@ */ package org.netbeans.modules.lsp.client.bindings; -import com.vladsch.flexmark.html.HtmlRenderer; -import com.vladsch.flexmark.parser.Parser; -import java.awt.Color; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.event.KeyEvent; -import java.net.URL; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; -import javax.swing.Action; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JToolTip; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.JTextComponent; -import javax.swing.text.StyledDocument; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionItemKind; import org.eclipse.lsp4j.CompletionList; import org.eclipse.lsp4j.CompletionOptions; import org.eclipse.lsp4j.CompletionParams; import org.eclipse.lsp4j.InitializeResult; -import org.eclipse.lsp4j.InsertReplaceEdit; -import org.eclipse.lsp4j.MarkupContent; import org.eclipse.lsp4j.ParameterInformation; import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.SignatureHelp; import org.eclipse.lsp4j.SignatureHelpParams; import org.eclipse.lsp4j.SignatureInformation; import org.eclipse.lsp4j.TextDocumentIdentifier; -import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.jsonrpc.messages.Either; -import org.netbeans.api.editor.completion.Completion; import org.netbeans.api.editor.mimelookup.MimeRegistration; -import org.netbeans.editor.BaseDocument; -import org.netbeans.editor.Utilities; import org.netbeans.modules.editor.NbEditorUtilities; import org.netbeans.modules.lsp.client.LSPBindings; import org.netbeans.modules.lsp.client.Utils; -import org.netbeans.spi.editor.completion.CompletionDocumentation; import org.netbeans.spi.editor.completion.CompletionProvider; import org.netbeans.spi.editor.completion.CompletionResultSet; import org.netbeans.spi.editor.completion.CompletionTask; import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery; import org.netbeans.spi.editor.completion.support.AsyncCompletionTask; -import org.netbeans.spi.editor.completion.support.CompletionUtilities; import org.openide.filesystems.FileObject; -import org.openide.text.NbDocument; import org.openide.util.Exceptions; import org.openide.util.ImageUtilities; import org.openide.xml.XMLUtil; @@ -184,172 +166,9 @@ protected void query(CompletionResultSet resultSet, Document doc, int caretOffse CompletionItemKind kind = i.getKind(); Icon ic = Icons.getCompletionIcon(kind); ImageIcon icon = new ImageIcon(ImageUtilities.icon2Image(ic)); - resultSet.addItem(new org.netbeans.spi.editor.completion.CompletionItem() { - @Override - public void defaultAction(JTextComponent jtc) { - commit(""); - } - private void commit(String appendText) { - Either edit = i.getTextEdit(); - if (edit != null && edit.isRight()) { - //TODO: the NetBeans client does not current support InsertReplaceEdits, should not happen - Completion.get().hideDocumentation(); - Completion.get().hideCompletion(); - return ; - } - TextEdit te = edit != null ? edit.getLeft() : null; - NbDocument.runAtomic((StyledDocument) doc, () -> { - try { - int endPos; - if (te != null) { - int start = Utils.getOffset(doc, te.getRange().getStart()); - int end = Utils.getOffset(doc, te.getRange().getEnd()); - doc.remove(start, end - start); - doc.insertString(start, te.getNewText(), null); - endPos = start + te.getNewText().length(); - } else { - String toAdd = i.getInsertText(); - if (toAdd == null) { - toAdd = i.getLabel(); - } - int[] identSpan = Utilities.getIdentifierBlock((BaseDocument) doc, caretOffset); - if (identSpan != null) { - doc.remove(identSpan[0], identSpan[1] - identSpan[0]); - doc.insertString(identSpan[0], toAdd, null); - endPos = identSpan[0] + toAdd.length(); - } else { - doc.insertString(caretOffset, toAdd, null); - endPos = caretOffset + toAdd.length(); - } - } - doc.insertString(endPos, appendText, null); - } catch (BadLocationException ex) { - Exceptions.printStackTrace(ex); - } - }); - Completion.get().hideDocumentation(); - Completion.get().hideCompletion(); - } - - @Override - public void processKeyEvent(KeyEvent ke) { - if (ke.getID() == KeyEvent.KEY_TYPED) { - String commitText = String.valueOf(ke.getKeyChar()); - List commitCharacters = i.getCommitCharacters(); - - if (commitCharacters != null && commitCharacters.contains(commitText)) { - commit(commitText); - ke.consume(); - if (isTriggerCharacter(server, commitText)) { - Completion.get().showCompletion(); - } - } - } - } - - @Override - public int getPreferredWidth(Graphics grphcs, Font font) { - return CompletionUtilities.getPreferredWidth(leftLabel, rightLabel, grphcs, font); - } - - @Override - public void render(Graphics grphcs, Font font, Color color, Color color1, int i, int i1, boolean bln) { - CompletionUtilities.renderHtml(icon, leftLabel, rightLabel, grphcs, font, color, i, i1, bln); - } - - @Override - public CompletionTask createDocumentationTask() { - return new AsyncCompletionTask(new AsyncCompletionQuery() { - @Override - protected void query(CompletionResultSet resultSet, Document doc, int caretOffset) { - CompletionItem resolved; - if ((i.getDetail() == null || i.getDocumentation() == null) && hasCompletionResolve(server)) { - CompletionItem temp; - try { - temp = server.getTextDocumentService().resolveCompletionItem(i).get(); - } catch (InterruptedException | ExecutionException ex) { - Exceptions.printStackTrace(ex); - temp = i; - } - resolved = temp; - } else { - resolved = i; - } - if (resolved.getDocumentation() != null || resolved.getDetail() != null) { - resultSet.setDocumentation(new CompletionDocumentation() { - @Override - public String getText() { - StringBuilder documentation = new StringBuilder(); - documentation.append("\n"); - if (resolved.getDetail() != null) { - documentation.append("").append(escape(resolved.getDetail())).append(""); - documentation.append("\n

"); - } - if (resolved.getDocumentation() != null) { - MarkupContent content; - if (resolved.getDocumentation().isLeft()) { - content = new MarkupContent(); - content.setKind("plaintext"); - content.setValue(resolved.getDocumentation().getLeft()); - } else { - content = resolved.getDocumentation().getRight(); - } - switch (content.getKind()) { - default: - case "plaintext": documentation.append("

\n").append(content.getValue()).append("\n
"); break; - case "markdown": documentation.append(HtmlRenderer.builder().build().render(Parser.builder().build().parse(content.getValue()))); break; - } - } - return documentation.toString(); - } - @Override - public URL getURL() { - return null; - } - @Override - public CompletionDocumentation resolveLink(String link) { - return null; - } - @Override - public Action getGotoSourceAction() { - return null; - } - }); - } - resultSet.finish(); - } - }); - } - - @Override - public CompletionTask createToolTipTask() { - return null; - } - - @Override - public boolean instantSubstitution(JTextComponent jtc) { - return false; - } - - @Override - public int getSortPriority() { - return 100; - } - - @Override - public CharSequence getSortText() { - return sortText; - } - - @Override - public CharSequence getInsertPrefix() { - return insert; - } - }); + resultSet.addItem(new LspCompletionItem(i, doc, caretOffset, server, leftLabel, rightLabel, icon, sortText, insert, this, org.netbeans.modules.lsp.client.bindings.CompletionProviderImpl.this)); } - } catch (BadLocationException | InterruptedException ex) { - Exceptions.printStackTrace(ex); - } catch (ExecutionException ex) { + } catch (BadLocationException | InterruptedException | ExecutionException ex) { Exceptions.printStackTrace(ex); } finally { resultSet.finish(); @@ -357,17 +176,19 @@ public CharSequence getInsertPrefix() { } }, component); } - - private boolean hasCompletionResolve(LSPBindings server) { + + final boolean hasCompletionResolve(LSPBindings server) { ServerCapabilities capabilities = server.getInitResult().getCapabilities(); - if (capabilities == null) return false; + if (capabilities == null) { + return false; + } CompletionOptions completionProvider = capabilities.getCompletionProvider(); if (completionProvider == null) return false; Boolean resolveProvider = completionProvider.getResolveProvider(); return resolveProvider != null && resolveProvider; } - private static String escape(String s) { + static String escape(String s) { if (s != null) { try { return XMLUtil.toAttributeValue(s); @@ -393,8 +214,8 @@ public int getAutoQueryTypes(JTextComponent component, String typedText) { } return isTriggerCharacter(server, typedText) ? COMPLETION_QUERY_TYPE : 0; } - - private boolean isTriggerCharacter(LSPBindings server, String text) { + + boolean isTriggerCharacter(LSPBindings server, String text) { InitializeResult init = server.getInitResult(); if (init == null) return false; ServerCapabilities capabilities = init.getCapabilities(); diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java index 68e68c5176a9..ef7e909403c8 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java @@ -41,7 +41,7 @@ public final class Icons { private Icons() { } - public static Icon getCompletionIcon(CompletionItemKind completionKind) { + public static Icon getCompletionIcon(Enum completionKind) { Image img = null; if (completionKind != null) { diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LspCompletionItem.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LspCompletionItem.java new file mode 100644 index 000000000000..606d413faab5 --- /dev/null +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LspCompletionItem.java @@ -0,0 +1,142 @@ +/* + * 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 com.vladsch.flexmark.html.HtmlRenderer; +import com.vladsch.flexmark.parser.Parser; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; +import javax.swing.ImageIcon; +import javax.swing.text.Document; +import org.eclipse.lsp4j.InsertReplaceEdit; +import org.eclipse.lsp4j.MarkupContent; +import org.eclipse.lsp4j.TextEdit; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.netbeans.modules.lsp.client.LSPBindings; +import org.netbeans.modules.lsp.client.Utils; +import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery; +import org.openide.util.Exceptions; + +final class LspCompletionItem extends AbstractCompletionItem> { + private final org.eclipse.lsp4j.CompletionItem i; + private final LSPBindings server; + private final AsyncCompletionQuery outer; + private final CompletionProviderImpl cp; + + public LspCompletionItem( + org.eclipse.lsp4j.CompletionItem i, + Document doc, int caretOffset, LSPBindings server, + String leftLabel, String rightLabel, ImageIcon icon, String sortText, + String insert, + AsyncCompletionQuery outer, CompletionProviderImpl cp + ) { + super(doc, caretOffset, leftLabel, rightLabel, icon, sortText, insert); + this.cp = cp; + this.outer = outer; + this.i = i; + this.server = server; + } + + @Override + Either findEdit(boolean[] hideImmediately) { + Either edit = i.getTextEdit(); + if (edit != null && edit.isRight()) { + //TODO: the NetBeans client does not currently support InsertReplaceEdits, should not happen + hideImmediately[0] = true; + return null; + } + return edit; + } + + @Override + boolean isTextEdit(Either edit) { + return edit != null && edit.getLeft() != null; + } + + @Override + int findStart(Document doc, Either te) { + return Utils.getOffset(doc, te.getLeft().getRange().getStart()); + } + + @Override + int findEnd(Document doc, Either te) { + return Utils.getOffset(doc, te.getLeft().getRange().getEnd()); + } + + @Override + String findNewText(Either te) { + return te.getLeft().getNewText(); + } + + @Override + List getCommitCharacters() { + return i.getCommitCharacters(); + } + + @Override + boolean isTriggerCharacter(String commitText) { + return cp.isTriggerCharacter(server, commitText); + } + + @Override + Supplier resolveDocumentation(Document doc, int caretOffset) { + org.eclipse.lsp4j.CompletionItem resolved; + if ((i.getDetail() == null || i.getDocumentation() == null) && cp.hasCompletionResolve(server)) { + org.eclipse.lsp4j.CompletionItem temp; + try { + temp = server.getTextDocumentService().resolveCompletionItem(i).get(); + } catch (InterruptedException | ExecutionException ex) { + Exceptions.printStackTrace(ex); + temp = i; + } + resolved = temp; + } else { + resolved = i; + } + if (resolved.getDocumentation() != null || resolved.getDetail() != null) { + return () -> { + String txt; + if (resolved.getDocumentation() != null) { + MarkupContent content; + if (resolved.getDocumentation().isLeft()) { + content = new MarkupContent(); + content.setKind("plaintext"); + content.setValue(resolved.getDocumentation().getLeft()); + } else { + content = resolved.getDocumentation().getRight(); + } + switch (content.getKind()) { + case "markdown": + txt = HtmlRenderer.builder().build().render(Parser.builder().build().parse(content.getValue())); + break; + default: + txt = "
\n" + content.getValue() + "\n
"; + break; + } + } else { + txt = null; + } + return new String[] { resolved.getDetail(), txt }; + }; + } else { + return null; + } + } +} diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LspCompletionProviderImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LspCompletionProviderImpl.java new file mode 100644 index 000000000000..36444e3d5f19 --- /dev/null +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LspCompletionProviderImpl.java @@ -0,0 +1,178 @@ +/** + * 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.util.List; +import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import org.netbeans.api.editor.mimelookup.MimeLookup; +import org.netbeans.api.editor.mimelookup.MimeRegistration; +import org.netbeans.api.lsp.TextEdit; +import org.netbeans.modules.editor.NbEditorUtilities; +import org.netbeans.spi.editor.completion.CompletionProvider; +import org.netbeans.spi.editor.completion.CompletionResultSet; +import org.netbeans.spi.editor.completion.CompletionTask; +import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery; +import org.netbeans.spi.editor.completion.support.AsyncCompletionTask; +import org.netbeans.spi.lsp.CompletionCollector; +import org.openide.filesystems.FileObject; +import org.openide.util.ImageUtilities; +import org.openide.util.Lookup; + +@MimeRegistration(mimeType = "", service = CompletionProvider.class) +public class LspCompletionProviderImpl implements CompletionProvider { + + @Override + public CompletionTask createTask(int queryType, JTextComponent component) { + return new AsyncCompletionTask(new AsyncCompletionQuery() { + @Override + protected void query(CompletionResultSet resultSet, Document doc, int caretOffset) { + FileObject file = NbEditorUtilities.getFileObject(doc); + if (file == null) { + //TODO: beep + resultSet.finish(); + return; + } + final String mime = file.getMIMEType(); + for (Lookup.Item item : MimeLookup.getLookup(mime).lookupResult(CompletionProvider.class).allItems()) { + String id = item.getId(); + if (id.startsWith("Editors/"+ mime)) { + // found real CompletionProvider - don't bridge LSP API + resultSet.finish(); + return; + } + } + Consumer consumer = (i) -> { + String insert = i.getInsertText() != null ? i.getInsertText() : i.getLabel(); + String leftLabel = encode(i.getLabel()); + String rightLabel = null; + try { + if (i.getDetail() != null) { + rightLabel = encode(i.getDetail().get()); + } + } catch (InterruptedException | ExecutionException interruptedException) { + // leave null + } + String sortText = i.getSortText() != null ? i.getSortText() : i.getLabel(); + org.netbeans.api.lsp.Completion.Kind kind = i.getKind(); + Icon ic = Icons.getCompletionIcon(kind); + ImageIcon icon = new ImageIcon(ImageUtilities.icon2Image(ic)); + resultSet.addItem(new LspApiCompletionItem(i, doc, caretOffset, leftLabel, rightLabel, icon, sortText, insert)); + }; + org.netbeans.api.lsp.Completion.Context context = new org.netbeans.api.lsp.Completion.Context(org.netbeans.api.lsp.Completion.TriggerKind.Invoked, null); + for (CompletionCollector cc : MimeLookup.getLookup(mime).lookupAll(CompletionCollector.class)) { + cc.collectCompletions(doc, caretOffset, context, consumer); + } + resultSet.finish(); + } + }, component); + } + + private String encode(String str) { + return str.replace("&", "&") + .replace("<", "<"); + } + + @Override + public int getAutoQueryTypes(JTextComponent component, String typedText) { + FileObject file = NbEditorUtilities.getFileObject(component.getDocument()); + if (file == null) { + return 0; + } + return 0; + } + + private static class LspApiCompletionItem extends AbstractCompletionItem { + + private final org.netbeans.api.lsp.Completion i; + + public LspApiCompletionItem(org.netbeans.api.lsp.Completion i, Document doc, int caretOffset, String leftLabel, String rightLabel, ImageIcon icon, String sortText, String insert) { + super(doc, caretOffset, leftLabel, rightLabel, icon, sortText, insert); + this.i = i; + } + + @Override + TextEdit findEdit(boolean[] hideNow) { + return i.getTextEdit(); + } + + @Override + boolean isTextEdit(TextEdit te) { + return te != null; + } + + @Override + int findStart(Document doc, TextEdit te) { + return te.getStartOffset(); + } + + @Override + int findEnd(Document doc, TextEdit te) { + return te.getEndOffset(); + } + + @Override + String findNewText(TextEdit te) { + return te.getNewText(); + } + + @Override + List getCommitCharacters() { + return i.getCommitCharacters().stream().map(String::valueOf).collect(Collectors.toList()); + } + + @Override + boolean isTriggerCharacter(String commitText) { + return false; + } + + @Override + Supplier resolveDocumentation(Document doc, int caretOffset) { + if (i.getDetail() == null && i.getDocumentation() == null) { + return null; + } else { + return () -> { + String detail = null; + String documentation = null; + try { + if (i.getDetail() != null) { + detail = i.getDetail().get(); + } + } catch (InterruptedException | ExecutionException interruptedException) { + // leave null + } + try { + if (i.getDocumentation() != null) { + documentation = i.getDocumentation().get(); + } + } catch (InterruptedException | ExecutionException interruptedException) { + // leave null + } + return new String[]{detail, documentation}; + }; + } + } + } +} diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/NavigatorPanelImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/NavigatorPanelImpl.java index bde41feae455..c03162f7fbd9 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/NavigatorPanelImpl.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/NavigatorPanelImpl.java @@ -18,19 +18,13 @@ */ package org.netbeans.modules.lsp.client.bindings; -import java.awt.BorderLayout; import java.awt.event.ActionEvent; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.logging.Level; -import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; -import javax.swing.JComponent; -import javax.swing.JPanel; -import javax.swing.SwingUtilities; import org.eclipse.lsp4j.DocumentSymbol; import org.eclipse.lsp4j.DocumentSymbolParams; import org.eclipse.lsp4j.Range; @@ -40,17 +34,10 @@ import org.netbeans.modules.lsp.client.LSPBindings; import org.netbeans.modules.lsp.client.LSPBindings.BackgroundTask; import org.netbeans.modules.lsp.client.Utils; -import org.netbeans.spi.navigator.NavigatorPanel; -import org.openide.explorer.ExplorerManager; -import org.openide.explorer.view.BeanTreeView; import org.openide.filesystems.FileObject; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Node; -import org.openide.util.Lookup; -import org.openide.util.LookupEvent; -import org.openide.util.LookupListener; -import org.openide.util.NbBundle.Messages; /** *