Skip to content

Feature Request: Distinguish explicit vs implicit feature invocations #1617

Description

@SergejSalnikov

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):

  1. The explicit "Go To > LSP References" menu item is visible (because canSupportAction() checks isSupported(), not isEnabled())
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions