From 5a6dc9f6ac306e91757c76f616d1df34cc700c6a Mon Sep 17 00:00:00 2001 From: azerr Date: Fri, 18 Aug 2023 11:55:09 +0200 Subject: [PATCH] feat: Show LSP completion item documentation in popup Fixes #1046 Signed-off-by: azerr --- .../lsp4ij/LSPVirtualFileWrapper.java | 5 +- .../lsp4ij/internal/SupportedFeatures.java | 2 +- .../completion/LSPCompletionContributor.java | 12 +- .../completion/LSPCompletionProposal.java | 47 +++- .../LSPDocumentationProvider.java | 204 ++++++++++++++++++ .../LSPPsiElementForLookupItem.java | 94 ++++++++ .../LSPTextHoverForFile.java | 91 ++------ .../highlight/LSPHighlightUsagesHandler.java | 1 - .../lsp4ij/operations/hover/LSPTextHover.java | 124 ----------- .../resources/META-INF/lsp4ij-quarkus.xml | 4 +- src/main/resources/META-INF/lsp4ij-qute.xml | 10 +- 11 files changed, 380 insertions(+), 214 deletions(-) create mode 100644 src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPDocumentationProvider.java create mode 100644 src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPPsiElementForLookupItem.java rename src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/{hover => documentation}/LSPTextHoverForFile.java (63%) delete mode 100644 src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/hover/LSPTextHover.java diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPVirtualFileWrapper.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPVirtualFileWrapper.java index 64ca86cb0..2b8321421 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPVirtualFileWrapper.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPVirtualFileWrapper.java @@ -23,9 +23,10 @@ import com.intellij.psi.PsiElement; import com.redhat.devtools.intellij.lsp4ij.operations.diagnostics.LSPDiagnosticsForServer; import com.redhat.devtools.intellij.lsp4ij.operations.documentLink.LSPDocumentLinkForServer; -import com.redhat.devtools.intellij.lsp4ij.operations.hover.LSPTextHoverForFile; +import com.redhat.devtools.intellij.lsp4ij.operations.documentation.LSPTextHoverForFile; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DocumentLink; +import org.eclipse.lsp4j.MarkupContent; import java.util.List; import java.util.Collection; @@ -109,7 +110,7 @@ public Collection getAllDocumentLink() { } - public String getHoverContent(PsiElement element, int targetOffset, Editor editor) { + public List getHoverContent(PsiElement element, int targetOffset, Editor editor) { return hover.getHoverContent(element, targetOffset, editor); } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/internal/SupportedFeatures.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/internal/SupportedFeatures.java index 82a916ea2..7f63c59be 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/internal/SupportedFeatures.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/internal/SupportedFeatures.java @@ -53,7 +53,7 @@ public class SupportedFeatures { .setDocumentationFormat(Arrays.asList(MarkupKind.MARKDOWN, MarkupKind.PLAINTEXT)); completionItemCapabilities.setInsertTextModeSupport(new CompletionItemInsertTextModeSupportCapabilities(List.of(InsertTextMode.AsIs, InsertTextMode.AdjustIndentation))); - // completionItemCapabilities.setResolveSupport(new CompletionItemResolveSupportCapabilities(List.of("documentation", "detail", "additionalTextEdits"))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + completionItemCapabilities.setResolveSupport(new CompletionItemResolveSupportCapabilities(List.of("documentation" /*, "detail", "additionalTextEdits" */))); CompletionCapabilities completionCapabilities = new CompletionCapabilities(completionItemCapabilities); completionCapabilities.setCompletionList(new CompletionListCapabilities(List.of("editRange"))); textDocumentClientCapabilities.setCompletion(completionCapabilities); diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionContributor.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionContributor.java index 93011bd23..11eeb7bd8 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionContributor.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionContributor.java @@ -69,20 +69,20 @@ public void fillCompletionVariants(@NotNull CompletionParameters parameters, @No async processing is occuring on a separate thread. */ CompletionParams params = LSPIJUtils.toCompletionParams(LSPIJUtils.toUri(document), offset, document); - BlockingDeque, CompletionList>, LanguageServer>> proposals = new LinkedBlockingDeque<>(); + BlockingDeque, CompletionList>, LanguageServerItem>> proposals = new LinkedBlockingDeque<>(); CompletableFuture future = completionLanguageServersFuture .thenComposeAsync(languageServers -> cancellationSupport.execute( CompletableFuture.allOf(languageServers.stream() .map(languageServer -> cancellationSupport.execute(languageServer.getServer().getTextDocumentService().completion(params)) - .thenAcceptAsync(completion -> proposals.add(new Pair<>(completion, languageServer.getServer())))) + .thenAcceptAsync(completion -> proposals.add(new Pair<>(completion, languageServer)))) .toArray(CompletableFuture[]::new)))); ProgressManager.checkCanceled(); while (!future.isDone() || !proposals.isEmpty()) { ProgressManager.checkCanceled(); - Pair, CompletionList>, LanguageServer> pair = proposals.poll(25, TimeUnit.MILLISECONDS); + Pair, CompletionList>, LanguageServerItem> pair = proposals.poll(25, TimeUnit.MILLISECONDS); if (pair != null) { Either, CompletionList> completion = pair.getFirst(); if (completion != null) { @@ -101,7 +101,7 @@ public void fillCompletionVariants(@NotNull CompletionParameters parameters, @No } private void addCompletionItems(PsiFile file, Editor editor, CompletionPrefix completionPrefix, Either, - CompletionList> completion, LanguageServer languageServer, @NotNull CompletionResultSet result, CancellationSupport cancellationSupport) { + CompletionList> completion, LanguageServerItem languageServer, @NotNull CompletionResultSet result, CancellationSupport cancellationSupport) { CompletionItemDefaults itemDefaults = null; List items = null; if (completion.isLeft()) { @@ -138,7 +138,7 @@ private void addCompletionItems(PsiFile file, Editor editor, CompletionPrefix co private static LSPCompletionProposal createLookupItem(PsiFile file, Editor editor, int offset, CompletionItem item, - CompletionItemDefaults itemDefaults, LanguageServer languageServer) { + CompletionItemDefaults itemDefaults, LanguageServerItem languageServer) { // Update text edit range with item defaults if needed updateWithItemDefaults(item, itemDefaults); return new LSPCompletionProposal(file, editor, offset, item, languageServer); @@ -179,4 +179,4 @@ private static CompletableFuture> initiateLanguageServe return false; }); } -} +} \ No newline at end of file diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionProposal.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionProposal.java index 1115973f5..94e60eb2b 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionProposal.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionProposal.java @@ -21,24 +21,26 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorModificationUtil; -import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiFile; import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils; +import com.redhat.devtools.intellij.lsp4ij.LanguageServerItem; import com.redhat.devtools.intellij.lsp4ij.LanguageServiceAccessor; import com.redhat.devtools.intellij.lsp4ij.command.internal.CommandExecutor; import com.redhat.devtools.intellij.lsp4ij.operations.completion.snippet.LspSnippetIndentOptions; import org.apache.commons.lang.StringUtils; import org.eclipse.lsp4j.*; import org.eclipse.lsp4j.jsonrpc.messages.Either; -import org.eclipse.lsp4j.services.LanguageServer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import static com.redhat.devtools.intellij.lsp4ij.operations.completion.CompletionProposalTools.createLspIndentOptions; import static com.redhat.devtools.intellij.lsp4ij.operations.completion.snippet.LspSnippetVariableConstants.*; @@ -53,12 +55,14 @@ public class LSPCompletionProposal extends LookupElement { private final CompletionItem item; private final int initialOffset; private final PsiFile file; + private final Boolean supportResolveCompletion; private int currentOffset; private int bestOffset; private final Editor editor; - private final LanguageServer languageServer; + private final LanguageServerItem languageServer; + private String documentation; - public LSPCompletionProposal(PsiFile file, Editor editor, int offset, CompletionItem item, LanguageServer languageServer) { + public LSPCompletionProposal(PsiFile file, Editor editor, int offset, CompletionItem item, LanguageServerItem languageServer) { this.file = file; this.item = item; this.editor = editor; @@ -66,6 +70,8 @@ public LSPCompletionProposal(PsiFile file, Editor editor, int offset, Completion this.initialOffset = offset; this.currentOffset = offset; this.bestOffset = getPrefixCompletionStart(editor.getDocument(), offset); + ServerCapabilities serverCapabilities = languageServer.getServerWrapper().getServerCapabilities(); + this.supportResolveCompletion = serverCapabilities != null && serverCapabilities.getCompletionProvider() != null && serverCapabilities.getCompletionProvider().getResolveProvider(); putUserData(CodeCompletionHandlerBase.DIRECT_INSERTION, true); } @@ -274,7 +280,7 @@ private void executeCustomCommand(@NotNull Command command, Document document) { Project project = editor.getProject(); // Execute custom command of the completion item. LanguageServiceAccessor.getInstance(project) - .resolveServerDefinition(languageServer).map(definition -> definition.id) + .resolveServerDefinition(languageServer.getServer()).map(definition -> definition.id) .ifPresent(id -> { CommandExecutor.executeCommand(project, command, document, id); }); @@ -347,4 +353,35 @@ public CompletionItem getItem() { } } + public MarkupContent getDocumentation() { + if (item.getDocumentation() == null && supportResolveCompletion) { + try { + CompletionItem resolved = languageServer.getServer() + .getTextDocumentService() + .resolveCompletionItem(item) + .get(1000, TimeUnit.MILLISECONDS); + if (resolved != null) { + item.setDocumentation(resolved.getDocumentation()); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } catch (TimeoutException e) { + throw new RuntimeException(e); + } + } + return getDocumentation(item.getDocumentation()); + } + + private static MarkupContent getDocumentation(Either documentation) { + if (documentation == null) { + return null; + } + if (documentation.isLeft()) { + String content = documentation.getLeft(); + return new MarkupContent(content, MarkupKind.PLAINTEXT); + } + return documentation.getRight(); + } } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPDocumentationProvider.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPDocumentationProvider.java new file mode 100644 index 000000000..d3d249b4e --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPDocumentationProvider.java @@ -0,0 +1,204 @@ +/******************************************************************************* + * Copyright (c) 2020 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at https://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.intellij.lsp4ij.operations.documentation; + +import com.intellij.lang.documentation.DocumentationProviderEx; +import com.intellij.lang.documentation.ExternalDocumentationHandler; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils; +import com.redhat.devtools.intellij.lsp4ij.LSPVirtualFileWrapper; +import com.redhat.devtools.intellij.lsp4ij.operations.completion.LSPCompletionProposal; +import com.vladsch.flexmark.html.HtmlRenderer; +import com.vladsch.flexmark.parser.Parser; +import org.eclipse.lsp4j.MarkupContent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * {@link DocumentationProviderEx} implementation for LSP to support: + * + *
    + *
  • testDocument/hover
  • + *
  • documentation for completion item
  • + *
. + */ +public class LSPDocumentationProvider extends DocumentationProviderEx implements ExternalDocumentationHandler { + + private static final Parser PARSER = Parser.builder().build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().build(); + + private static final Key TARGET_OFFSET_KEY = new Key<>(LSPDocumentationProvider.class.getName()); + + @Nullable + @Override + public String getQuickNavigateInfo(PsiElement element, PsiElement originalElement) { + return generateDoc(element, originalElement); + } + + @Override + public @Nullable PsiElement getCustomDocumentationElement(@NotNull Editor editor, @NotNull PsiFile file, @Nullable PsiElement contextElement, int targetOffset) { + if (contextElement != null) { + // Store the offset where the hover has been triggered + contextElement.putUserData(TARGET_OFFSET_KEY, targetOffset); + } + return super.getCustomDocumentationElement(editor, file, contextElement, targetOffset); + } + + @Nullable + @Override + public String generateDoc(PsiElement element, @Nullable PsiElement originalElement) { + Project project = element.getProject(); + if (project.isDisposed()) { + return null; + } + Editor editor = LSPIJUtils.editorForElement(element); + if (editor == null) { + return null; + } + List result = getMarkupContents(element, originalElement); + if (result == null || result.isEmpty()) { + return null; + } + String s = result + .stream() + .map(m -> m.getValue()) + .collect(Collectors.joining("\n\n")); + return styleHtml(editor, RENDERER.render(PARSER.parse(s))); + } + + @Nullable + public List getMarkupContents(PsiElement element, @Nullable PsiElement originalElement) { + if (element instanceof LSPPsiElementForLookupItem) { + return ((LSPPsiElementForLookupItem) element).getDocumentation(); + } + if (originalElement == null || !Objects.equals(element.getContainingFile(), originalElement.getContainingFile())) { + return null; + } + Editor editor = LSPIJUtils.editorForElement(element); + if (editor == null) { + return null; + } + + VirtualFile file = originalElement.getContainingFile().getVirtualFile(); + int targetOffset = getTargetOffset(originalElement); + if (LSPVirtualFileWrapper.hasWrapper(file)) { + return LSPVirtualFileWrapper.getLSPVirtualFileWrapper(file).getHoverContent(element, targetOffset, editor); + } + return null; + } + + private static int getTargetOffset(PsiElement originalElement) { + Integer targetOffset = originalElement.getUserData(TARGET_OFFSET_KEY); + if (targetOffset != null) { + return targetOffset; + } + int startOffset = originalElement.getTextOffset(); + int textLength = originalElement.getTextLength(); + return startOffset + textLength / 2; + } + + @Nullable + @Override + public PsiElement getDocumentationElementForLookupItem(PsiManager psiManager, Object object, PsiElement element) { + if (object instanceof LSPCompletionProposal) { + MarkupContent documentation = ((LSPCompletionProposal) object).getDocumentation(); + if (documentation != null) { + return new LSPPsiElementForLookupItem(documentation, psiManager, element); + } + } + return null; + } + + @Nullable + @Override + public PsiElement getDocumentationElementForLink(PsiManager psiManager, String link, PsiElement context) { + return null; + } + + @Override + public boolean handleExternal(PsiElement element, PsiElement originalElement) { + return false; + } + + @Override + public boolean handleExternalLink(PsiManager psiManager, String link, PsiElement context) { + VirtualFile file = LSPIJUtils.findResourceFor(link); + if (file != null) { + FileEditorManager.getInstance(psiManager.getProject()).openFile(file, true, true); + return true; + } + return false; + } + + @Override + public boolean canFetchDocumentationLink(String link) { + return false; + } + + @Override + public @NotNull String fetchExternalDocumentation(@NotNull String link, @Nullable PsiElement element) { + return null; + } + + + public static String styleHtml(Editor editor, String html) { + if (html == null || html.isEmpty()) { + return html; + } + Color background = editor.getColorsScheme().getDefaultBackground(); + Color foreground = editor.getColorsScheme().getDefaultForeground(); + // put CSS styling to match Eclipse style + String style = ""; //$NON-NLS-1$ + + /*int headIndex = html.indexOf(HEAD); + StringBuilder builder = new StringBuilder(html.length() + style.length()); + builder.append(html.substring(0, headIndex + HEAD.length())); + builder.append(style); + builder.append(html.substring(headIndex + HEAD.length())); + return builder.toString();*/ + StringBuilder builder = new StringBuilder(style); + builder.append(html).append(""); + return builder.toString(); + } + + private static String toHTMLrgb(Color rgb) { + StringBuilder builder = new StringBuilder(7); + builder.append('#'); + appendAsHexString(builder, rgb.getRed()); + appendAsHexString(builder, rgb.getGreen()); + appendAsHexString(builder, rgb.getBlue()); + return builder.toString(); + } + + private static void appendAsHexString(StringBuilder buffer, int intValue) { + String hexValue = Integer.toHexString(intValue); + if (hexValue.length() == 1) { + buffer.append('0'); + } + buffer.append(hexValue); + } + +} diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPPsiElementForLookupItem.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPPsiElementForLookupItem.java new file mode 100644 index 000000000..f1672e5cc --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPPsiElementForLookupItem.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.lsp4ij.operations.documentation; + +import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.psi.impl.FakePsiElement; +import com.intellij.psi.impl.source.tree.TreeUtil; +import org.eclipse.lsp4j.MarkupContent; +import org.intellij.plugins.relaxNG.compact.RncElementTypes; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Implement a fake {@link PsiElement} which stores the completion item documentation. + */ +public class LSPPsiElementForLookupItem extends FakePsiElement { + private final MarkupContent documentation; + private final PsiManager psiManager; + private final PsiFile file; + + int myStartOffset; + + int myColumn; + + public LSPPsiElementForLookupItem(MarkupContent documentation, PsiManager psiManager, PsiElement element) { + this.psiManager = psiManager; + this.file = element.getContainingFile(); + this.documentation = documentation; + myStartOffset=1; + myColumn=1; + } + + public List getDocumentation() { + return documentation != null ? Collections.singletonList(documentation) : null; + } + + public PsiElement getNavigationElement() { + + final PsiElement rncElement = file.findElementAt(myStartOffset + myColumn); + final ASTNode pattern = rncElement != null ? TreeUtil.findParent(rncElement.getNode(), RncElementTypes.PATTERN) : null; + final ASTNode nameClass = pattern != null ? pattern.findChildByType(RncElementTypes.NAME_CLASS) : null; + //noinspection ConstantConditions + return nameClass != null ? nameClass.getPsi() : rncElement; + } + + @Override + public PsiElement getParent() { + return getNavigationElement(); + } + + @Override + public String getName() { + return ""; + } + + @Override + public PsiFile getContainingFile() { + return file; + } + + + @Override + public PsiManager getManager() { + return psiManager; + } + + @Override + public boolean equals(Object another) { + return another instanceof LSPPsiElementForLookupItem && + ((LSPPsiElementForLookupItem)another).file == file; + } + + @Override + public int hashCode() { + return Objects.hash(file); + } + +} diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/hover/LSPTextHoverForFile.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPTextHoverForFile.java similarity index 63% rename from src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/hover/LSPTextHoverForFile.java rename to src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPTextHoverForFile.java index a5e4f9e8c..4fc9fb128 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/hover/LSPTextHoverForFile.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPTextHoverForFile.java @@ -8,7 +8,7 @@ * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ -package com.redhat.devtools.intellij.lsp4ij.operations.hover; +package com.redhat.devtools.intellij.lsp4ij.operations.documentation; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; @@ -17,24 +17,15 @@ import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils; import com.redhat.devtools.intellij.lsp4ij.LanguageServiceAccessor; import com.redhat.devtools.intellij.lsp4ij.internal.CancellationSupport; -import com.vladsch.flexmark.html.HtmlRenderer; -import com.vladsch.flexmark.parser.Parser; -import org.eclipse.lsp4j.Hover; -import org.eclipse.lsp4j.MarkedString; -import org.eclipse.lsp4j.MarkupContent; -import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.lsp4j.*; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.awt.*; import java.util.List; import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.*; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -44,29 +35,33 @@ public class LSPTextHoverForFile { private static final Logger LOGGER = LoggerFactory.getLogger(LSPTextHoverForFile.class); - private static final Parser PARSER = Parser.builder().build(); - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().build(); - private PsiElement lastElement; private int lastOffset = -1; private CompletableFuture> lspRequest; private CancellationSupport previousCancellationSupport; - public String getHoverContent(PsiElement element, int targetOffset, Editor editor) { + public List getHoverContent(PsiElement element, int targetOffset, Editor editor) { initiateHoverRequest(element, targetOffset); try { - String result = lspRequest.get(500, TimeUnit.MILLISECONDS).stream() + List result = lspRequest + .get(500, TimeUnit.MILLISECONDS).stream() .filter(Objects::nonNull) .map(LSPTextHoverForFile::getHoverString) .filter(Objects::nonNull) - .collect(Collectors.joining("\n\n")) //$NON-NLS-1$ - .trim(); - if (!result.isEmpty()) { + .collect(Collectors.toList()); + //.collect(Collectors.joining("\n\n")) //$NON-NLS-1$ + //.trim(); + /*if (!result.isEmpty()) { return styleHtml(editor, RENDERER.render(PARSER.parse(result))); - } + }*/ // The LSP hover request are finished, don't need to cancel the previous LSP requests. previousCancellationSupport = null; - } catch (ExecutionException | TimeoutException e) { + return result; + } catch (ExecutionException e) { + if (!(e.getCause() instanceof CancellationException)) { + LOGGER.warn(e.getLocalizedMessage(), e); + } + } catch (TimeoutException e) { LOGGER.warn(e.getLocalizedMessage(), e); } catch (InterruptedException e) { LOGGER.warn(e.getLocalizedMessage(), e); @@ -85,7 +80,7 @@ public String getHoverContent(PsiElement element, int targetOffset, Editor edito private void initiateHoverRequest(PsiElement element, int offset) { if (this.previousCancellationSupport != null) { - // The prvious LSP hover request is not finished,cancel it + // The previous LSP hover request is not finished,cancel it this.previousCancellationSupport.cancel(); } PsiDocumentManager manager = PsiDocumentManager.getInstance(element.getProject()); @@ -110,14 +105,14 @@ private void initiateHoverRequest(PsiElement element, int offset) { } } - private static @Nullable String getHoverString(Hover hover) { + private static @Nullable MarkupContent getHoverString(Hover hover) { Either>, MarkupContent> hoverContent = hover.getContents(); if (hoverContent.isLeft()) { List> contents = hoverContent.getLeft(); if (contents == null || contents.isEmpty()) { return null; } - return contents.stream().map(content -> { + String s = contents.stream().map(content -> { if (content.isLeft()) { return content.getLeft(); } else if (content.isRight()) { @@ -134,9 +129,10 @@ private void initiateHoverRequest(PsiElement element, int offset) { } else { return ""; //$NON-NLS-1$ } - }).filter(((Predicate) String::isEmpty).negate()).collect(Collectors.joining("\n\n")); //$NON-NLS-1$ ) + }).filter(((Predicate) String::isEmpty).negate()).collect(Collectors.joining("\n\n")); + return new MarkupContent(s, MarkupKind.PLAINTEXT); } else { - return hoverContent.getRight().getValue(); + return hoverContent.getRight(); } } @@ -144,45 +140,4 @@ private static boolean isHoverCapable(ServerCapabilities capabilities) { return (capabilities.getHoverProvider().isLeft() && capabilities.getHoverProvider().getLeft()) || capabilities.getHoverProvider().isRight(); } - - public static String styleHtml(Editor editor, String html) { - if (html == null || html.isEmpty()) { - return html; - } - Color background = editor.getColorsScheme().getDefaultBackground(); - Color foreground = editor.getColorsScheme().getDefaultForeground(); - // put CSS styling to match Eclipse style - String style = ""; //$NON-NLS-1$ - - /*int headIndex = html.indexOf(HEAD); - StringBuilder builder = new StringBuilder(html.length() + style.length()); - builder.append(html.substring(0, headIndex + HEAD.length())); - builder.append(style); - builder.append(html.substring(headIndex + HEAD.length())); - return builder.toString();*/ - StringBuilder builder = new StringBuilder(style); - builder.append(html).append(""); - return builder.toString(); - } - - private static String toHTMLrgb(Color rgb) { - StringBuilder builder = new StringBuilder(7); - builder.append('#'); - appendAsHexString(builder, rgb.getRed()); - appendAsHexString(builder, rgb.getGreen()); - appendAsHexString(builder, rgb.getBlue()); - return builder.toString(); - } - - private static void appendAsHexString(StringBuilder buffer, int intValue) { - String hexValue = Integer.toHexString(intValue); - if (hexValue.length() == 1) { - buffer.append('0'); - } - buffer.append(hexValue); - } - } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandler.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandler.java index 909c57ca0..14d19ceed 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandler.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandler.java @@ -22,7 +22,6 @@ import java.util.logging.Logger; public class LSPHighlightUsagesHandler extends HighlightUsagesHandlerBase { - private static final Logger LOGGER = Logger.getLogger(LSPHighlightUsagesHandler.class.getName()); private final List targets; public LSPHighlightUsagesHandler(Editor editor, PsiFile file, List targets) { diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/hover/LSPTextHover.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/hover/LSPTextHover.java deleted file mode 100644 index a24b3b118..000000000 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/hover/LSPTextHover.java +++ /dev/null @@ -1,124 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2020 Red Hat, Inc. - * Distributed under license by Red Hat, Inc. All rights reserved. - * This program is made available under the terms of the - * Eclipse Public License v2.0 which accompanies this distribution, - * and is available at https://www.eclipse.org/legal/epl-v20.html - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - ******************************************************************************/ -package com.redhat.devtools.intellij.lsp4ij.operations.hover; - -import com.intellij.lang.documentation.DocumentationProviderEx; -import com.intellij.lang.documentation.ExternalDocumentationHandler; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils; -import com.redhat.devtools.intellij.lsp4ij.LSPVirtualFileWrapper; -import com.redhat.devtools.intellij.lsp4ij.LanguageServiceAccessor; -import com.redhat.devtools.intellij.lsp4ij.internal.CancellationSupport; -import com.vladsch.flexmark.html.HtmlRenderer; -import com.vladsch.flexmark.parser.Parser; -import org.eclipse.lsp4j.Hover; -import org.eclipse.lsp4j.MarkedString; -import org.eclipse.lsp4j.MarkupContent; -import org.eclipse.lsp4j.ServerCapabilities; -import org.eclipse.lsp4j.jsonrpc.messages.Either; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.awt.*; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -/** - * LSP testDocument/hover support. - * - */ -public class LSPTextHover extends DocumentationProviderEx implements ExternalDocumentationHandler { - - @Nullable - @Override - public String getQuickNavigateInfo(PsiElement element, PsiElement originalElement) { - return generateDoc(element, originalElement); - } - - @Nullable - @Override - public String generateDoc(PsiElement element, @Nullable PsiElement originalElement) { - Project project = element.getProject(); - if (project.isDisposed()) { - return null; - } - if (originalElement == null || !Objects.equals(element.getContainingFile(), originalElement.getContainingFile())) { - return null; - } - Editor editor = LSPIJUtils.editorForElement(element); - if (editor == null) { - return null; - } - - VirtualFile file = originalElement.getContainingFile().getVirtualFile(); - int targetOffset = getTargetOffset(originalElement); - return LSPVirtualFileWrapper.getLSPVirtualFileWrapper(file).getHoverContent(element, targetOffset, editor); - } - - private static int getTargetOffset(PsiElement originalElement) { - int startOffset = originalElement.getTextOffset(); - int textLength = originalElement.getTextLength(); - return startOffset + textLength / 2; - } - - @Nullable - @Override - public PsiElement getDocumentationElementForLookupItem(PsiManager psiManager, Object object, PsiElement element) { - return null; - } - - @Nullable - @Override - public PsiElement getDocumentationElementForLink(PsiManager psiManager, String link, PsiElement context) { - return null; - } - - @Override - public boolean handleExternal(PsiElement element, PsiElement originalElement) { - return false; - } - - @Override - public boolean handleExternalLink(PsiManager psiManager, String link, PsiElement context) { - VirtualFile file = LSPIJUtils.findResourceFor(link); - if (file != null) { - FileEditorManager.getInstance(psiManager.getProject()).openFile(file, true, true); - return true; - } - return false; - } - - @Override - public boolean canFetchDocumentationLink(String link) { - return false; - } - - @Override - public @NotNull String fetchExternalDocumentation(@NotNull String link, @Nullable PsiElement element) { - return null; - } -} diff --git a/src/main/resources/META-INF/lsp4ij-quarkus.xml b/src/main/resources/META-INF/lsp4ij-quarkus.xml index 9e149efbc..56fb1dd12 100644 --- a/src/main/resources/META-INF/lsp4ij-quarkus.xml +++ b/src/main/resources/META-INF/lsp4ij-quarkus.xml @@ -47,10 +47,10 @@ - + - + diff --git a/src/main/resources/META-INF/lsp4ij-qute.xml b/src/main/resources/META-INF/lsp4ij-qute.xml index 4d7b16516..2e2e43b31 100644 --- a/src/main/resources/META-INF/lsp4ij-qute.xml +++ b/src/main/resources/META-INF/lsp4ij-qute.xml @@ -24,11 +24,11 @@ - - - - - + + + + +