1212
1313import com .intellij .find .findUsages .CustomUsageSearcher ;
1414import com .intellij .find .findUsages .FindUsagesOptions ;
15+ import com .intellij .openapi .application .ApplicationManager ;
1516import com .intellij .openapi .editor .Document ;
1617import com .intellij .openapi .progress .ProcessCanceledException ;
1718import com .intellij .openapi .project .Project ;
2627import com .intellij .util .Processor ;
2728import com .intellij .util .containers .ContainerUtil ;
2829import com .redhat .devtools .lsp4ij .LSPIJUtils ;
29- import com .redhat .devtools .lsp4ij .LanguageServiceAccessor ;
3030import org .eclipse .lsp4j .Position ;
3131import org .jetbrains .annotations .NotNull ;
3232import org .jetbrains .annotations .Nullable ;
@@ -77,6 +77,9 @@ public class LSPUsageSearcher extends CustomUsageSearcher {
7777
7878 private static final Logger LOGGER = LoggerFactory .getLogger (LSPUsageSearcher .class );
7979
80+ record ElementContext (Project project , Position position , PsiFile file ) {
81+ }
82+
8083 /**
8184 * Processes all usages of the given element by querying the language server.
8285 * <p>
@@ -103,100 +106,105 @@ public class LSPUsageSearcher extends CustomUsageSearcher {
103106 * separate results.
104107 * </p>
105108 *
106- * @param element the PSI element to find usages for (typically an {@link LSPUsageTriggeredPsiElement}).
109+ * @param element the PSI element to find usages for (typically an {@link LSPUsageTriggeredPsiElement}).
107110 * @param processor the processor to handle each found usage.
108- * @param options the Find Usages options, including the search scope.
111+ * @param options the Find Usages options, including the search scope.
109112 */
110113 @ Override
111114 public void processElementUsages (@ NotNull PsiElement element , @ NotNull Processor <? super Usage > processor , @ NotNull FindUsagesOptions options ) {
112- // 1. Collect only what we need from the element inside a narrow ReadAction
113- Project project = element .getProject ();
114- Position position = runCancellableReadAction (() -> getPosition (element , element .getContainingFile ()), project );
115- PsiFile file = runCancellableReadAction (() -> element .getContainingFile (), project );
116-
117- if (position == null || file == null ) {
118- return ;
119- }
120- if (!isUsageSupportedByLanguageServer (file )) {
121- return ;
122- }
115+ // 1. Collect only what we need from the element inside a narrow ReadAction
116+ ElementContext ctx = runCancellableReadAction (() -> {
117+ PsiFile file = element .getContainingFile ();
118+ return new ElementContext (file .getProject (), getPosition (element , file ), file );
119+ }, ApplicationManager .getApplication ());
120+ Project project = ctx .project ();
121+ Position position = ctx .position ();
122+ PsiFile file = ctx .file ();
123+
124+
125+ if (position == null || file == null ) {
126+ return ;
127+ }
128+ if (!isUsageSupportedByLanguageServer (file )) {
129+ return ;
130+ }
123131
124- SearchScope searchScope = options .searchScope ;
125-
126- // Fast path: return cached references if available (avoids redundant LSP requests)
127- if (element instanceof LSPUsageTriggeredPsiElement elt ) {
128- if (elt .getLSPReferences () != null ) {
129- elt .getLSPReferences ()
130- .forEach (ref -> {
131- runCancellableReadAction (() -> {
132- var psiElement = LSPUsagesManager .toPsiElement (ref .location (), ref .languageServer ().getClientFeatures (), LSPUsagePsiElement .UsageKind .references , project );
133- if (psiElement != null ) {
134- VirtualFile psiElementFile = LSPIJUtils .getFile (psiElement );
135- if (psiElementFile != null ) {
136- processor .process (new UsageInfo2UsageAdapter (new UsageInfo (psiElement )));
137- }
132+ SearchScope searchScope = options .searchScope ;
133+
134+ // Fast path: return cached references if available (avoids redundant LSP requests)
135+ if (element instanceof LSPUsageTriggeredPsiElement elt ) {
136+ if (elt .getLSPReferences () != null ) {
137+ elt .getLSPReferences ()
138+ .forEach (ref -> {
139+ runCancellableReadAction (() -> {
140+ var psiElement = LSPUsagesManager .toPsiElement (ref .location (), ref .languageServer ().getClientFeatures (), LSPUsagePsiElement .UsageKind .references , project );
141+ if (psiElement != null ) {
142+ VirtualFile psiElementFile = LSPIJUtils .getFile (psiElement );
143+ if (psiElementFile != null ) {
144+ processor .process (new UsageInfo2UsageAdapter (new UsageInfo (psiElement )));
138145 }
139- }, project );
140- });
141- return ;
142- }
146+ }
147+ }, project );
148+ }) ;
149+ return ;
143150 }
151+ }
144152
145- // 2. Dispatch the async LSP request OUTSIDE of the ReadAction
146- LSPUsageSupport usageSupport = new LSPUsageSupport (file );
147- LSPUsageSupport .LSPUsageSupportParams params = new LSPUsageSupport .LSPUsageSupportParams (position );
148- CompletableFuture <List <LSPUsagePsiElement >> usagesFuture = usageSupport .getFeatureData (params );
149- try {
150- // 3. Wait for the future OUTSIDE of the ReadAction
151- // This allows the ForkJoinPool background threads to freely acquire ReadActions
152- // to resolve the PSI elements during mapping!
153- waitUntilDone (usagesFuture );
154- if (usagesFuture .isDone ()) {
155- List <LSPUsagePsiElement > usages = usagesFuture .getNow (null );
156- if (usages != null && !usages .isEmpty ()) {
157- // 4. Process the results (requires ReadAction again for VFS/PSI mapping)
158- runCancellableReadAction (() -> {
159- List <LSPUsagePsiElement > filteredUsages = new ArrayList <>(usages );
160- // Remove invalid usages and deduplicate overlapping ranges
161- filteredUsages .removeIf (usage -> ContainerUtil .exists (usages , otherUsage -> {
162- VirtualFile usageFile = LSPIJUtils .getFile (usage );
163- if (usageFile == null ) {
164- return true ;
165- }
166- // TODO: respect search scope - currently disabled
167- // if (!searchScope.contains(usageFile)) return true;
168-
169- // Remove usages that fully contain other usages (keep the more specific one)
170- if (usage != otherUsage ) {
171- TextRange textRange = usage .getTextRange ();
172- TextRange otherTextRange = otherUsage .getTextRange ();
173- return (textRange != null ) &&
174- (otherTextRange != null ) &&
175- textRange .contains (otherTextRange ) &&
176- !textRange .equals (otherTextRange );
177- }
153+ // 2. Dispatch the async LSP request OUTSIDE of the ReadAction
154+ LSPUsageSupport usageSupport = new LSPUsageSupport (file );
155+ LSPUsageSupport .LSPUsageSupportParams params = new LSPUsageSupport .LSPUsageSupportParams (position );
156+ CompletableFuture <List <LSPUsagePsiElement >> usagesFuture = usageSupport .getFeatureData (params );
157+ try {
158+ // 3. Wait for the future OUTSIDE of the ReadAction
159+ // This allows the ForkJoinPool background threads to freely acquire ReadActions
160+ // to resolve the PSI elements during mapping!
161+ waitUntilDone (usagesFuture );
162+ if (usagesFuture .isDone ()) {
163+ List <LSPUsagePsiElement > usages = usagesFuture .getNow (null );
164+ if (usages != null && !usages .isEmpty ()) {
165+ // 4. Process the results (requires ReadAction again for VFS/PSI mapping)
166+ runCancellableReadAction (() -> {
167+ List <LSPUsagePsiElement > filteredUsages = new ArrayList <>(usages );
168+ // Remove invalid usages and deduplicate overlapping ranges
169+ filteredUsages .removeIf (usage -> ContainerUtil .exists (usages , otherUsage -> {
170+ VirtualFile usageFile = LSPIJUtils .getFile (usage );
171+ if (usageFile == null ) {
172+ return true ;
173+ }
174+ // TODO: respect search scope - currently disabled
175+ // if (!searchScope.contains(usageFile)) return true;
176+
177+ // Remove usages that fully contain other usages (keep the more specific one)
178+ if (usage != otherUsage ) {
179+ TextRange textRange = usage .getTextRange ();
180+ TextRange otherTextRange = otherUsage .getTextRange ();
181+ return (textRange != null ) &&
182+ (otherTextRange != null ) &&
183+ textRange .contains (otherTextRange ) &&
184+ !textRange .equals (otherTextRange );
185+ }
178186
179- return false ;
180- }));
187+ return false ;
188+ }));
181189
182- for (LSPUsagePsiElement usage : filteredUsages ) {
183- processor .process (new UsageInfo2UsageAdapter (new UsageInfo (usage )));
184- }
185- }, project );
186- }
190+ for (LSPUsagePsiElement usage : filteredUsages ) {
191+ processor .process (new UsageInfo2UsageAdapter (new UsageInfo (usage )));
192+ }
193+ }, project );
187194 }
188- } catch (ProcessCanceledException pce ) {
189- throw pce ;
190- } catch (Exception e ) {
191- LOGGER .error ("Error while collection LSP Usages" , e );
192195 }
196+ } catch (ProcessCanceledException pce ) {
197+ throw pce ;
198+ } catch (Exception e ) {
199+ LOGGER .error ("Error while collection LSP Usages" , e );
200+ }
193201
194- LSPExternalReferencesFinder .processExternalReferences (
195- file ,
196- element .getTextOffset (),
197- searchScope ,
198- reference -> processor .process (new UsageInfo2UsageAdapter (new UsageInfo (reference )))
199- );
202+ LSPExternalReferencesFinder .processExternalReferences (
203+ file ,
204+ element .getTextOffset (),
205+ searchScope ,
206+ reference -> processor .process (new UsageInfo2UsageAdapter (new UsageInfo (reference )))
207+ );
200208 }
201209
202210 /**
0 commit comments