Skip to content

Langium 4.x does not support two-phase code lens resolution #2051

@johnholliday

Description

@johnholliday

CodeLensProvider Does Not Support Two-Phase Resolution (resolveProvider Hard-Coded to False)

Langium version: 4.1.0
Package name: langium

Steps To Reproduce

  1. Implement a CodeLensProvider with expensive operations (e.g., finding all references across the workspace)
  2. Register the provider in your language services module
  3. Observe that the LSP initialization sets codeLensProvider: { resolveProvider: false } in server capabilities
  4. Notice that there is no way to implement a resolveCodeLens method because the interface doesn't support it

Link to code example:

See the Langium source code itself:

  • packages/langium/src/lsp/language-server.ts lines 177-179
  • packages/langium/src/lsp/code-lens-provider.ts lines 12-14

The current behavior

In language-server.ts (lines 177-179):

codeLensProvider: hasCodeLensProvider
    ? { resolveProvider: false }
    : undefined,

The CodeLensProvider interface (lines 12-14):

export interface CodeLensProvider {
    provideCodeLens(document: LangiumDocument, params: CodeLensParams, cancelToken?:
CancellationToken): MaybePromise<CodeLens[] | undefined>
}

The resolveProvider capability is hard-coded to false, and there is no resolveCodeLens method in the interface. This means:

  1. All code lens data must be computed immediately in provideCodeLens
  2. Expensive operations (like finding references) block the initial code lens request
  3. Users see delays when opening files with many code lenses
  4. There's no way to defer expensive computations until a lens is actually clicked/resolved

The expected behavior

Langium should support optional two-phase code lens resolution, exactly like it does for
WorkspaceSymbolProvider.

Example of how it SHOULD work (see WorkspaceSymbolProvider in lines 184-186):

workspaceSymbolProvider: workspaceSymbolProvider
    ? { resolveProvider: Boolean(workspaceSymbolProvider.resolveSymbol) }
    : undefined

The WorkspaceSymbolProvider interface includes an optional resolve method:

export interface WorkspaceSymbolProvider {
    getSymbols(params: WorkspaceSymbolParams, cancelToken?: CancellationToken):
MaybePromise<WorkspaceSymbol[]>;

    resolveSymbol?(symbol: WorkspaceSymbol, cancelToken?: CancellationToken):
MaybePromise<WorkspaceSymbol>;  // OPTIONAL
}

Proposed Solution

  1. Update the CodeLensProvider interface to include an optional resolveCodeLens method:
export interface CodeLensProvider {
    provideCodeLens(document: LangiumDocument, params: CodeLensParams, cancelToken?:
CancellationToken): MaybePromise<CodeLens[] | undefined>;

    resolveCodeLens?(codeLens: CodeLens, cancelToken?: CancellationToken): MaybePromise<CodeLens>;
 // NEW
}
  1. Update the capability registration in language-server.ts (lines 177-179):
codeLensProvider: hasCodeLensProvider
    ? { resolveProvider: Boolean(hasCodeLensProvider.resolveCodeLens) }
    : undefined,
  1. Add the resolve handler (similar to workspace symbol):
export function addCodeLensResolveHandler(connection: Connection, services: LangiumSharedServices):
 void {
    connection.onCodeLensResolve(createServerRequestHandler(
        (services, document, codeLens, cancelToken) =>
services.lsp?.CodeLensProvider?.resolveCodeLens?.(codeLens, cancelToken) ?? codeLens,
        services,
        DocumentState.IndexedReferences
    ));
}

Benefits

  • Performance: Defer expensive operations (like reference counting) until the lens is actually needed
  • Consistency: Matches the pattern already established by WorkspaceSymbolProvider,
    DocumentLinkProvider, and InlayHintProvider
  • Developer Experience: Provides flexibility for implementers to choose immediate vs. deferred resolution based on their use case
  • Backward Compatibility: The optional method means existing implementations continue to work unchanged

Why This Matters

Code lenses that display reference counts are a common use case, but finding all references across a large workspace can be expensive. The current limitation forces all computation to happen synchronously, causing poor user experience with visible delays. The framework already has the pattern and infrastructure to support two-phase resolution—it just hasn't been applied to CodeLensProvider.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions