From 0fcdb72e5fde3d1be709523abb606cb64d88cd91 Mon Sep 17 00:00:00 2001 From: azerr Date: Sun, 8 Oct 2023 17:11:00 +0200 Subject: [PATCH] fix: CodeLens are not displayed in IDEA EAP 2023-3 EAP Fixes #1204 Signed-off-by: azerr --- .../lsp4ij/AbstractLSPInlayProvider.java | 48 +++++++++++-- .../codelens/LSPCodelensInlayProvider.java | 69 +++++++++---------- .../inlayhint/LSPInlayHintInlayProvider.java | 24 ++++--- 3 files changed, 90 insertions(+), 51 deletions(-) diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/AbstractLSPInlayProvider.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/AbstractLSPInlayProvider.java index 25ad680ee..436ece3d6 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/AbstractLSPInlayProvider.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/AbstractLSPInlayProvider.java @@ -10,11 +10,7 @@ ******************************************************************************/ package com.redhat.devtools.intellij.lsp4ij; -import com.intellij.codeInsight.hints.ChangeListener; -import com.intellij.codeInsight.hints.ImmediateConfigurable; -import com.intellij.codeInsight.hints.InlayHintsProvider; -import com.intellij.codeInsight.hints.NoSettings; -import com.intellij.codeInsight.hints.SettingsKey; +import com.intellij.codeInsight.hints.*; import com.intellij.ide.DataManager; import com.intellij.lang.Language; import com.intellij.openapi.actionSystem.ActionManager; @@ -24,6 +20,12 @@ import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.actionSystem.impl.SimpleDataContext; +import com.intellij.openapi.editor.Editor; +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.ui.layout.LCFlags; import com.intellij.ui.layout.LayoutKt; import com.redhat.devtools.intellij.lsp4ij.commands.CommandExecutor; @@ -36,6 +38,12 @@ public abstract class AbstractLSPInlayProvider implements InlayHintsProvider { + private final Key sinkKey; + + protected AbstractLSPInlayProvider(Key sinkKey) { + this.sinkKey = sinkKey; + } + private SettingsKey key = new SettingsKey<>("LSP.hints"); @Override @@ -90,11 +98,39 @@ protected void executeClientCommand(Component source, Command command) { if (command != null) { AnAction action = ActionManager.getInstance().getAction(command.getCommand()); if (action != null) { - DataContext context = SimpleDataContext.getSimpleContext(CommandExecutor.LSP_COMMAND, command, DataManager.getInstance().getDataContext(source)); + DataContext context = SimpleDataContext.getSimpleContext(CommandExecutor.LSP_COMMAND, command, DataManager.getInstance().getDataContext(source)); action.actionPerformed(new AnActionEvent(null, context, ActionPlaces.UNKNOWN, new Presentation(), ActionManager.getInstance(), 0)); } } } + + /** + * Returns the virtual file where inlay hint must be added and null otherwise. + * + * @param psiFile the psi file. + * @param editor the editor. + * @param inlayHintsSink the inlay hints sink. + * @return the virtual file where inlay hint must be added and null otherwise. + */ + protected @Nullable VirtualFile getFile(@NotNull PsiFile psiFile, @NotNull Editor editor, @NotNull InlayHintsSink inlayHintsSink) { + Project project = psiFile.getProject(); + if (project.isDisposed()) { + // The project has been closed, don't collect inlay hints. + return null; + } + // Before IJ 2023-3, FactoryInlayHintsCollector#collect(PsiElement element.. is called once time with PsiFile as element. + // Since IJ 2023-3, FactoryInlayHintsCollector#collect(PsiElement element.. is called several times for each tokens of the PsiFile + // which causes the problem of codelens/inlay hint which are not displayed because there are too many call of LSP request codelens/inlayhint which are cancelled. + // With IJ 2023-3 we need to collect LSP CodeLens/InlayHint just for the first call. To implement this idea, we store the instance InlayHintsSink, + // and we forbid the compute of inlay hint if InlayHintsSink is already filled. + InlayHintsSink sink = editor.getUserData(sinkKey); + if (sink == inlayHintsSink) { + // LSP CodeLens/InlayHint has already be done for teh file, ignore it. + return null; + } + editor.putUserData(sinkKey, inlayHintsSink); + return LSPIJUtils.getFile(psiFile); + } } 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 384e3100c..d88aa14fc 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 @@ -22,6 +22,7 @@ import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; @@ -40,11 +41,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.awt.Component; +import java.awt.*; import java.net.URI; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.BlockingDeque; import java.util.concurrent.CompletableFuture; @@ -58,6 +58,12 @@ public class LSPCodelensInlayProvider extends AbstractLSPInlayProvider { private static final Logger LOGGER = LoggerFactory.getLogger(LSPCodelensInlayProvider.class); + private static final Key SINK_KEY = new Key<>(LSPCodelensInlayProvider.class.getName()); + + public LSPCodelensInlayProvider() { + super(SINK_KEY); + } + @Nullable @Override public InlayHintsCollector getCollectorFor(@NotNull PsiFile psiFile, @@ -67,38 +73,36 @@ public InlayHintsCollector getCollectorFor(@NotNull PsiFile psiFile, return new FactoryInlayHintsCollector(editor) { @Override public boolean collect(@NotNull PsiElement psiElement, @NotNull Editor editor, @NotNull InlayHintsSink inlayHintsSink) { - Project project = psiElement.getProject(); - if (project.isDisposed()) { - // The project has been closed, don't collect code lenses. + VirtualFile file = getFile(psiFile, editor, inlayHintsSink); + if (file == null) { + // Codelens must not be collected return false; } Document document = editor.getDocument(); final CancellationSupport cancellationSupport = new CancellationSupport(); try { - VirtualFile file = LSPIJUtils.getFile(psiElement); - URI docURI = LSPIJUtils.toUri(file); - if (docURI != null) { - CodeLensParams param = new CodeLensParams(new TextDocumentIdentifier(docURI.toString())); - BlockingDeque> pairs = new LinkedBlockingDeque<>(); + URI fileUri = LSPIJUtils.toUri(file); + CodeLensParams param = new CodeLensParams(new TextDocumentIdentifier(fileUri.toASCIIString())); + BlockingDeque> pairs = new LinkedBlockingDeque<>(); - CompletableFuture future = collect(file, project, param, pairs, cancellationSupport); - List>> codeLenses = createCodeLenses(document, pairs, future, cancellationSupport); - codeLenses.stream() - .collect(Collectors.groupingBy(p -> p.first)) - .forEach((offset, list) -> - inlayHintsSink.addBlockElement( - offset, - true, - true, - 0, - toPresentation(editor, offset, list, getFactory())) - ); - } + CompletableFuture future = collect(file, psiFile.getProject(), param, pairs, cancellationSupport); + List>> codeLenses = createCodeLenses(document, pairs, future, cancellationSupport); + codeLenses.stream() + .collect(Collectors.groupingBy(p -> p.first)) + .forEach((offset, list) -> + inlayHintsSink.addBlockElement( + offset, + true, + true, + 0, + toPresentation(editor, offset, list, getFactory())) + ); } catch (ProcessCanceledException e) { // Cancel all LSP requests cancellationSupport.cancel(); - throw e; } catch (InterruptedException e) { + // Cancel all LSP requests + cancellationSupport.cancel(); LOGGER.warn(e.getLocalizedMessage(), e); Thread.currentThread().interrupt(); } @@ -109,22 +113,17 @@ public boolean collect(@NotNull PsiElement psiElement, @NotNull Editor editor, @ private List>> createCodeLenses(Document document, BlockingDeque> pairs, CompletableFuture future, CancellationSupport cancellationSupport) throws InterruptedException { List>> codelenses = new ArrayList<>(); while (!future.isDone() || !pairs.isEmpty()) { - try { - 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)); - } - } catch (ProcessCanceledException e) { - cancellationSupport.cancel(); - throw e; + 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(VirtualFile file, Project project, CodeLensParams param, BlockingDeque> pairs, CancellationSupport cancellationSupport) { + private @NotNull CompletableFuture collect(@NotNull VirtualFile file, @NotNull Project project, @NotNull CodeLensParams param, @NotNull BlockingDeque> pairs, @NotNull CancellationSupport cancellationSupport) { return LanguageServiceAccessor.getInstance(project) .getLanguageServers(file, capabilities -> capabilities.getCodeLensProvider() != null) .thenComposeAsync(languageServers -> 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 f89f2965d..a9075be86 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 @@ -22,6 +22,7 @@ import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; @@ -55,6 +56,12 @@ public class LSPInlayHintInlayProvider extends AbstractLSPInlayProvider { private static final Logger LOGGER = LoggerFactory.getLogger(LSPInlayHintInlayProvider.class); + private static final Key SINK_KEY = new Key<>(LSPInlayHintInlayProvider.class.getName()); + + public LSPInlayHintInlayProvider() { + super(SINK_KEY); + } + @Nullable @Override public InlayHintsCollector getCollectorFor(@NotNull PsiFile psiFile, @@ -64,21 +71,17 @@ public InlayHintsCollector getCollectorFor(@NotNull PsiFile psiFile, return new FactoryInlayHintsCollector(editor) { @Override public boolean collect(@NotNull PsiElement psiElement, @NotNull Editor editor, @NotNull InlayHintsSink inlayHintsSink) { - Project project = psiElement.getProject(); - if (project.isDisposed()) { - // The project has been closed, don't collect inlay hints. + VirtualFile file = getFile(psiFile, editor, inlayHintsSink); + if (file == null) { + // InlayHint must not be collected return false; } Document document = editor.getDocument(); final CancellationSupport cancellationSupport = new CancellationSupport(); try { - VirtualFile file = LSPIJUtils.getFile(psiFile); - if (file == null) { - return false; - } - URI docURI = LSPIJUtils.toUri(file); + URI fileUri = LSPIJUtils.toUri(file); Range viewPortRange = getViewPortRange(editor); - InlayHintParams param = new InlayHintParams(new TextDocumentIdentifier(docURI.toString()), viewPortRange); + InlayHintParams param = new InlayHintParams(new TextDocumentIdentifier(fileUri.toASCIIString()), viewPortRange); BlockingDeque> pairs = new LinkedBlockingDeque<>(); CompletableFuture future = collect(psiElement.getProject(), file, param, pairs, cancellationSupport); @@ -90,8 +93,9 @@ public boolean collect(@NotNull PsiElement psiElement, @NotNull Editor editor, @ } catch (ProcessCanceledException e) { // Cancel all LSP requests cancellationSupport.cancel(); - throw e; } catch (InterruptedException e) { + // Cancel all LSP requests + cancellationSupport.cancel(); LOGGER.warn(e.getLocalizedMessage(), e); Thread.currentThread().interrupt(); }