Skip to content

Commit d4d186d

Browse files
angelozerrfbricon
authored andcommitted
fix: Enable Find Usages menu for custom file type different from TEXT
and textmate Fixes #352 Signed-off-by: azerr <[email protected]>
1 parent b7ca462 commit d4d186d

File tree

6 files changed

+214
-23
lines changed

6 files changed

+214
-23
lines changed

src/main/java/com/redhat/devtools/lsp4ij/LanguageServersRegistry.java

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import com.intellij.codeInsight.hints.NoSettings;
1414
import com.intellij.codeInsight.hints.ProviderInfo;
1515
import com.intellij.lang.Language;
16+
import com.intellij.lang.findUsages.EmptyFindUsagesProvider;
17+
import com.intellij.lang.findUsages.LanguageFindUsages;
1618
import com.intellij.openapi.application.ApplicationManager;
1719
import com.intellij.openapi.fileTypes.FileNameMatcher;
1820
import com.intellij.openapi.fileTypes.FileType;
@@ -30,6 +32,7 @@
3032
import com.redhat.devtools.lsp4ij.server.definition.*;
3133
import com.redhat.devtools.lsp4ij.server.definition.extension.*;
3234
import com.redhat.devtools.lsp4ij.server.definition.launching.UserDefinedLanguageServerDefinition;
35+
import com.redhat.devtools.lsp4ij.usages.LSPFindUsagesProvider;
3336
import org.jetbrains.annotations.NotNull;
3437
import org.jetbrains.annotations.Nullable;
3538
import org.slf4j.Logger;
@@ -69,7 +72,7 @@ private void initialize() {
6972
// Load language servers / mappings from user settings
7073
loadServersAndMappingFromSettings();
7174

72-
updateInlayHintsProviders();
75+
updateLanguages();
7376
}
7477

7578
private void loadServersAndMappingsFromExtensionPoint() {
@@ -145,9 +148,8 @@ private void loadServersAndMappingFromSettings() {
145148
}
146149
}
147150

148-
private void updateInlayHintsProviders() {
149-
// register LSPInlayHintInlayHintsProvider + LSPCodelensInlayHintsProvider automatically for all languages
150-
// which are associated with a language server.
151+
private void updateLanguages() {
152+
151153
Set<Language> distinctLanguages = fileAssociations
152154
.stream()
153155
.map(LanguageServerFileAssociation::getLanguage)
@@ -164,6 +166,16 @@ private void updateInlayHintsProviders() {
164166
// the language received in InlayHintProviders is plain/text, we add it to support
165167
// LSP inlayHint, color for a file which is not linked to a language.
166168
distinctLanguages.add(PlainTextLanguage.INSTANCE);
169+
170+
// register LSPInlayHintsProvider + LSPColorProvider automatically
171+
// for all languages associated with a language server.
172+
updateInlayHintsProviders(distinctLanguages);
173+
// register LSPFindUsagesProvider automatically
174+
// for all languages associated with a language server.
175+
updateFindUsagesProvider(distinctLanguages);
176+
}
177+
178+
private void updateInlayHintsProviders(Set<Language> distinctLanguages) {
167179
LSPInlayHintsProvider lspInlayHintsProvider = new LSPInlayHintsProvider();
168180
LSPColorProvider lspColorProvider = new LSPColorProvider();
169181
inlayHintsProviders.clear();
@@ -173,6 +185,19 @@ private void updateInlayHintsProviders() {
173185
}
174186
}
175187

188+
private void updateFindUsagesProvider(Set<Language> distinctLanguages) {
189+
// Associate the LSP find usage provider
190+
// for all languages associated with a language server.
191+
// and which does not already define a provider for the language.
192+
LSPFindUsagesProvider provider = new LSPFindUsagesProvider();
193+
for (Language language : distinctLanguages) {
194+
var existingProviders = LanguageFindUsages.INSTANCE.allForLanguage(language);
195+
if (existingProviders.isEmpty() || (existingProviders.size() == 1 && existingProviders.get(0) instanceof EmptyFindUsagesProvider)) {
196+
LanguageFindUsages.INSTANCE.addExplicitExtension(language, provider);
197+
}
198+
}
199+
}
200+
176201
private static String getServerNotAvailableMessage(ServerMapping mapping) {
177202
StringBuilder message = new StringBuilder("server '");
178203
message.append(mapping.getServerId());
@@ -260,7 +285,7 @@ public Collection<LanguageServerDefinition> getServerDefinitions() {
260285
public void addServerDefinition(@NotNull Project project, @NotNull LanguageServerDefinition serverDefinition, @Nullable List<ServerMappingSettings> mappings) {
261286
String languageServerId = serverDefinition.getId();
262287
addServerDefinitionWithoutNotification(serverDefinition, toServerMappings(languageServerId, mappings));
263-
updateInlayHintsProviders();
288+
updateLanguages();
264289
LanguageServerDefinitionListener.LanguageServerAddedEvent event = new LanguageServerDefinitionListener.LanguageServerAddedEvent(project, Collections.singleton(serverDefinition));
265290
for (LanguageServerDefinitionListener listener : this.listeners) {
266291
try {
@@ -363,7 +388,7 @@ public void removeServerDefinition(@NotNull Project project, @NotNull LanguageSe
363388
// remove associations
364389
removeAssociationsFor(serverDefinition);
365390

366-
updateInlayHintsProviders();
391+
updateLanguages();
367392
// Update settings
368393
UserDefinedLanguageServerSettings.getInstance().removeServerDefinition(languageServerId);
369394
// Notifications
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Red Hat, Inc.
3+
* Distributed under license by Red Hat, Inc. All rights reserved.
4+
* This program is made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution,
6+
* and is available at http://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
******************************************************************************/
11+
package com.redhat.devtools.lsp4ij.usages;
12+
13+
import com.intellij.find.actions.FindUsagesAction;
14+
import com.intellij.openapi.actionSystem.AnAction;
15+
import com.intellij.openapi.actionSystem.AnActionEvent;
16+
import com.intellij.openapi.actionSystem.CommonDataKeys;
17+
import com.intellij.openapi.actionSystem.ex.AnActionListener;
18+
import com.intellij.openapi.editor.Editor;
19+
import com.intellij.psi.PsiElement;
20+
import com.intellij.psi.PsiFile;
21+
import com.intellij.usages.UsageTargetUtil;
22+
import org.jetbrains.annotations.NotNull;
23+
24+
/**
25+
* "Find Usages" action execution tracker used to know if {@link LSPUsageTargetProvider}
26+
* must return LSP {@link com.intellij.usages.UsageTarget} or not:
27+
*
28+
* <ul>
29+
* <li>when no UsageTarget is already associated to a language, the LSP UsageTarget must be returned to enable the `Find usages` command.</li>
30+
* <li>when a UsageTarget is already associated to a language (e.g. Java in IDEA), the LSP UsageTarget must not be returned,
31+
* to avoid overriding the platform native support for `Find usages` for that language.</li>
32+
* </ul>
33+
*/
34+
public class LSPFindActionTracker implements AnActionListener {
35+
36+
@Override
37+
public void beforeActionPerformed(@NotNull AnAction action, @NotNull AnActionEvent event) {
38+
if (action instanceof FindUsagesAction) {
39+
// Before executing "Find Usages"....
40+
41+
// The Find usages can be processed if
42+
// - Psi file and editor exists
43+
// - or Psi element exists
44+
PsiFile psiFile = event.getData(CommonDataKeys.PSI_FILE);
45+
Editor editor = event.getData(CommonDataKeys.EDITOR);
46+
PsiElement psiElement = event.getData(CommonDataKeys.PSI_ELEMENT);
47+
if ((editor == null || psiFile == null) && psiElement == null) {
48+
return;
49+
}
50+
51+
// Find usages will be processed...
52+
53+
// Disable collection of LSP UsageTarget
54+
if (psiFile != null) {
55+
LSPUsageTargetProvider.setDisabled(psiFile, true);
56+
}
57+
if (psiElement != null) {
58+
LSPUsageTargetProvider.setDisabled(psiElement, true);
59+
}
60+
61+
// Collect other usage targets while the LSP Usage Target is disabled
62+
var result = UsageTargetUtil.findUsageTargets(id -> event.getDataContext().getData(id));
63+
if (result == null || result.length == 0) {
64+
// No other existing usage targets, re-enable the LSP usage target
65+
// to contribute to the `Find Usages` command
66+
if (psiFile != null) {
67+
LSPUsageTargetProvider.setDisabled(psiFile, null);
68+
}
69+
if (psiElement != null) {
70+
LSPUsageTargetProvider.setDisabled(psiElement, null);
71+
}
72+
}
73+
}
74+
}
75+
76+
}

src/main/java/com/redhat/devtools/lsp4ij/usages/LSPFindUsagesHandlerFactory.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.intellij.find.findUsages.FindUsagesHandler;
1414
import com.intellij.find.findUsages.FindUsagesHandlerFactory;
1515
import com.intellij.psi.PsiElement;
16+
import com.redhat.devtools.lsp4ij.LanguageServersRegistry;
1617
import org.jetbrains.annotations.NotNull;
1718
import org.jetbrains.annotations.Nullable;
1819

@@ -23,8 +24,8 @@ public class LSPFindUsagesHandlerFactory extends FindUsagesHandlerFactory {
2324
@Override
2425
public boolean canFindUsages(@NotNull PsiElement element) {
2526
// Execute the dummy LSP find usage handler to collect references, implementations
26-
// with LSPUsageSearcher.
27-
return element instanceof LSPUsageTriggeredPsiElement;
27+
// with LSPUsageSearcher if file is associated to a language server.
28+
return LanguageServersRegistry.getInstance().isFileSupported(element.getContainingFile());
2829
}
2930

3031
@Override
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Red Hat, Inc.
3+
* Distributed under license by Red Hat, Inc. All rights reserved.
4+
* This program is made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution,
6+
* and is available at http://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
******************************************************************************/
11+
package com.redhat.devtools.lsp4ij.usages;
12+
13+
import com.intellij.lang.findUsages.FindUsagesProvider;
14+
import com.intellij.psi.PsiElement;
15+
import com.redhat.devtools.lsp4ij.LanguageServersRegistry;
16+
import org.jetbrains.annotations.Nls;
17+
import org.jetbrains.annotations.NonNls;
18+
import org.jetbrains.annotations.NotNull;
19+
import org.jetbrains.annotations.Nullable;
20+
21+
import java.util.Set;
22+
23+
/**
24+
* Dummy class solely used to decide if the "Find Usages" menu action must be shown
25+
* (if file is associated to a language server) or hidden otherwise.
26+
*
27+
* This class is associated to the LSPFindUsagesProvider singleton when language server mappings are loaded / updated
28+
* in the {@link LanguageServersRegistry#updateFindUsagesProvider(Set)}}.
29+
*
30+
* See <a href="https://github.com/JetBrains/intellij-community/blob/e0de7cbb9e6045d682229032e2cbc47304c5310d/platform/lang-impl/src/com/intellij/find/actions/FindUsagesInFileAction.java#L111">canFindUsages</a>
31+
*/
32+
public class LSPFindUsagesProvider implements FindUsagesProvider {
33+
@Override
34+
public boolean canFindUsagesFor(@NotNull PsiElement psiElement) {
35+
return true;
36+
}
37+
38+
@Override
39+
public @Nullable @NonNls String getHelpId(@NotNull PsiElement psiElement) {
40+
return null;
41+
}
42+
43+
@Override
44+
public @Nls @NotNull String getType(@NotNull PsiElement element) {
45+
return "LSP TYPE";
46+
}
47+
48+
@Override
49+
public @Nls @NotNull String getDescriptiveName(@NotNull PsiElement element) {
50+
return "LSP description";
51+
}
52+
53+
@Override
54+
public @Nls @NotNull String getNodeText(@NotNull PsiElement element, boolean useFullName) {
55+
return "LSP node";
56+
}
57+
}

src/main/java/com/redhat/devtools/lsp4ij/usages/LSPUsageTargetProvider.java

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@
1313
import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
1414
import com.intellij.openapi.editor.Document;
1515
import com.intellij.openapi.editor.Editor;
16+
import com.intellij.openapi.util.Key;
1617
import com.intellij.openapi.util.TextRange;
1718
import com.intellij.psi.PsiElement;
1819
import com.intellij.psi.PsiFile;
1920
import com.intellij.usages.UsageTarget;
2021
import com.intellij.usages.UsageTargetProvider;
2122
import com.redhat.devtools.lsp4ij.LSPIJUtils;
2223
import com.redhat.devtools.lsp4ij.LanguageServersRegistry;
23-
import com.redhat.devtools.lsp4ij.internal.SimpleLanguageUtils;
2424
import org.jetbrains.annotations.NotNull;
2525
import org.jetbrains.annotations.Nullable;
2626

@@ -31,14 +31,36 @@
3131
*/
3232
public class LSPUsageTargetProvider implements UsageTargetProvider {
3333

34+
private static final Key<Boolean> DISABLED_KEY = Key.create("lsp.find.usages.disabled");
35+
3436
@Override
3537
public UsageTarget @Nullable [] getTargets(@NotNull Editor editor, @NotNull PsiFile file) {
36-
if (!SimpleLanguageUtils.isSupported(file.getLanguage())) {
38+
if (isDisabled(file)) {
39+
return null;
40+
}
41+
if (!LanguageServersRegistry.getInstance().isFileSupported(file)) {
42+
// The file is not associated to a language server
3743
return null;
3844
}
45+
// Get LSP usage targets
3946
return getLSPTargets(editor, file);
4047
}
4148

49+
@Override
50+
public UsageTarget @Nullable [] getTargets(@NotNull PsiElement psiElement) {
51+
if (isDisabled(psiElement)) {
52+
return null;
53+
}
54+
PsiFile file = psiElement.getContainingFile();
55+
if (!LanguageServersRegistry.getInstance().isFileSupported(file)) {
56+
// The file is not associated to a language server
57+
return null;
58+
}
59+
// Get LSP usage targets
60+
Editor editor = LSPIJUtils.editorForElement(psiElement);
61+
return editor != null ? getLSPTargets(editor, file) : null;
62+
}
63+
4264
@NotNull
4365
private static UsageTarget[] getLSPTargets(@NotNull Editor editor, @NotNull PsiFile file) {
4466
int offset = editor.getCaretModel().getOffset();
@@ -61,13 +83,20 @@ private static UsageTarget[] getLSPTargets(@NotNull Editor editor, @NotNull PsiF
6183
return new UsageTarget[]{target};
6284
}
6385

64-
@Override
65-
public UsageTarget @Nullable [] getTargets(@NotNull PsiElement psiElement) {
66-
PsiFile file = psiElement.getContainingFile();
67-
if (!LanguageServersRegistry.getInstance().isFileSupported(file)) {
68-
return null;
69-
}
70-
Editor editor = LSPIJUtils.editorForElement(psiElement);
71-
return editor != null ? getLSPTargets(editor, file) : null;
86+
private static boolean isDisabled(PsiFile file) {
87+
return file.getUserData(DISABLED_KEY) != null;
7288
}
89+
90+
public static void setDisabled(PsiFile file, Boolean disabled) {
91+
file.putUserData(DISABLED_KEY, disabled);
92+
}
93+
94+
private static boolean isDisabled(PsiElement element) {
95+
return element.getUserData(DISABLED_KEY) != null;
96+
}
97+
98+
public static void setDisabled(PsiElement element, Boolean disabled) {
99+
element.putUserData(DISABLED_KEY, disabled);
100+
}
101+
73102
}

src/main/resources/META-INF/plugin.xml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@
150150
<!-- LSP textDocument/completion request support -->
151151
<typedHandler id="LSPTypedHandlerDelegate"
152152
order="last"
153-
implementation="com.redhat.devtools.lsp4ij.features.completion.LSPTypedHandlerDelegate" />
153+
implementation="com.redhat.devtools.lsp4ij.features.completion.LSPTypedHandlerDelegate"/>
154154
<completion.contributor
155155
id="LSPCompletionContributor"
156156
language="any"
@@ -243,6 +243,7 @@
243243
<findUsagesHandlerFactory
244244
implementation="com.redhat.devtools.lsp4ij.usages.LSPFindUsagesHandlerFactory"/>
245245
<usageTypeProvider
246+
order="last"
246247
implementation="com.redhat.devtools.lsp4ij.usages.LSPUsageTypeProvider"/>
247248

248249
<!-- LSP textDocument/rename request support -->
@@ -380,15 +381,15 @@
380381

381382
<notificationGroup
382383
id="LSP/window/showMessage"
383-
displayType="BALLOON" />
384+
displayType="BALLOON"/>
384385

385386
<notificationGroup
386387
id="LSP/window/showMessageRequest"
387-
displayType="STICKY_BALLOON" />
388+
displayType="STICKY_BALLOON"/>
388389

389390
<notificationGroup
390391
id="LSP4IJ general notifications"
391-
displayType="BALLOON" />
392+
displayType="BALLOON"/>
392393
</extensions>
393394

394395
<!-- Actions to execute LSP features Find Implementation, etc -->
@@ -494,13 +495,15 @@
494495
<listener
495496
topic="com.intellij.openapi.project.ProjectManagerListener"
496497
class="com.redhat.devtools.lsp4ij.ConnectDocumentToLanguageServerSetupParticipant"/>
498+
<listener topic="com.intellij.openapi.actionSystem.ex.AnActionListener"
499+
class="com.redhat.devtools.lsp4ij.usages.LSPFindActionTracker"/>
497500
</applicationListeners>
498501

499502
<projectListeners>
500503
<listener topic="com.intellij.refactoring.listeners.RefactoringEventListener"
501504
class="com.redhat.devtools.lsp4ij.features.refactoring.LSPRefactoringListener"/>
502505
<listener topic="com.intellij.codeInsight.lookup.LookupManagerListener"
503-
class="com.redhat.devtools.lsp4ij.features.completion.LSPCompletionContributor$LSPLookupManagerListener" />
506+
class="com.redhat.devtools.lsp4ij.features.completion.LSPCompletionContributor$LSPLookupManagerListener"/>
504507
</projectListeners>
505508

506509
</idea-plugin>

0 commit comments

Comments
 (0)