Summary
Allow LSP client plugins to differentiate between explicit user-triggered actions ("Go To > LSP Reference(s)") and implicit framework-driven invocations (Ctrl+Click, Find Usages) when gating LSP features. Currently, both paths share the same gating mechanism, making it impossible to disable a feature for implicit integration while keeping the explicit "Go To" actions available.
Motivation
Use case
Consider a language server that supports textDocument/references for all languages, but the client plugin provides per-language user preferences (e.g., a "Navigation" toggle in settings). When a user disables Navigation for a specific language:
- Expected: The implicit integration (Ctrl+Click contributing to Find Usages,
GotoDeclarationHandler, etc.) should stop — the user doesn't want the LSP to participate in these automatic flows for this language.
- Expected: The explicit "Go To > LSP Reference(s)" action should still work — the user deliberately chose this action from the context menu, signaling intent to use the language server.
- Actual: Both paths are blocked because they both flow through
getLanguageServers(file, isEnabled, isSupported), and the isEnabled() filter is applied uniformly.
Why this matters
Many language server clients support languages that also have first-class IDE plugins (e.g., Java in IntelliJ, Python in PyCharm). For these languages, users typically want:
- Implicit integration disabled (to avoid conflicts with the native plugin's Ctrl+Click, Find Usages, etc.)
- Explicit LSP actions available as a fallback (e.g., "Go To > LSP References" when the native plugin's results are insufficient)
Currently, there's no way to express this distinction.
Current Architecture
Two invocation paths
Implicit (framework-driven):
Ctrl+Click → LSPGotoDeclarationHandler → LSPDefinitionSupport.getDefinitions()
→ getLanguageServers(file,
f -> f.getDefinitionFeature().isEnabled(file),
f -> f.getDefinitionFeature().isSupported(file))
Explicit (user-triggered):
"Go To > LSP References" → LSPGoToReferenceAction.canSupportAction()
→ canSupportFeature() → isSupported(file) // visibility (OK)
→ LSPGoToReferenceAction.actionPerformed()
→ LSPReferenceSupport.getReferences()
→ getLanguageServers(file,
f -> f.getReferencesFeature().isEnabled(file),
f -> f.getReferencesFeature().isSupported(file))
The problem
Both paths call getLanguageServers() with the same isEnabled() predicate. When isEnabled() returns false (because the user disabled the feature for this language):
- The explicit "Go To > LSP References" menu item is visible (because
canSupportAction() checks isSupported(), not isEnabled())
- But clicking it produces no results (because
getLanguageServers() filters out the server via isEnabled())
This is confusing UX — the action appears available but silently does nothing.
Affected features
This affects all features that have both explicit and implicit paths:
| Feature |
Explicit Action |
Implicit Integration |
| References |
LSPGoToReferenceAction |
LSPUsageSearcher, LSPFindUsagesHandlerFactory |
| Declaration |
LSPGoToDeclarationAction |
LSPGotoDeclarationHandler |
| Implementation |
LSPGoToImplementationAction |
(via Find Usages) |
| Type Definition |
LSPGoToTypeDefinitionAction |
(via Find Usages) |
Workaround
Until this is addressed, client plugins can work around the issue by always returning true from isEnabled() and moving the user-preference check into is*Supported(). However, this is suboptimal because isEnabled() serves as a pre-server-start optimization — returning true unconditionally may cause the server to be started for files where it shouldn't be.
Environment
- LSP4IJ version: latest (as of July 2025)
- Affected classes:
AbstractLSPDocumentFeature.isEnabled()
AbstractLSPDocumentFeatureSupport.getLanguageServers()
AbstractLSPGoToAction and all subclasses
LSPGotoDeclarationHandler
LSPFindUsagesHandlerFactory / LSPUsageSearcher
Summary
Allow LSP client plugins to differentiate between explicit user-triggered actions ("Go To > LSP Reference(s)") and implicit framework-driven invocations (Ctrl+Click, Find Usages) when gating LSP features. Currently, both paths share the same gating mechanism, making it impossible to disable a feature for implicit integration while keeping the explicit "Go To" actions available.
Motivation
Use case
Consider a language server that supports
textDocument/referencesfor all languages, but the client plugin provides per-language user preferences (e.g., a "Navigation" toggle in settings). When a user disables Navigation for a specific language:GotoDeclarationHandler, etc.) should stop — the user doesn't want the LSP to participate in these automatic flows for this language.getLanguageServers(file, isEnabled, isSupported), and theisEnabled()filter is applied uniformly.Why this matters
Many language server clients support languages that also have first-class IDE plugins (e.g., Java in IntelliJ, Python in PyCharm). For these languages, users typically want:
Currently, there's no way to express this distinction.
Current Architecture
Two invocation paths
Implicit (framework-driven):
Explicit (user-triggered):
The problem
Both paths call
getLanguageServers()with the sameisEnabled()predicate. WhenisEnabled()returnsfalse(because the user disabled the feature for this language):canSupportAction()checksisSupported(), notisEnabled())getLanguageServers()filters out the server viaisEnabled())This is confusing UX — the action appears available but silently does nothing.
Affected features
This affects all features that have both explicit and implicit paths:
LSPGoToReferenceActionLSPUsageSearcher,LSPFindUsagesHandlerFactoryLSPGoToDeclarationActionLSPGotoDeclarationHandlerLSPGoToImplementationActionLSPGoToTypeDefinitionActionWorkaround
Until this is addressed, client plugins can work around the issue by always returning
truefromisEnabled()and moving the user-preference check intois*Supported(). However, this is suboptimal becauseisEnabled()serves as a pre-server-start optimization — returningtrueunconditionally may cause the server to be started for files where it shouldn't be.Environment
AbstractLSPDocumentFeature.isEnabled()AbstractLSPDocumentFeatureSupport.getLanguageServers()AbstractLSPGoToActionand all subclassesLSPGotoDeclarationHandlerLSPFindUsagesHandlerFactory/LSPUsageSearcher