Skip to content

Proposal proposal: Easier codeAction/resolve handling #1748

Open
@mheiber

Description

@mheiber

This is a proposal for an easier workflow for lazy code actions.
A lazy code action is a code action where we separate the following, usually for performance:

  • resolving the title, kind, and location of the code action
  • resolving the edits for the code action

Status Quo

The current flow for lazy code actions is:

  1. client sends a textDocument/codeAction request, the body of which has type CodeActionParams
  2. If the server replies with a code action missing edit and command fields and (optional) data field of any type
  3. Then client sends textDocument/resolve, including the data the client replied to textDocument/codeAction with

The idea of the existing flow, as I understand it, is that the information in data will be sufficient to fill in the edit field of the code action.

The difficulty in implementing is that an LSP server implementor must design and implement a custom protocol for every lazy code action: what information to serialize into data and then read back.
There is no standard way of doing this and it sounds mistake-prone.

Alternative

I suggest adding an alternative flow:

  1. client sends a textDocument/codeAction request, the body of which has type CodeActionParams
  2. If the server replies with a code action missing edit and command fields
  3. Then client sends textDocument/resolve, including a codeActionParams field of type CodeActionParams corresponding to (1)

For backwards compatibility, the flow can be opt-in via a server and client capability called something like supportsCodeActionParamsInResolve.

I found a flow like this easier and simpler to implement because:

  • The logic for finding the range and title to show to the user was similar to the logic
    for finding the edits for a refactor, so it's natural to reuse the same code path
  • By reusing the same code path and avoiding custom protocol via data it's harder to make a mistake
    in the generting/consuming the custom data or a mistake in ensuring that the edits go with the appropriate code action.
  • Once some plumbing is set up, converting code actions to be lazy can be done in two lines of code each
    facebook/hhvm@07b43be

Simulating the alternative lazy resolution flow today

I simulated this alernative lazy code action flow with:

textDocument/codeAction server reply implementation:

  • Step 1: set edit to a () => WorkspaceEdit[] (In OCaml I used a lazy, but it shakes out to the same thing)
  • Step 2: Before serializing, replace edit with undefined and copy the codeActionParams to the data field of each code action.

testDocument/resolve server reply implementation:

  • Step 1: same code path as step 1 for textDocument/codeAction
  • Step 2: Before serializing, replace the edit field (set to () => WorkspaceEdit[] ) with the result of applying the function, so we
    return a fully resolved code action {edit: WorkspaceEdit[], ...}

Downsides of merely simulating are:

  • The hack is non-obvious and not discoverable for LSP server implementors
  • The CodeActionParams payload is repeated by the server once for every code action returned in textDocument/codeAction, which
    makes messages harder to read for debugging and testing and has a (small) speed and memory cost

Next steps

I could adapt this into a full proposal after getting feedback.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions