diff --git a/src/main/java/com/redhat/devtools/lsp4ij/LSPFileSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/LSPFileSupport.java index d4eb0e017..8258fdfc7 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/LSPFileSupport.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/LSPFileSupport.java @@ -19,6 +19,7 @@ import com.redhat.devtools.lsp4ij.features.color.LSPColorSupport; import com.redhat.devtools.lsp4ij.features.completion.LSPCompletionSupport; import com.redhat.devtools.lsp4ij.features.declaration.LSPDeclarationSupport; +import com.redhat.devtools.lsp4ij.features.definition.LSPDefinitionSupport; import com.redhat.devtools.lsp4ij.features.documentLink.LSPDocumentLinkSupport; import com.redhat.devtools.lsp4ij.features.documentation.LSPHoverSupport; import com.redhat.devtools.lsp4ij.features.foldingRange.LSPFoldingRangeSupport; @@ -43,6 +44,7 @@ public class LSPFileSupport extends UserDataHolderBase implements Disposable { private static final Key LSP_FILE_SUPPORT_KEY = Key.create("lsp.file.support"); + private final PsiFile file; private final LSPCodeLensSupport codeLensSupport; @@ -75,6 +77,8 @@ public class LSPFileSupport extends UserDataHolderBase implements Disposable { private final LSPReferenceSupport referenceSupport; + private final LSPDefinitionSupport definitionSupport; + private final LSPDeclarationSupport declarationSupport; private final LSPTypeDefinitionSupport typeDefinitionSupport; @@ -99,6 +103,7 @@ private LSPFileSupport(@NotNull PsiFile file) { this.implementationSupport = new LSPImplementationSupport(file); this.referenceSupport = new LSPReferenceSupport(file); this.declarationSupport = new LSPDeclarationSupport(file); + this.definitionSupport = new LSPDefinitionSupport(file); this.typeDefinitionSupport = new LSPTypeDefinitionSupport(file); this.semanticTokensSupport = new LSPSemanticTokensSupport(file); file.putUserData(LSP_FILE_SUPPORT_KEY, this); @@ -123,6 +128,7 @@ public void dispose() { getCompletionSupport().cancel(); getImplementationSupport().cancel(); getReferenceSupport().cancel(); + getDefinitionSupport().cancel(); getDeclarationSupport().cancel(); getTypeDefinitionSupport().cancel(); getSemanticTokensSupport().cancel(); @@ -270,6 +276,15 @@ public LSPReferenceSupport getReferenceSupport() { return referenceSupport; } + /** + * Returns the LSP definition support. + * + * @return the LSP definition support. + */ + public LSPDefinitionSupport getDefinitionSupport() { + return definitionSupport; + } + /** * Returns the LSP declaration support. * diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/declaration/LSPDeclarationSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/features/declaration/LSPDeclarationSupport.java index 2624d9315..671c8bead 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/declaration/LSPDeclarationSupport.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/declaration/LSPDeclarationSupport.java @@ -13,7 +13,6 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; -import com.redhat.devtools.lsp4ij.LSPIJUtils; import com.redhat.devtools.lsp4ij.LSPRequestConstants; import com.redhat.devtools.lsp4ij.LanguageServerItem; import com.redhat.devtools.lsp4ij.LanguageServiceAccessor; @@ -61,7 +60,6 @@ protected CompletableFuture> doLoad(LSPDeclarationParams params, @NotNull Project project, @NotNull LSPDeclarationParams params, @NotNull CancellationSupport cancellationSupport) { - var textDocumentIdentifier = LSPIJUtils.toTextDocumentIdentifier(file); return LanguageServiceAccessor.getInstance(project) .getLanguageServers(file, LanguageServerItem::isDeclarationSupported) .thenComposeAsync(languageServers -> { diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/definition/LSPDefinitionParams.java b/src/main/java/com/redhat/devtools/lsp4ij/features/definition/LSPDefinitionParams.java new file mode 100644 index 000000000..4d422ef8f --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/definition/LSPDefinitionParams.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2024 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 definition + ******************************************************************************/ +package com.redhat.devtools.lsp4ij.features.definition; + +import org.eclipse.lsp4j.DefinitionParams; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.TextDocumentIdentifier; + +/** + * LSP definition parameters which hosts the offset where definition has been triggered. + */ +public class LSPDefinitionParams extends DefinitionParams { + + // Use transient to avoid serializing the fields when GSON will be processed + private transient final int offset; + + public LSPDefinitionParams(TextDocumentIdentifier textDocument, Position position, int offset) { + super.setTextDocument(textDocument); + super.setPosition(position); + this.offset = offset; + } + + public int getOffset() { + return offset; + } +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/definition/LSPDefinitionSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/features/definition/LSPDefinitionSupport.java new file mode 100644 index 000000000..638f7ec24 --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/definition/LSPDefinitionSupport.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2024 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and definition + ******************************************************************************/ +package com.redhat.devtools.lsp4ij.features.definition; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.redhat.devtools.lsp4ij.LSPRequestConstants; +import com.redhat.devtools.lsp4ij.LanguageServerItem; +import com.redhat.devtools.lsp4ij.LanguageServiceAccessor; +import com.redhat.devtools.lsp4ij.features.AbstractLSPDocumentFeatureSupport; +import com.redhat.devtools.lsp4ij.internal.CancellationSupport; +import com.redhat.devtools.lsp4ij.internal.CompletableFutures; +import org.eclipse.lsp4j.Location; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * LSP definition support which collect: + * + * + */ +public class LSPDefinitionSupport extends AbstractLSPDocumentFeatureSupport> { + + private Integer previousOffset; + + public LSPDefinitionSupport(@NotNull PsiFile file) { + super(file); + } + + public CompletableFuture> getDefinitions(LSPDefinitionParams params) { + int offset = params.getOffset(); + if (previousOffset != null && !previousOffset.equals(offset)) { + super.cancel(); + } + previousOffset = offset; + return super.getFeatureData(params); + } + + @Override + protected CompletableFuture> doLoad(LSPDefinitionParams params, CancellationSupport cancellationSupport) { + PsiFile file = super.getFile(); + return collectTypeDefinitions(file.getVirtualFile(), file.getProject(), params, cancellationSupport); + } + + private static @NotNull CompletableFuture> collectTypeDefinitions(@NotNull VirtualFile file, + @NotNull Project project, + @NotNull LSPDefinitionParams params, + @NotNull CancellationSupport cancellationSupport) { + return LanguageServiceAccessor.getInstance(project) + .getLanguageServers(file, LanguageServerItem::isTypeDefinitionSupported) + .thenComposeAsync(languageServers -> { + // Here languageServers is the list of language servers which matches the given file + // and which have definition capability + if (languageServers.isEmpty()) { + return CompletableFuture.completedFuture(null); + } + + // Collect list of textDocument/definition future for each language servers + List>> definitionsPerServerFutures = languageServers + .stream() + .map(languageServer -> getTypeDefinitionFor(params, languageServer, cancellationSupport)) + .toList(); + + // Merge list of textDocument/definition future in one future which return the list of definition ranges + return CompletableFutures.mergeInOneFuture(definitionsPerServerFutures, cancellationSupport); + }); + } + + private static CompletableFuture> getTypeDefinitionFor(LSPDefinitionParams params, + LanguageServerItem languageServer, + CancellationSupport cancellationSupport) { + return cancellationSupport.execute(languageServer + .getTextDocumentService() + .definition(params), languageServer, LSPRequestConstants.TEXT_DOCUMENT_DEFINITION) + .thenApplyAsync(locations -> { + if (locations == null) { + // textDocument/definition may return null + return Collections.emptyList(); + } + if (locations.isLeft()) { + return locations.getLeft() + .stream() + .map(l -> new Location(l.getUri(), l.getRange())) + .toList(); + + } + return locations.getRight() + .stream() + .map(l -> new Location(l.getTargetUri(), l.getTargetRange())) + .toList(); + }); + } + +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/implementation/LSPImplementationSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/features/implementation/LSPImplementationSupport.java index 7650fe786..53b5887cc 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/implementation/LSPImplementationSupport.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/implementation/LSPImplementationSupport.java @@ -13,7 +13,6 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; -import com.redhat.devtools.lsp4ij.LSPIJUtils; import com.redhat.devtools.lsp4ij.LSPRequestConstants; import com.redhat.devtools.lsp4ij.LanguageServerItem; import com.redhat.devtools.lsp4ij.LanguageServiceAccessor; @@ -61,7 +60,6 @@ protected CompletableFuture> doLoad(LSPImplementationParams param @NotNull Project project, @NotNull LSPImplementationParams params, @NotNull CancellationSupport cancellationSupport) { - var textDocumentIdentifier = LSPIJUtils.toTextDocumentIdentifier(file); return LanguageServiceAccessor.getInstance(project) .getLanguageServers(file, LanguageServerItem::isImplementationSupported) .thenComposeAsync(languageServers -> { diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/navigation/LSPGotoDeclarationHandler.java b/src/main/java/com/redhat/devtools/lsp4ij/features/navigation/LSPGotoDeclarationHandler.java index c95002928..15b1f79d6 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/navigation/LSPGotoDeclarationHandler.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/navigation/LSPGotoDeclarationHandler.java @@ -11,31 +11,39 @@ package com.redhat.devtools.lsp4ij.features.navigation; import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.redhat.devtools.lsp4ij.LSPFileSupport; import com.redhat.devtools.lsp4ij.LSPIJUtils; -import com.redhat.devtools.lsp4ij.LanguageServerItem; import com.redhat.devtools.lsp4ij.LanguageServersRegistry; -import com.redhat.devtools.lsp4ij.LanguageServiceAccessor; -import com.redhat.devtools.lsp4ij.internal.CancellationSupport; -import com.redhat.devtools.lsp4ij.internal.CancellationUtil; import com.redhat.devtools.lsp4ij.features.LSPPsiElement; -import org.eclipse.lsp4j.DefinitionParams; +import com.redhat.devtools.lsp4ij.features.definition.LSPDefinitionParams; +import com.redhat.devtools.lsp4ij.features.definition.LSPDefinitionSupport; +import com.redhat.devtools.lsp4ij.features.references.LSPReferenceParams; +import com.redhat.devtools.lsp4ij.features.references.LSPReferenceSupport; +import com.redhat.devtools.lsp4ij.usages.LSPUsageType; +import com.redhat.devtools.lsp4ij.usages.LSPUsagesManager; import org.eclipse.lsp4j.Location; -import org.eclipse.lsp4j.LocationLink; -import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; -import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.util.Ranges; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; -import java.util.concurrent.*; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import static com.redhat.devtools.lsp4ij.features.LSPPsiElementFactory.toPsiElement; +import static com.redhat.devtools.lsp4ij.internal.CompletableFutures.isDoneNormally; +import static com.redhat.devtools.lsp4ij.internal.CompletableFutures.waitUntilDone; public class LSPGotoDeclarationHandler implements GotoDeclarationHandler { private static final Logger LOGGER = LoggerFactory.getLogger(LSPGotoDeclarationHandler.class); @@ -43,7 +51,8 @@ public class LSPGotoDeclarationHandler implements GotoDeclarationHandler { @Nullable @Override public PsiElement[] getGotoDeclarationTargets(@Nullable PsiElement sourceElement, int offset, Editor editor) { - if (!LanguageServersRegistry.getInstance().isFileSupported(sourceElement.getContainingFile())) { + PsiFile psiFile = sourceElement.getContainingFile(); + if (!LanguageServersRegistry.getInstance().isFileSupported(psiFile)) { return null; } VirtualFile file = LSPIJUtils.getFile(sourceElement); @@ -51,55 +60,74 @@ public PsiElement[] getGotoDeclarationTargets(@Nullable PsiElement sourceElement return PsiElement.EMPTY_ARRAY; } Document document = editor.getDocument(); - DefinitionParams params = new DefinitionParams(LSPIJUtils.toTextDocumentIdentifier(file), LSPIJUtils.toPosition(offset, document)); - Set targets = new HashSet<>(); - final CancellationSupport cancellationSupport = new CancellationSupport(); + + LSPDefinitionSupport definitionSupport = LSPFileSupport.getSupport(psiFile).getDefinitionSupport(); + var params = new LSPDefinitionParams(LSPIJUtils.toTextDocumentIdentifier(file), LSPIJUtils.toPosition(offset, document), offset); + CompletableFuture> definitionsFuture = definitionSupport.getDefinitions(params); try { - Project project = editor.getProject(); - LanguageServiceAccessor.getInstance(project) - .getLanguageServers(file, LanguageServerItem::isDefinitionSupported) - .thenComposeAsync(languageServers -> - cancellationSupport.execute( - CompletableFuture.allOf( - languageServers - .stream() - .map(server -> - cancellationSupport.execute(server - .getTextDocumentService() - .definition(params), server, "Definition") - .thenAcceptAsync(definitions -> targets.addAll(toElements(project, definitions)))) - .toArray(CompletableFuture[]::new)))) - .get(1_000, TimeUnit.MILLISECONDS); - } catch (ResponseErrorException | ExecutionException | CancellationException e) { - // do not report error if the server has cancelled the request - if (!CancellationUtil.isRequestCancelledException(e)) { - LOGGER.warn(e.getLocalizedMessage(), e); + waitUntilDone(definitionsFuture, psiFile); + } catch (ProcessCanceledException ex) { + // cancel the LSP requests textDocument/definition + definitionSupport.cancel(); + } catch (CancellationException ex) { + // cancel the LSP requests textDocument/definition + definitionSupport.cancel(); + } catch (ExecutionException e) { + LOGGER.error("Error while consuming LSP 'textDocument/definition' request", e); + } + + if (isDoneNormally(definitionsFuture)) { + // textDocument/definition has been collected correctly + List definitions = definitionsFuture.getNow(null); + if (definitions != null && !definitions.isEmpty()) { + if (definitions.size() == 1) { + Location location = definitions.get(0); + if (LSPIJUtils.findResourceFor(location.getUri()).equals(file) + && Ranges.containsPosition(location.getRange(), params.getPosition()) ) { + ApplicationManager.getApplication().invokeLater(() -> { + openReference(offset, psiFile, file, document, editor); + }); + return PsiElement.EMPTY_ARRAY; + } + } + Project project = editor.getProject(); + List targets = definitions + .stream() + .map(location -> toPsiElement(location, project)) + .filter(Objects::nonNull) + .toList(); + return targets.toArray(new PsiElement[targets.size()]); } - } catch (TimeoutException e) { - LOGGER.warn(e.getLocalizedMessage(), e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - LOGGER.warn(e.getLocalizedMessage(), e); } - return targets.toArray(new PsiElement[targets.size()]); + return PsiElement.EMPTY_ARRAY; } - private static List toElements(Project project, Either, List> definitions) { - if (definitions == null) { - return Collections.emptyList(); + private static void openReference(int offset, PsiFile psiFile, VirtualFile file, Document document, Editor editor) { + LSPReferenceSupport referenceSupport = LSPFileSupport.getSupport(psiFile).getReferenceSupport(); + var params = new LSPReferenceParams(LSPIJUtils.toTextDocumentIdentifier(file), LSPIJUtils.toPosition(offset, document), offset); + CompletableFuture> referencesFuture = referenceSupport.getReferences(params); + try { + waitUntilDone(referencesFuture, psiFile); + } catch (ProcessCanceledException ex) { + // cancel the LSP requests textDocument/reference + referenceSupport.cancel(); + } catch (CancellationException ex) { + // cancel the LSP requests textDocument/reference + referenceSupport.cancel(); + } catch (ExecutionException e) { + LOGGER.error("Error while consuming LSP 'textDocument/reference' request", e); } - if (definitions.isLeft()) { - return definitions.getLeft() - .stream() - .map(location -> toPsiElement(location, project)) - .filter(Objects::nonNull) - .toList(); + + if (isDoneNormally(referencesFuture)) { + // textDocument/reference has been collected correctly + List references = referencesFuture.getNow(null); + if (references != null && !references.isEmpty()) { + + // Call "Find Usages" in popup mode. + LSPUsagesManager.getInstance(psiFile.getProject()) + .findShowUsagesInPopup(references, LSPUsageType.References, editor, null); + + } } - return definitions.getRight() - .stream() - .map(location -> toPsiElement(location, project)) - .filter(Objects::nonNull) - .toList(); } - } diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/references/LSPReferenceSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/features/references/LSPReferenceSupport.java index ec03d8282..eb0390220 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/references/LSPReferenceSupport.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/references/LSPReferenceSupport.java @@ -13,7 +13,6 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; -import com.redhat.devtools.lsp4ij.LSPIJUtils; import com.redhat.devtools.lsp4ij.LSPRequestConstants; import com.redhat.devtools.lsp4ij.LanguageServerItem; import com.redhat.devtools.lsp4ij.LanguageServiceAccessor; @@ -61,7 +60,6 @@ protected CompletableFuture> doLoad(LSPReferenceParams params, Ca @NotNull Project project, @NotNull LSPReferenceParams params, @NotNull CancellationSupport cancellationSupport) { - var textDocumentIdentifier = LSPIJUtils.toTextDocumentIdentifier(file); return LanguageServiceAccessor.getInstance(project) .getLanguageServers(file, LanguageServerItem::isReferencesSupported) .thenComposeAsync(languageServers -> { diff --git a/src/main/java/com/redhat/devtools/lsp4ij/features/typeDefinition/LSPTypeDefinitionSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/features/typeDefinition/LSPTypeDefinitionSupport.java index c6fa4e19b..573230952 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/features/typeDefinition/LSPTypeDefinitionSupport.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/features/typeDefinition/LSPTypeDefinitionSupport.java @@ -13,7 +13,6 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; -import com.redhat.devtools.lsp4ij.LSPIJUtils; import com.redhat.devtools.lsp4ij.LSPRequestConstants; import com.redhat.devtools.lsp4ij.LanguageServerItem; import com.redhat.devtools.lsp4ij.LanguageServiceAccessor; @@ -61,7 +60,6 @@ protected CompletableFuture> doLoad(LSPTypeDefinitionParams param @NotNull Project project, @NotNull LSPTypeDefinitionParams params, @NotNull CancellationSupport cancellationSupport) { - var textDocumentIdentifier = LSPIJUtils.toTextDocumentIdentifier(file); return LanguageServiceAccessor.getInstance(project) .getLanguageServers(file, LanguageServerItem::isTypeDefinitionSupported) .thenComposeAsync(languageServers -> { diff --git a/src/main/java/com/redhat/devtools/lsp4ij/usages/LSPUsagesManager.java b/src/main/java/com/redhat/devtools/lsp4ij/usages/LSPUsagesManager.java index bbbf57d8b..fab9f6902 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/usages/LSPUsagesManager.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/usages/LSPUsagesManager.java @@ -54,13 +54,25 @@ private LSPUsagesManager(@NotNull Project project) { // Show references, implementation, etc in Popup + public void findShowUsagesInPopup(@NotNull List locations, @NotNull LSPUsageType usageType, @NotNull DataContext dataContext, @Nullable MouseEvent event) { + @Nullable Editor editor = dataContext.getData(CommonDataKeys.EDITOR); + if(editor == null) { + return; + } + findShowUsagesInPopup(locations, usageType, editor,event); + } + + public void findShowUsagesInPopup(@NotNull List locations, + @NotNull LSPUsageType usageType, + @NotNull Editor editor, + @Nullable MouseEvent event) { switch (locations.size()) { case 0: { - showNoUsage(usageType, dataContext); + showNoUsage(usageType, editor); break; } case 1: { @@ -71,7 +83,6 @@ public void findShowUsagesInPopup(@NotNull List locations, } default: { // Open locations in a Popup - @Nullable Editor editor = dataContext.getData(CommonDataKeys.EDITOR); Location ref = locations.get(0); LSPUsageTriggeredPsiElement element = toUsageTriggeredPsiElement(ref, project); if(element != null) { @@ -110,8 +121,7 @@ public static LSPUsageTriggeredPsiElement toUsageTriggeredPsiElement(@NotNull Lo return LSPPsiElementFactory.toPsiElement(location, project, USAGE_TRIGGERED_ELEMENT_FACTORY); } - private void showNoUsage(@NotNull LSPUsageType usageType, DataContext dataContext) { - @Nullable Editor editor = dataContext.getData(CommonDataKeys.EDITOR); + private void showNoUsage(@NotNull LSPUsageType usageType, @NotNull Editor editor) { @NotNull String key = getNoUsageMessageKey(usageType); String message = LanguageServerBundle.message(key); if (editor == null) {