Semantic tokens are empty when requested immediately after textDocument/didOpen
When kak-lsp sends
textDocument/semanticTokens/full soonish after textDocument/didOpen, the semantic tokens response is empty ([]).
Jan 29 10:39:42.769 DEBG To server: {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"languageId":"rust","text":"mod kamp;\n\nuse anyhow::Result;\n\nfn main() -> Result<()> {\n let res = kamp::run()?;\n if let Some(res) = res {\n print!(\"{}\", res);\n }\n Ok(())\n}\n","uri":"file:///home/johannes/git/kampliment/src/main.rs","version":1}}}, module: kak_lsp::language_server_transport:182
Jan 29 10:39:42.769 DEBG To server: {"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"hoverActions":{"enable":false}}}}, module: kak_lsp::language_server_transport:182
Jan 29 10:39:42.769 DEBG To server: {"jsonrpc":"2.0","method":"textDocument/semanticTokens/full","params":{"textDocument":{"uri":"file:///home/johannes/git/kampliment/src/main.rs"}},"id":1}, module: kak_lsp::language_server_transport:182
Jan 29 10:39:42.771 DEBG From server: {"jsonrpc":"2.0","id":1,"result":{"resultId":"1","data":[]}}, module: kak_lsp::language_server_transport:156
If the semantic tokens requests is sent a bit later I get a valid, non-empty response.
The workaround of sleeping is obvious and easy but not ideal. (I used a different workaround of treating an empty response as error)
I'd expect rust-analyzer to return an error response if it didn't yet process textDocument/didOpen,
and a non-empty response if it did.
The current behavior feels like a race where semantic tokens is computed based on half-processed textDocument/didOpen.
I wonder how/if LSP generally solves synchronization issues like this.
Maybe we can process at least textDocument/didOpen and textDocument/didChange atomically.
rust-analyzer version: rust-analyzer 1 (daa0138e4 2023-01-21)
rustc version: rustc 1.64.0 (a55dd71d5 2022-09-19)
relevant settings: Kakoune v2022.10.31 + kak-lsp (version c5645e7c8bc7c3508cf04939af3e365e248de8b0) and this ~/.config/kak/kakrc
eval %sh{kak-lsp -s $kak_session --kakoune}
lsp-enable
hook global WinSetOption filetype=rust %{
remove-highlighter window/rust # remove regex highlighting
hook window NormalIdle .* lsp-semantic-tokens
}
Originally reported as https://github.com/kak-lsp/kak-lsp/issues/668
Same for textDocument/codeLens which I use to run Rust unit tests.
If textDocument/codeLens is sent too early after opening the file, I get an empty response and it's not clear when I should re-request it.
VSCode seems to wait for 2 seconds after opening the file, or perhaps they wait for the first publishDiagnostics.
I don't know what we should wait for here (ideally the waiting happens inside rust-analyzer).
Sep 28 11:44:17.658 DEBG To server rust-analyzer: {"jsonrpc":"2.0","method":"initialize","params":{"capabilities":{"experimental":{"commands":{"commands":["rust-analyzer.runSingle"]},"hoverActions":true},"general":{"markdown":{"parser":"kakoune-lsp","version":"17.1.2-snapshot"},"positionEncodings":["utf-8","utf-16"],"regularExpressions":{"engine":"Rust regex"}},"offsetEncoding":["utf-8","utf-16"],"textDocument":{"callHierarchy":{"dynamicRegistration":false},"codeAction":{"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.fixAll","source.organizeImports"]}},"dynamicRegistration":false,"isPreferredSupport":false,"resolveSupport":{"properties":["edit"]}},"codeLens":{"dynamicRegistration":false},"colorProvider":{"dynamicRegistration":false},"completion":{"completionItem":{"commitCharactersSupport":false,"deprecatedSupport":false,"documentationFormat":["markdown","plaintext"],"preselectSupport":false,"resolveSupport":{"properties":["additionalTextEdits","detail","documentation"]},"snippetSupport":true},"completionItemKind":{"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]},"contextSupport":false,"dynamicRegistration":false},"declaration":{"dynamicRegistration":false,"linkSupport":false},"definition":{"dynamicRegistration":false,"linkSupport":false},"documentHighlight":{"dynamicRegistration":false},"documentLink":{"dynamicRegistration":false,"tooltipSupport":false},"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]}},"formatting":{"dynamicRegistration":false},"hover":{"contentFormat":["markdown","plaintext"],"dynamicRegistration":false},"implementation":{"dynamicRegistration":false,"linkSupport":false},"inlayHint":{"dynamicRegistration":false},"onTypeFormatting":{"dynamicRegistration":false},"publishDiagnostics":{"relatedInformation":true,"tagSupport":{"valueSet":[1,2]}},"rangeFormatting":{"dynamicRegistration":false},"references":{"dynamicRegistration":false},"rename":{"dynamicRegistration":false,"prepareSupport":false},"selectionRange":{},"semanticTokens":{"dynamicRegistration":true,"formats":["relative"],"requests":{"full":true,"range":false},"serverCancelSupport":true,"tokenModifiers":["documentation","readonly","constant"],"tokenTypes":["type","method","function","comment","keyword","variable","operator","namespace","string"]},"signatureHelp":{"contextSupport":false,"dynamicRegistration":false,"signatureInformation":{"documentationFormat":["plaintext"],"parameterInformation":{"labelOffsetSupport":false}}},"synchronization":{"didSave":true,"dynamicRegistration":false,"willSave":false,"willSaveWaitUntil":false},"typeDefinition":{"dynamicRegistration":false,"linkSupport":false}},"window":{"showMessage":{"messageActionItem":{"additionalPropertiesSupport":true}},"workDoneProgress":true},"workspace":{"applyEdit":true,"codeLens":{},"configuration":true,"didChangeConfiguration":{"dynamicRegistration":false},"executeCommand":{"dynamicRegistration":false},"inlayHint":{"refreshSupport":false},"symbol":{"dynamicRegistration":false,"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]}},"workspaceEdit":{"changeAnnotationSupport":{},"documentChanges":true,"failureHandling":"abort","normalizesLineEndings":false,"resourceOperations":["create","delete","rename"]},"workspaceFolders":true}},"clientInfo":{"name":"kakoune-lsp","version":"17.1.2-snapshot"},"processId":800786,"rootPath":"/home/johannes/git/kakoune-lsp/t/rustapp","rootUri":"file:///home/johannes/git/kakoune-lsp/t/rustapp","trace":"off","workspaceFolders":[{"name":"/home/johannes/git/kakoune-lsp/t/rustapp","uri":"file:///home/johannes/git/kakoune-lsp/t/rustapp"}]},"id":0}, module: kak_lsp::log:47
Sep 28 11:44:17.728 DEBG From server rust-analyzer: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{"positionEncoding":"utf-8","textDocumentSync":{"openClose":true,"change":2,"save":{}},"selectionRangeProvider":true,"hoverProvider":true,"completionProvider":{"resolveProvider":true,"triggerCharacters":[":",".","'","("],"completionItem":{"labelDetailsSupport":false}},"signatureHelpProvider":{"triggerCharacters":["(",",","<"]},"definitionProvider":true,"typeDefinitionProvider":true,"implementationProvider":true,"referencesProvider":true,"documentHighlightProvider":true,"documentSymbolProvider":true,"workspaceSymbolProvider":true,"codeActionProvider":{"codeActionKinds":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite"],"resolveProvider":true},"codeLensProvider":{"resolveProvider":true},"documentFormattingProvider":true,"documentRangeFormattingProvider":false,"documentOnTypeFormattingProvider":{"firstTriggerCharacter":"=","moreTriggerCharacter":[".",">","{","("]},"renameProvider":{"prepareProvider":true},"foldingRangeProvider":true,"declarationProvider":true,"workspace":{"workspaceFolders":{"supported":true,"changeNotifications":true},"fileOperations":{"willRename":{"filters":[{"scheme":"file","pattern":{"glob":"**/*.rs","matches":"file"}},{"scheme":"file","pattern":{"glob":"**","matches":"folder"}}]}}},"callHierarchyProvider":true,"semanticTokensProvider":{"legend":{"tokenTypes":["comment","decorator","enumMember","enum","function","interface","keyword","macro","method","namespace","number","operator","parameter","property","string","struct","typeParameter","variable","angle","arithmetic","attributeBracket","attribute","bitwise","boolean","brace","bracket","builtinAttribute","builtinType","character","colon","comma","comparison","constParameter","const","deriveHelper","derive","dot","escapeSequence","formatSpecifier","generic","invalidEscapeSequence","label","lifetime","logical","macroBang","parenthesis","procMacro","punctuation","selfKeyword","selfTypeKeyword","semicolon","static","toolModule","typeAlias","union","unresolvedReference"],"tokenModifiers":["async","documentation","declaration","static","defaultLibrary","associated","attribute","callable","constant","consuming","controlFlow","crateRoot","injected","intraDocLink","library","macro","mutable","procMacro","public","reference","trait","unsafe"]},"range":true,"full":{"delta":true}},"inlayHintProvider":{"resolveProvider":true},"experimental":{"externalDocs":true,"hoverRange":true,"joinLines":true,"matchingBrace":true,"moveItem":true,"onEnter":true,"openCargoToml":true,"parentModule":true,"runnables":{"kinds":["cargo"]},"ssr":true,"workspaceSymbolScopeKindFiltering":true}},"serverInfo":{"name":"rust-analyzer","version":"1.81.0 (eeb90cd 2024-09-04)"}}}, module: kak_lsp::log:47
Sep 28 11:44:17.732 DEBG To server rust-analyzer: {"jsonrpc":"2.0","method":"initialized","params":{}}, module: kak_lsp::log:47
Sep 28 11:44:17.734 DEBG To server rust-analyzer: {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"languageId":"rust","text":"#[test]\nfn test_foo() {\n assert!(false);\n}\n","uri":"file:///home/johannes/git/kakoune-lsp/t/rustapp/src/main.rs","version":1}}}, module: kak_lsp::log:47
Sep 28 11:44:17.735 DEBG To server rust-analyzer: {"jsonrpc":"2.0","method":"textDocument/codeLens","params":{"textDocument":{"uri":"file:///home/johannes/git/kakoune-lsp/t/rustapp/src/main.rs"}},"id":1}, module: kak_lsp::log:47
Sep 28 11:44:17.736 DEBG To server rust-analyzer: {"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{}}}, module: kak_lsp::log:47
Sep 28 11:44:17.739 DEBG From server rust-analyzer: {"jsonrpc":"2.0","id":1,"result":[]}, module: kak_lsp::log:47
One problem is that crate_graph() is empty this early on; I'm not yet sure where that function is defined, probably a salsa thing
Startup requires a couple seconds for reasons (invoking cargo, building build scripts etc) which is why responses are usually empty at the start, the client should ideally support refresh notifications such that the server can inform the client when to re-request: https://github.com/rust-lang/rust-analyzer/blob/546339a7be357b3e95fc4b79a8816dce540d477b/crates/rust-analyzer/src/main_loop.rs#L417-L434
Ok thanks, we implemented refresh notifications now. We trigger the refreshes as soon as an editor window sees any activity. Could also refresh all open buffers but there could be a lot of buffers. Or all visible editor windows but we don't keep track of that right now.
We should probably still fix this but refresh seems like a usable workaround.