-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Trigger VSCode to rename after extract variable assist is applied #17587
Conversation
When the user applies the "Extract Variable" assist, the cursor is positioned at the newly inserted variable. This commit adds a command to the assist that triggers the rename action in VSCode. This way, the user can quickly rename the variable after applying the assist. Fixes part of: rust-lang#17579
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally all of the extract & generate assists should still be using the add_placeholder_snippet_group
for better out-of-the box support for other editor extensions, but that's an entirely separate issue 🙂
I experimented with that first and it seemed like it was a less than ideal experience. Agree that this is a separate issue. |
- move `edit.rename()` to the end of the function - use a match statement to set `res.command`
Thanks! |
☀️ Test successful - checks-actions |
@joshka, @Veykril, shouldn't RA check whether the LSP client announced its support for the command "rust-analyzer.rename" before sending it in a codeAction/resolve reply? Also, I'm not sure asking the client to do a rename after the the extract is the best approach since RA can send "disjoint" snippets as well. I have not tested it, but instead of sending this: {
"jsonrpc": "2.0", "id": 7,
"result": {
"title": "Extract into variable",
"kind": "refactor.extract",
"command": { "title": "rename", "command": "rust-analyzer.rename" },
"edit": {
"documentChanges": [
{
"textDocument": { "uri": "file:///tmp/rust-rename-17587/src/main.rs", "version": 0 },
"edits": [
{
"range": {
"start": { "line": 1, "character": 8 },
"end": { "line": 1, "character": 9 }
},
"newText": "$0var_name",
"insertTextFormat": 2
},
{
"range": {
"start": { "line": 1, "character": 18 },
"end": { "line": 1, "character": 18 }
},
"newText": "\n let a = var_name;"
}
]
}
]
}
}
} RA could send something like this (edited): {
"jsonrpc": "2.0", "id": 7,
"result": {
"title": "Extract into variable",
"kind": "refactor.extract",
"edit": {
"documentChanges": [
{
"textDocument": { "uri": "file:///tmp/rust-rename-17587/src/main.rs", "version": 0 },
"edits": [
{
"range": {
"start": { "line": 1, "character": 8 },
"end": { "line": 1, "character": 9 }
},
"newText": "$0${1:var_name}",
"insertTextFormat": 2
},
{
"range": {
"start": { "line": 1, "character": 18 },
"end": { "line": 1, "character": 18 }
},
"newText": "\n let a = $1;"
"insertTextFormat": 2
}
]
}
]
}
}
} I.e., the "var_name" is a just default value for the mirrored tab-stop field. The commit log of #15876 directly says:
|
Hmm, it should be respecting the client commands list that the client declares and only emitting it if
This would also be better for editors like Helix, where I forsee code action snippet support being available before a plugin system is ready. It is possible to do it today like is done for |
That is not what happens in my case. The client only states that it supports the most basic rename action in its RenameClientCapabilities and that's all. And yet the server sends this in its textDocument/rename reply:
This is the initialize request my LSP client sends:{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "processId": 1602814, "clientInfo": { "name": "Eglot", "version": "1.17" }, "rootPath": "/tmp/rust-rename-17587/", "rootUri": "file:///tmp/rust-rename-17587", "initializationOptions": {}, "capabilities": { "workspace": { "applyEdit": true, "executeCommand": { "dynamicRegistration": false }, "workspaceEdit": { "documentChanges": true }, "didChangeWatchedFiles": { "dynamicRegistration": true }, "symbol": { "dynamicRegistration": false }, "configuration": true, "workspaceFolders": true }, "textDocument": { "synchronization": { "dynamicRegistration": false, "willSave": true, "willSaveWaitUntil": true, "didSave": true }, "completion": { "dynamicRegistration": false, "completionItem": { "snippetSupport": true, "deprecatedSupport": true, "resolveSupport": { "properties": [ "documentation", "details", "additionalTextEdits" ] }, "tagSupport": { "valueSet": [ 1 ] } }, "contextSupport": true }, "hover": { "dynamicRegistration": false, "contentFormat": [ "plaintext" ] }, "signatureHelp": { "dynamicRegistration": false, "signatureInformation": { "parameterInformation": { "labelOffsetSupport": true }, "documentationFormat": [ "plaintext" ], "activeParameterSupport": true } }, "references": { "dynamicRegistration": false }, "definition": { "dynamicRegistration": false, "linkSupport": true }, "declaration": { "dynamicRegistration": false, "linkSupport": true }, "implementation": { "dynamicRegistration": false, "linkSupport": true }, "typeDefinition": { "dynamicRegistration": false, "linkSupport": true }, "documentSymbol": { "dynamicRegistration": false, "hierarchicalDocumentSymbolSupport": true, "symbolKind": { "valueSet": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 ] } }, "documentHighlight": { "dynamicRegistration": false }, "codeAction": { "dynamicRegistration": false, "resolveSupport": { "properties": [ "edit", "command" ] }, "dataSupport": true, "codeActionLiteralSupport": { "codeActionKind": { "valueSet": [ "quickfix", "refactor", "refactor.extract", "refactor.inline", "refactor.rewrite", "source", "source.organizeImports" ] } }, "isPreferredSupport": true }, "formatting": { "dynamicRegistration": false }, "rangeFormatting": { "dynamicRegistration": false }, "rename": { "dynamicRegistration": false }, "inlayHint": { "dynamicRegistration": false }, "publishDiagnostics": { "relatedInformation": false, "codeDescriptionSupport": false, "tagSupport": { "valueSet": [ 1, 2 ] } } }, "window": { "showDocument": { "support": true }, "workDoneProgress": true }, "general": { "positionEncodings": [ "utf-32", "utf-8", "utf-16" ] }, "experimental": { "snippetTextEdit": true, "serverStatusNotification": true, "colorDiagnosticOutput": true, "openServerLogs": true, "testExplorer": true, "localDocs": true }, "offsetEncoding": [ "utf-32", "utf-16" ] }, "workspaceFolders": [ { "uri": "file:///tmp/rust-rename-17587", "name": "/tmp/rust-rename-17587/" } ] } } |
There is a weird |
The rename UX flow seems (subjectively) nicer than the disjoint snippets flow to me as there's no good intuitive way to finish the disjoint edit (neither Tab or Enter work, only Escape), also there's some editing weirdness: Changing extract_variable.rs:131 (and removing the rename call): if let Some(cap) = ctx.config.snippet_cap {
if let Some(ast::Pat::IdentPat(ident_pat)) = let_stmt.pat() {
if let Some(name) = ident_pat.name() {
edit.add_placeholder_snippet_group(
cap,
vec![name.syntax().clone(), name_expr.syntax().clone()],
);
}
}
} Screen.Recording.2024-07-19.at.12.35.01.PM.movRegarding the client commands, the server side execution of the rename command is a noop if the editor.action.rename command is not enabled (as commands.rename == false in that case). I don't think the assists have access to the any client command config, so there's no way to filter this out. rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs Lines 1339 to 1346 in b333f85
Is there any impact of this other than an unnecessary round trip? I'm trying to understand what the error / problem is here, but I'm not seeing it. |
That depends on the client, we are violating the LSP (or in this case the lsp extension I suppose), we shouldn't be doing that. The problem is this config being enabled by default for some reason https://github.com/veykril/rust-analyzer/blob/09ef75c717e9ffb4cbc71ce1cfec7694244742c2/crates/rust-analyzer/src/config.rs#L564 which then force enables all of the commands here https://github.com/veykril/rust-analyzer/blob/09ef75c717e9ffb4cbc71ce1cfec7694244742c2/crates/rust-analyzer/src/config.rs#L1896-L1911 |
Ah got it |
There is one additional thing that I don't understand. The client needs to declare support for Thanks. |
Same goes for the |
The motivation is to trigger a rename after extract variable assist is applied: rust-lang/rust-analyzer#17587 The implementation might need to be updated later: rust-lang/rust-analyzer#17644 * eglot-x.el (eglot-x-client-commands): New defcustom. (eglot-client-capabilities): Set experimental/commands based on the defcustom. (eglot-x--apply-text-edits): Sync with upstream version of eglot--apply-text-edits, and remove heuristic to detect a snippet, which is no longer needed. (eglot-x-execute-command): New defun. Handle the client command "rust-analyzer.rename" locally. (elgot-execute): New cl-defmethod. Extend the default implementation by calling eglot-x-execute-command for commands.
When the user applies the "Extract Variable" assist, the cursor is
positioned at the newly inserted variable. This commit adds a command
to the assist that triggers the rename action in VSCode. This way, the
user can quickly rename the variable after applying the assist.
Fixes part of: #17579
Screen.Recording.2024-07-12.at.6.56.43.PM.mov
I haven't yet looked at the module or function extraction assists yet.