Description
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:
- client sends a
textDocument/codeAction
request, the body of which has typeCodeActionParams
- If the server replies with a code action missing
edit
andcommand
fields and (optional)data
field of any type - Then client sends
textDocument/resolve
, including thedata
the client replied totextDocument/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:
- client sends a
textDocument/codeAction
request, the body of which has typeCodeActionParams
- If the server replies with a code action missing
edit
andcommand
fields - Then client sends
textDocument/resolve
, including acodeActionParams
field of typeCodeActionParams
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 customdata
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 alazy
, but it shakes out to the same thing) - Step 2: Before serializing, replace
edit
withundefined
and copy thecodeActionParams
to thedata
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 intextDocument/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.