diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/codelens/LSPCodelensInlayProvider.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/codelens/LSPCodelensInlayProvider.java index cc6d7b130..6c2f3ce6c 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/codelens/LSPCodelensInlayProvider.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/codelens/LSPCodelensInlayProvider.java @@ -17,6 +17,7 @@ import com.intellij.codeInsight.hints.presentation.InlayPresentation; import com.intellij.codeInsight.hints.presentation.PresentationFactory; import com.intellij.codeInsight.hints.presentation.SequencePresentation; +import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; @@ -64,39 +65,27 @@ public InlayHintsCollector getCollectorFor(@NotNull PsiFile psiFile, @Override public boolean collect(@NotNull PsiElement psiElement, @NotNull Editor editor, @NotNull InlayHintsSink inlayHintsSink) { try { + Document document = editor.getDocument(); Project project = psiElement.getProject(); if (project.isDisposed()) { // The project has been closed, don't collect code lenses. return false; } - URI docURI = LSPIJUtils.toUri(editor.getDocument()); + URI docURI = LSPIJUtils.toUri(document); if (docURI != null) { CodeLensParams param = new CodeLensParams(new TextDocumentIdentifier(docURI.toString())); BlockingDeque> pairs = new LinkedBlockingDeque<>(); - List>> codelenses = new ArrayList<>(); - CompletableFuture future = LanguageServiceAccessor.getInstance(project) - .getLanguageServers(editor.getDocument(), capabilities -> capabilities.getCodeLensProvider() != null) - .thenComposeAsync(languageServers -> CompletableFuture.allOf(languageServers.stream() - .map(languageServer -> languageServer.getSecond().getTextDocumentService().codeLens(param) - .thenAcceptAsync(codeLenses -> { - // textDocument/codeLens may return null - if (codeLenses != null) { - codeLenses.stream().filter(Objects::nonNull) - .forEach(codeLens -> pairs.add(new Pair(codeLens, languageServer.getSecond()))); - } - })) - .toArray(CompletableFuture[]::new))); - while (!future.isDone() || !pairs.isEmpty()) { - ProgressManager.checkCanceled(); - Pair pair = pairs.poll(25, TimeUnit.MILLISECONDS); - if (pair != null) { - int offset = LSPIJUtils.toOffset(pair.getFirst().getRange().getStart(), editor.getDocument()); - codelenses.add(Pair.create(offset, pair)); - } - } - Map>>> elements = codelenses.stream().collect(Collectors.groupingBy(p -> p.first)); - elements.forEach((offset, list) -> inlayHintsSink.addBlockElement(offset, true, - true, 0, toPresentation(editor, offset, list, getFactory()))); + CompletableFuture future = collect(document, project, param, pairs); + List>> codeLenses = createCodeLenses(document, pairs, future); + Map>>> elements = codeLenses.stream().collect(Collectors.groupingBy(p -> p.first)); + elements.forEach((offset, list) -> + inlayHintsSink.addBlockElement( + offset, + true, + true, + 0, + toPresentation(editor, offset, list, getFactory())) + ); } } catch (InterruptedException e) { LOGGER.warn(e.getLocalizedMessage(), e); @@ -104,13 +93,44 @@ public boolean collect(@NotNull PsiElement psiElement, @NotNull Editor editor, @ } return false; } + + @NotNull + private List>> createCodeLenses(Document document, BlockingDeque> pairs, CompletableFuture future) throws InterruptedException { + List>> codelenses = new ArrayList<>(); + while (!future.isDone() || !pairs.isEmpty()) { + ProgressManager.checkCanceled(); + Pair pair = pairs.poll(25, TimeUnit.MILLISECONDS); + if (pair != null) { + int offset = LSPIJUtils.toOffset(pair.getFirst().getRange().getStart(), document); + codelenses.add(Pair.create(offset, pair)); + } + } + return codelenses; + } + + private CompletableFuture collect(Document document, Project project, CodeLensParams param, BlockingDeque> pairs) { + return LanguageServiceAccessor.getInstance(project) + .getLanguageServers(document, capabilities -> capabilities.getCodeLensProvider() != null) + .thenComposeAsync(languageServers -> CompletableFuture.allOf(languageServers.stream() + .map(languageServer -> languageServer.getSecond().getTextDocumentService().codeLens(param) + .thenAcceptAsync(codeLenses -> { + // textDocument/codeLens may return null + if (codeLenses != null) { + codeLenses.stream().filter(Objects::nonNull) + .forEach(codeLens -> pairs.add(new Pair(codeLens, languageServer.getSecond()))); + } + })) + .toArray(CompletableFuture[]::new))); + } }; } - private InlayPresentation toPresentation(Editor editor, int offset, - List>> elements, - PresentationFactory factory) { - + private InlayPresentation toPresentation( + Editor editor, + int offset, + List>> elements, + PresentationFactory factory + ) { int line = editor.getDocument().getLineNumber(offset); int column = offset - editor.getDocument().getLineStartOffset(line); List presentations = new ArrayList<>(); @@ -124,9 +144,9 @@ private InlayPresentation toPresentation(Editor editor, int offset, presentations.add(text); } else { // Codelens defines a Command, create a clickable inlay hint - InlayPresentation clickableText = factory.referenceOnHover(text, (event, translated) -> { - executeClientCommand(p.second.second, p.second.first, (Component) event.getSource(), editor.getProject()); - }); + InlayPresentation clickableText = factory.referenceOnHover(text, (event, translated) -> + executeClientCommand(p.second.second, p.second.first, (Component) event.getSource(), editor.getProject()) + ); presentations.add(clickableText); } presentations.add(factory.textSpacePlaceholder(1, true)); @@ -135,11 +155,12 @@ private InlayPresentation toPresentation(Editor editor, int offset, } private void executeClientCommand(LanguageServer languageServer, CodeLens codeLens, Component source, Project project) { - if (LanguageServiceAccessor.getInstance(project).checkCapability(languageServer, - capabilites -> Boolean.TRUE.equals(capabilites.getCodeLensProvider().getResolveProvider()))) { - languageServer.getTextDocumentService().resolveCodeLens(codeLens).thenAcceptAsync(resolvedCodeLens -> { - executeClientCommand(source, resolvedCodeLens.getCommand()); - }); + if (LanguageServiceAccessor.getInstance(project).checkCapability(languageServer, capabilities -> + Boolean.TRUE.equals(capabilities.getCodeLensProvider().getResolveProvider())) + ) { + languageServer.getTextDocumentService().resolveCodeLens(codeLens).thenAcceptAsync(resolvedCodeLens -> + executeClientCommand(source, resolvedCodeLens.getCommand()) + ); } else { executeClientCommand(source, codeLens.getCommand()); } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/inlayhint/LSPInlayHintInlayProvider.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/inlayhint/LSPInlayHintInlayProvider.java index e581bb495..833a26d16 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/inlayhint/LSPInlayHintInlayProvider.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/inlayhint/LSPInlayHintInlayProvider.java @@ -63,43 +63,59 @@ public InlayHintsCollector getCollectorFor(@NotNull PsiFile psiFile, return new FactoryInlayHintsCollector(editor) { @Override public boolean collect(@NotNull PsiElement psiElement, @NotNull Editor editor, @NotNull InlayHintsSink inlayHintsSink) { + Document document = editor.getDocument(); try { - URI docURI = LSPIJUtils.toUri(editor.getDocument()); - if (docURI != null) { - Range viewPortRange = getViewPortRange(editor); - InlayHintParams param = new InlayHintParams(new TextDocumentIdentifier(docURI.toString()), viewPortRange); - BlockingDeque> pairs = new LinkedBlockingDeque<>(); - List>> inlayhints = new ArrayList<>(); - CompletableFuture future = LanguageServiceAccessor.getInstance(psiElement.getProject()) - .getLanguageServers(editor.getDocument(), capabilities -> capabilities.getInlayHintProvider() != null) - .thenComposeAsync(languageServers -> CompletableFuture.allOf(languageServers.stream() - .map(languageServer -> languageServer.getSecond().getTextDocumentService().inlayHint(param) - .thenAcceptAsync(inlayHints -> { - // textDocument/codeLens may return null - if (inlayHints != null) { - inlayHints.stream().filter(Objects::nonNull) - .forEach(inlayHint -> pairs.add(new Pair(inlayHint, languageServer.getSecond()))); - } - })) - .toArray(CompletableFuture[]::new))); - while (!future.isDone() || !pairs.isEmpty()) { - ProgressManager.checkCanceled(); - Pair pair = pairs.poll(25, TimeUnit.MILLISECONDS); - if (pair != null) { - int offset = LSPIJUtils.toOffset(pair.getFirst().getPosition(), editor.getDocument()); - inlayhints.add(Pair.create(offset, pair)); - } - } - Map>>> elements = inlayhints.stream().collect(Collectors.groupingBy(p -> p.first)); - elements.forEach((offset, list) -> inlayHintsSink.addInlineElement(offset, false, - toPresentation(editor, offset, list, getFactory()), false)); + URI docURI = LSPIJUtils.toUri(document); + if (docURI == null) { + return false; } + Range viewPortRange = getViewPortRange(editor); + InlayHintParams param = new InlayHintParams(new TextDocumentIdentifier(docURI.toString()), viewPortRange); + BlockingDeque> pairs = new LinkedBlockingDeque<>(); + CompletableFuture future = collect(psiElement.getProject(), document, param, pairs); + List>> inlayHints = createInlayHints(document, pairs, future); + Map>>> elements = inlayHints.stream().collect(Collectors.groupingBy(p -> p.first)); + elements.forEach((offset, list) -> + inlayHintsSink.addInlineElement(offset, false, toPresentation(editor, list, getFactory()), false)); } catch (InterruptedException e) { LOGGER.warn(e.getLocalizedMessage(), e); Thread.currentThread().interrupt(); } return false; } + + @NotNull + private List>> createInlayHints( + @NotNull Document document, + BlockingDeque> pairs, + CompletableFuture future) + throws InterruptedException { + List>> inlayHints = new ArrayList<>(); + while (!future.isDone() || !pairs.isEmpty()) { + ProgressManager.checkCanceled(); + Pair pair = pairs.poll(25, TimeUnit.MILLISECONDS); + if (pair != null) { + int offset = LSPIJUtils.toOffset(pair.getFirst().getPosition(), document); + inlayHints.add(Pair.create(offset, pair)); + } + } + return inlayHints; + } + + private CompletableFuture collect(@NotNull Project project, @NotNull Document document, InlayHintParams param, BlockingDeque> pairs) { + return LanguageServiceAccessor.getInstance(project) + .getLanguageServers(document, capabilities -> capabilities.getInlayHintProvider() != null) + .thenComposeAsync(languageServers -> CompletableFuture.allOf(languageServers.stream() + .map(languageServer -> languageServer.getSecond().getTextDocumentService().inlayHint(param) + .thenAcceptAsync(inlayHints -> { + // textDocument/codeLens may return null + if (inlayHints != null) { + inlayHints.stream().filter(Objects::nonNull) + .forEach(inlayHint -> pairs.add(new Pair(inlayHint, languageServer.getSecond()))); + } + })) + .toArray(CompletableFuture[]::new))); + } }; } @@ -114,7 +130,7 @@ private static Range getViewPortRange(Editor editor) { return new Range(start, end); } - private InlayPresentation toPresentation(Editor editor, int offset, + private InlayPresentation toPresentation(Editor editor, List>> elements, PresentationFactory factory) { List presentations = new ArrayList<>(); @@ -125,17 +141,7 @@ private InlayPresentation toPresentation(Editor editor, int offset, } else { int index = 0; for (InlayHintLabelPart part : label.getRight()) { - InlayPresentation text = factory.smallText(part.getValue()); - if (!hasCommand(part)) { - // No command, create a simple text inlay hint - presentations.add(text); - } else { - // InlayHintLabelPart defines a Command, create a clickable inlay hint - int finalIndex = index; - text = factory.referenceOnHover(text, (event, translated) -> { - executeClientCommand(p.second.second, p.second.first, finalIndex, (Component) event.getSource(), editor.getProject()); - }); - } + InlayPresentation text = createInlayPresentation(editor.getProject(), factory, presentations, p, index, part); if (part.getTooltip() != null && part.getTooltip().isLeft()) { text = factory.withTooltip(part.getTooltip().getLeft(), text); } @@ -147,13 +153,40 @@ private InlayPresentation toPresentation(Editor editor, int offset, return factory.roundWithBackground(new SequencePresentation(presentations)); } + @NotNull + private InlayPresentation createInlayPresentation( + Project project, + PresentationFactory factory, + List presentations, Pair> p, + int index, + InlayHintLabelPart part) { + InlayPresentation text = factory.smallText(part.getValue()); + if (!hasCommand(part)) { + // No command, create a simple text inlay hint + presentations.add(text); + } else { + // InlayHintLabelPart defines a Command, create a clickable inlay hint + int finalIndex = index; + text = factory.referenceOnHover(text, (event, translated) -> + executeClientCommand(p.second.second, p.second.first, finalIndex, (Component) event.getSource(), project) + ); + } + return text; + } + private static boolean hasCommand(InlayHintLabelPart part) { Command command = part.getCommand(); return (command != null && command.getCommand() != null && !command.getCommand().isEmpty()); } - private void executeClientCommand(LanguageServer languageServer, InlayHint inlayHint, int index, Component source, - Project project) { + private void executeClientCommand( + LanguageServer languageServer, + InlayHint inlayHint, + int index, + Component source, + Project project + ) { if (LanguageServiceAccessor.getInstance(project) .checkCapability(languageServer, capabilites -> isResolveSupported(capabilites.getInlayHintProvider()))) { languageServer.getTextDocumentService()