Use SearchEngine.getSearchParticipants() for non-Java language search#3772
Open
arcivanov wants to merge 1 commit into
Open
Use SearchEngine.getSearchParticipants() for non-Java language search#3772arcivanov wants to merge 1 commit into
arcivanov wants to merge 1 commit into
Conversation
6c62dc2 to
76d2bf7
Compare
SearchEngine.search() already supports multiple SearchParticipant instances, but all call sites in jdtls hardcode a single-element array containing only the default Java participant. This prevents non-Java languages from contributing index entries for cross-language call hierarchy, find references, type hierarchy, workspace symbol, and implementation search. This change updates all four search call sites (ReferencesHandler, CodeLensHandler, ImplementationCollector, HoverInfoProvider) to use the new JDT Core API SearchEngine.getSearchParticipants(), which returns the default participant plus any participants contributed via the org.eclipse.jdt.core.derivedSourceSearchParticipant extension point. WorkspaceSymbolHandler: supplement searchAllTypeNames() results with a search() call through contributed participants. searchAllTypeNames() only queries the default (Java) participant's indexes, making non-Java types invisible to workspace/symbol queries. The supplementary search creates a TYPE DECLARATIONS pattern with the same match rule (camelcase or wildcard) and runs it through contributed participants only, converting SearchMatch results to SymbolInformation with proper location, container, and kind. ReferencesHandler: accept A_INACCURATE matches for contributed (non-Java) elements, since JDT's MatchLocator cannot resolve type bindings for types not compiled by ECJ. TypeHierarchyHandler: supplement JDT's native type hierarchy with contributed search participants. For subtypes, run a supplementary IMPLEMENTORS search via contributed participants to discover non-Java types that extend or implement the target type. For supertypes of contributed types, resolve declared supertype names via type declaration search since JDT's newSupertypeHierarchy() does not work for non-Java IType implementations. Re-resolve contributed elements via codeSelect when JavaCore.create() cannot reconstitute them from handle identifiers. ImplementationCollector.findTypeImplementations(): supplement JDT's newTypeHierarchy().getAllSubtypes() with an IMPLEMENTORS search through contributed participants to discover non-Java types that implement the target interface or extend the target class. HoverInfoProvider: use DerivedSourceSearchParticipantRegistry.getLanguageId() to tag hover MarkedString content with the correct LSP language identifier (e.g. "kotlin" instead of "java") for elements from contributed search participants. JDTUtils.resolveCompilationUnit(IFile) now falls back to querying contributed DerivedSourceSearchParticipant.getCompilationUnit(IFile) for non-Java source files, enabling document symbols, hover, go-to-definition, call hierarchy, and code lenses for derived source languages (e.g., Kotlin .kt files). JDTUtils.findElementsAtSelection() now falls back to searching contributed participants when Java's codeSelect() returns empty. This enables go-to-definition and hover for types and methods provided by non-Java languages (e.g., Kotlin facade classes and property accessors) from Java source files. NavigateToDefinitionHandler.computeBreakContinue() now guarded with isJavaLikeFileName check to prevent ClassCastException when ASTParser attempts to cast a contributed ICompilationUnit (e.g. KotlinCompilationUnit) to ECJ's internal ICompilationUnit interface. NavigateToDeclarationHandler: split the non-METHOD element check so that non-Java files (e.g. .kt) fall back to computeDefinitionNavigation() for TYPE and FIELD elements instead of returning null. Java files preserve existing behavior (only METHOD elements proceed to declaration search). NavigateToTypeDefinitionHandler: add fallback for non-Java files when resolveElementType() returns null. For contributed types where the declaring type has no Java counterpart, the type signature cannot be resolved via IType.resolveType(). The fallback delegates to computeDefinitionNavigation(element) to navigate to the element itself. BaseJDTLanguageServer.computeAsync() and JDTLanguageServer .computeAsyncWithClientProgress() now log unhandled exceptions via whenComplete() instead of silently swallowing them, making failures in LSP request handlers visible in the server log. When no extensions are registered, behavior is identical to before (only the default Java participant is used), ensuring zero regression risk. SearchUtils.getContributedSearchParticipants() centralizes the "skip default participant at index 0" logic used by JDTUtils, WorkspaceSymbolHandler, TypeHierarchyHandler, and ImplementationCollector. The previous identity comparison against getDefaultSearchParticipant() never filtered anything since it creates a new instance per call. WorkspaceSymbolHandler: extract ContributedSearchRequestor to deduplicate two near-identical inline anonymous SearchRequestors for type and method contributed search. TypeHierarchyHandler: hoist getSearchParticipants() call in resolveContributedSupertypes to avoid N+1 array allocations. DerivedSourceSearchParticipantsTest expanded to 14 tests covering workspace symbol deduplication, hover language ID, findElementsAtSelection fallback, type hierarchy deduplication, and definition navigation.
76d2bf7 to
67c3a2f
Compare
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ReferencesHandler,CodeLensHandler,ImplementationCollector,HoverInfoProvider,WorkspaceSymbolHandler) to useSearchEngine.getSearchParticipants()instead of hardcoding only the default Java participantWorkspaceSymbolHandler.searchAllTypeNames()andsearchAllMethodNames()withsearch()calls through contributed participants — both APIs only query the default Java participant's indexes, making non-Java types and methods invisible to workspace/symbol queriesImplementationCollector.findTypeImplementations()with an IMPLEMENTORS search through contributed participants —newTypeHierarchy().getAllSubtypes()only discovers Java subtypesJDTUtils.resolveCompilationUnit(IFile)to query contributedSearchParticipant.getCompilationUnit(IFile)for non-Java source filesJDTUtils.findElementsAtSelection()to search contributed participants when Java'scodeSelect()returns empty — enables go-to-definition and hover for types/methods provided by non-Java languages (e.g., Kotlin facade classes and property accessors) from Java source filesReferencesHandlerwhen the search target itself is a non-Java element (contributed participants may not provide full accuracy information)SearchParticipantRegistryinstead of hardcoding"java"NavigateToDefinitionHandler.computeBreakContinue()withisJavaLikeFileNameto preventClassCastExceptionwhen ECJ'sASTParserattempts to parse a contributedICompilationUnit(e.g., Kotlin)NavigateToDeclarationHandlerto fall back to definition for non-METHOD elements (TYPE, FIELD) from non-Java files instead of returning null — enablestextDocument/declarationfor contributed languagesNavigateToTypeDefinitionHandlerto fall back to definition whenresolveElementType()returns null for non-Java files — enablestextDocument/typeDefinitionfor contributed languages whose declaring types have no Java counterpartBaseJDTLanguageServer.computeAsync()andJDTLanguageServer.computeAsyncWithClientProgress()instead of silently swallowing themSearchUtils.getContributedSearchParticipants()to centralize the "skip default participant at index 0" logic, fixing a bug where identity comparison againstgetDefaultSearchParticipant()never filtered anything (it creates a new instance per call)ContributedSearchRequestorinWorkspaceSymbolHandlerto deduplicate two near-identical inline anonymousSearchRequestorimplementationsMotivation
SearchEngine.search()already supports multipleSearchParticipantinstances, but all call sites in jdtls hardcode a single-element array containing only the default Java participant. This prevents non-Java languages from contributing search results for cross-language features.With the new
org.eclipse.jdt.core.searchParticipantextension point (eclipse-jdt/eclipse.jdt.core#4938), languages like Kotlin can register search participants that index.ktfiles and contribute to JDT's search infrastructure. This PR wires jdtls to use those contributed participants.Changes
1. Search call sites →
getSearchParticipants()ReferencesHandler,CodeLensHandler,ImplementationCollector, andHoverInfoProvider.isResolved()now callSearchEngine.getSearchParticipants()which returns[default, ...contributed].2.
WorkspaceSymbolHandler— contributed participant supplementationsearchAllTypeNames()andsearchAllMethodNames()only query indexes through the default Java search participant (seeBasicSearchEngineline 1987:getDefaultSearchParticipant(), // Java search only). Non-Java types and methods indexed by contributed participants are invisible to these APIs.After both the existing
searchAllTypeNamesandsearchAllMethodNamescalls, supplementarysearch()calls are now run through contributed participants only (excluding the default). They create TYPE/METHOD DECLARATIONS patterns with the same match rule (camelcase or wildcard) used by the workspace symbol query, and convertSearchMatchresults toSymbolInformationwith proper location, container name, and symbol kind. Both searches share a singleContributedSearchRequestorclass that handles bothITypeandIMethodelements viaIMember.3.
ImplementationCollector— contributed participant supplementationfindTypeImplementations()usestype.newTypeHierarchy().getAllSubtypes()which only discovers Java subtypes. For non-Java types (e.g., a Kotlin class implementing a Java/Kotlin interface), the type hierarchy is empty. After the standard hierarchy lookup, a supplementary IMPLEMENTORS search is now run through contributed participants (viaSearchUtils.getContributedSearchParticipants()) to discover non-Java implementations. Results are deduplicated by FQN against the types already found by the hierarchy.4.
SearchUtils.getContributedSearchParticipants()Centralizes the logic of obtaining contributed (non-default) search participants.
SearchEngine.getSearchParticipants()returns the default Java participant at index 0 followed by contributed participants; this utility returns only the contributed portion. Previously, call sites used identity comparison againstgetDefaultSearchParticipant()which never worked correctly —getDefaultSearchParticipant()creates a newJavaSearchParticipantinstance per call, so!=was always true and the default participant was never filtered out, causing duplicate results.5.
ReferencesHandler— accept inaccurate matches for non-Java elementsWhen the search target is a non-Java element (resolved via a contributed participant),
ReferencesHandlernow accepts inaccurate search matches. Contributed participants may not provide full accuracy information, and filtering these out would silently drop valid cross-language references.6.
resolveCompilationUnit(IFile)fallbackWhen a non-Java file (e.g.,
.kt) is opened, the method now queries contributed participants viagetCompilationUnit(IFile)as a fallback after the standard Java resolution path. This enables document symbols, hover, go-to-definition, call hierarchy, and code lenses for derived source languages.7.
findElementsAtSelection()search participant fallbackWhen Java's
codeSelect()returns empty (which happens for types and methods provided by non-Java languages), a newresolveViaSearchParticipants()helper extracts the identifier at the cursor and searches contributed participants for matching TYPE or METHOD declarations. This fixes Java→Kotlin navigation gaps (go-to-definition and hover on Kotlin facade classes and property accessors from Java files).8. Type hierarchy support for contributed participants
TypeHierarchyHandlercannot reconstitute aIJavaElementfrom its handle identifier (which happens for contributed elements), it falls back tocodeSelect()at the element's source position to re-resolve itresolveContributedSupertypes): For non-Java types, JDT'snewSupertypeHierarchy()does not work. Instead, the superclass and superinterface names are resolved viaSearchEnginesearch across all participants (hoisted to avoid N+1 array allocations), building the hierarchy items from the resolvedITypeinstancessupplementWithContributedSubtypes): After JDT computes subtypes via its own hierarchy, a secondary IMPLEMENTORS search is run against contributed participants only (viaSearchUtils.getContributedSearchParticipants()) to discover additional subtypes from non-Java languages9. Correct language ID for hover MarkedStrings
HoverInfoProvider.getLanguageId()queriesSearchParticipantRegistryfor the file extension and its associated LSP language ID, falling back to"java"for standard Java elements. This ensures hover popups render syntax highlighting for the correct language.10.
NavigateToDefinitionHandler— guard ECJ AST parsing for non-Java filescomputeBreakContinue()usesASTParser.createAST()to parse break/continue statements, which internally casts theICompilationUnitto ECJ'sorg.eclipse.jdt.internal.compiler.env.ICompilationUnit. Contributed compilation units (e.g.,KotlinCompilationUnit) do not implement this internal interface, causing aClassCastException. AddedJavaCore.isJavaLikeFileName()guard to skip break/continue resolution for non-Java files.11.
NavigateToDeclarationHandler— fallback for non-Java filestextDocument/declarationonly made sense for METHOD elements (finding the declaring method in the type hierarchy). For non-Java files, TYPE and FIELD elements were rejected before reaching the non-Java fallback. The check is now split: non-METHOD elements from non-Java files fall back tocomputeDefinitionNavigation(), while Java files preserve existing behavior (return null for non-METHOD elements).12.
NavigateToTypeDefinitionHandler— fallback for non-Java filestextDocument/typeDefinitionusesresolveElementType()which callsIType.resolveType(typeName)on the declaring type. For contributed types whose declaring type is a pure non-Java type with no Java counterpart,resolveType()returns null. Added fallback: when type resolution fails and the file is non-Java, delegate tocomputeDefinitionNavigation(element)to navigate to the element itself rather than returning nothing.13. Log unhandled exceptions in
computeAsync()BaseJDTLanguageServer.computeAsync()andJDTLanguageServer.computeAsyncWithClientProgress()now attach awhenComplete()handler that logs unhandled exceptions instead of silently swallowing them. This makes failures in LSP request handlers visible in the server log for diagnosis.Backward Compatibility
When no search participant extensions are registered,
getSearchParticipants()returns only the default Java participant, and the fallback paths only run when standard JDT resolution already returned empty. Behavior is identical to before.Test plan
SearchParticipantsTest(14 tests),WorkspaceSymbolHandlerTest(17 tests), andTypeHierarchyHandlerTest(4 tests) all pass. Tests verify:getSearchParticipants()API consistency (includes default, correct class, stable across calls)getCompilationUnit()returns nullresolveCompilationUnitfallback loop runs without error for non-Java files (returns null when no contributed participant is registered)resolveCompilationUnitstill works for Java filesMarkedStringlanguage ID is"java"for Java elementsfindElementsAtSelectionresolves Java elements and returns empty for whitespacecomputeBreakContinue)WorkspaceSymbolHandlerTestandTypeHierarchyHandlerTesttests pass (no regressions)