Skip to content

Add support for CompletionItem commands #389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 4, 2023

Conversation

vimpostor
Copy link
Contributor

@vimpostor vimpostor commented Sep 3, 2023

This patch implements support for the optional "command" field in the CompletionItem response to a "completionItem/resolve" request: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItem

Some language servers (e.g. haskell-language-server) use this field to apply other edits to the document (e.g. auto-importing functions).
While the spec technically says that "additionalTextEdits" should be used for that usecase (also see #367), apparently not all servers do and I can see the reason, because haskell-language-server also wants to show an "window/showMessage" info message to the user about the auto-import.

When we resolve a completion item with a "completionItem/resolve"
request, the LSP server returns a CompletionItem object [0], which may
contain an optional "command" field. This field describes a command that
must be called **after** inserting the completion.

For example the haskell-language-server may respond with the following
command after autocompleting an unimported function (arguments are left
out for brevity):

{'command': '10619:ghcide-extend-import-action:extendImport', 'arguments': [], 'title': 'extend import'}

Then when we send the matching "workspace/executeCommand" request, the
server will respond with a "workspace/applyEdit" request, that
autoimports the function.

Technically the specification dictates that servers should prefer to set
"additionalTextEdits" for that usecase, but there is still some use for
doing it this way, e.g. haskell-language-server also wants to display an
info message to the user, which doesn't happen with
"additionalTextEdits".

In any case it is important to support the "command" field, as it may
also contain actions unrelated to "additionalTextEdits".

[0] https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItem
The workspace edit may add or delete lines, so resetting it to the last
linenumber and column will very likely not match the last logical line
before the workspaceEdit operation.

For example an action might add an auto-import statement at the top of
the file, effectively adding one line in-between. If we now reset the
cursor, we will end up one logical line before the one where we actually
started at.

This behaviour is automatically fixed if we just don't reset the cursor
at all.
@vimpostor vimpostor force-pushed the completionitem_command branch from f9bf2c4 to 33c9dc3 Compare September 4, 2023 15:55
@vimpostor
Copy link
Contributor Author

vimpostor commented Sep 4, 2023

@yegappan I have also removed the cursor-reset after an workspaceEdit action. It caused problems, when a workspaceEdit inserts or deletes line, as we will then likely end up on a different logical line than it was before. If there was any reason for the cursor-reset, let me know.

@yegappan
Copy link
Owner

yegappan commented Sep 4, 2023

As long as the cursor is restored to the previous insertion end point so that the user can continue to enter text after a completion, it is fine to remove the cursor-reset code. I have also seen some problems in correctly restoring the cursor if an import is automatically added or some lines are removed. I will merge this and if we see any additional issues with cursor restoration, we can then address them.

@yegappan yegappan merged commit 5ffb7e2 into yegappan:main Sep 4, 2023
@Shane-XB-Qian
Copy link
Contributor

as we will then likely end up on a different logical line than it was before

it actually was the/my concern about cursor etc above (in the review) when/if there were both and not synced.
and not sure if they all would re-calc the cursor pos.

@Shane-XB-Qian
Copy link
Contributor

and the logic seems wrong like before for textadditionaledits :
seems it would NOT apply it when it do had it (vs delayed) now.

// BTW: @yegappan looks you're a bit hurry to merge recently except my PR. 😄

@vimpostor
Copy link
Contributor Author

vimpostor commented Sep 4, 2023

and the logic seems wrong like before for textadditionaledits : seems it would NOT apply it when it do had it (vs delayed) now.

I don't really understand what you are trying to say (the English is hard to understand tbh), but if you know about a specific language server that provides command only delayed, then we can amend the logic like for additionalTextEdits. But to me this sounds like speculation right now and would be a wasted resolveCompletion request.

@Shane-XB-Qian
Copy link
Contributor

  1. i meant i was concerning the cursor pos when/if they were both (additionaltextedits and command) set from lsp server specially if one was or both was delayed to process, but you said generally it was not possible happened, then ok, but just worry the cursor pos would be not correct.

  2. here :

  if !completionData->has_key('additionalTextEdits')
    # Some language servers (e.g. typescript) delay the computation of the

it would NOT apply the additionaltextedits when it do had it (vs delayed) now.
// this logic seems wrong (not like before).

vimpostor added a commit to vimpostor/lsp that referenced this pull request Sep 4, 2023
Regression was introduced in 6f4fdc7.
The logic was still fine if the LSP server delayed the
additionalTextEdits field, but if we already got it, we ended up
ignoring it.

Fix this by checking for additionalTextEdits outside the delayed-resolve
condition.

Also see yegappan#389
@vimpostor
Copy link
Contributor Author

it would NOT apply the additionaltextedits when it do had it (vs delayed) now. // this logic seems wrong (not like before).

I understand your point now, sry that if-case somehow slipped through. I fixed it in #390

@Shane-XB-Qian
Copy link
Contributor

Shane-XB-Qian commented Sep 4, 2023 via email

@Shane-XB-Qian
Copy link
Contributor

Shane-XB-Qian commented Sep 4, 2023

all in all, your PR was just adding following before additionaltextedits :

+  if completionData->has_key('command')
+    # Some language servers (e.g. haskell-language-server) want to apply
+    # additional commands after completion.
+    codeaction.DoCommand(lspserver, completionData.command)
+  endif

would you mind to change it to this way? it would be the less change and less impact.
// and not remove that cursor save/restore code if so, unless really confirm it required.

@vimpostor
Copy link
Contributor Author

vimpostor commented Sep 4, 2023

// if so, you above removed cursor save/restore code perhaps just leave it there too.

It was intentionally removed to avoid the logical line changing after the user confirmed the completion. That was an actual problem that was happening with haskell-language-server and other LSPs as well. Can you describe any scenario where there is buggy behaviour because the cursor is not manually reset? Personally I don't know any such scenario. But if you know one, I am happy to find something that works in both cases.

but just adding command before it

I am not entirely sure, but to me it sounds like command should be applied after additionalTextEdits, if both are present. In any case with #390 merged, from a code-perspective there is not really this difference between the two anymore, they are both equally short now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants