From 58618d208acd3827c4e86668529edb619bb9b8dd Mon Sep 17 00:00:00 2001 From: jdrouhard Date: Tue, 30 May 2023 13:56:29 -0500 Subject: [PATCH] feat(lsp)!: promote LspRequest to a full autocmd and enrich with additional data (#23694) BREAKING CHANGE: LspRequest is no longer a User autocmd but is now a first class citizen. LspRequest as a User autocmd had limited functionality. Namely, the only thing you could do was use the notification to do a lookup on all the clients' requests tables to figure out what changed. Promoting the autocmd to a full autocmd lets us set the buffer the request was initiated on (so people can set buffer-local autocmds for listening to these events). Additionally, when used from Lua, we can pass additional metadata about the request along with the notification, including the client ID, the request ID, and the actual request object stored on the client's requests table. Users can now listen for these events and act on them proactively instead of polling all of the requests tables and looking for changes. --- runtime/doc/lsp.txt | 51 +++++++++++++++++++++++++---- runtime/doc/news.txt | 6 ++++ runtime/lua/vim/lsp.lua | 27 ++++++++++++--- src/nvim/auevents.lua | 2 ++ test/functional/plugin/lsp_spec.lua | 2 +- 5 files changed, 75 insertions(+), 13 deletions(-) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 98a0801013..581cfd5348 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -619,19 +619,54 @@ to the callback in the "data" table. The token fields are documented in Note: doing anything other than calling |vim.lsp.semantic_tokens.highlight_token()| is considered experimental. -Also the following |User| |autocommand|s are provided: + +LspRequest *LspRequest* + +For each request sent to an LSP server, this event is triggered for every +change to the request's status. The status can be one of `pending`, +`complete`, or `cancel` and is sent as the {type} on the "data" table passed +to the callback function. + +It triggers when the initial request is sent ({type} == `pending`) and when +the LSP server responds ({type} == `complete`). If a cancelation is requested +using `client.cancel_request(request_id)`, then this event will trigger with +{type} == `cancel`. + +When used from Lua, the client ID, request ID, and request are sent in the +"data" table. See {requests} in |vim.lsp.client| for details on the {request} +value. If the request type is `complete`, the request will be deleted from the +client's pending requests table immediately after calling the event's +callbacks. Example: >lua + + vim.api.nvim_create_autocmd('LspRequest', { + callback = function(args) + local bufnr = args.buf + local client_id = args.data.client_id + local request_id = args.data.request_id + local request = args.data.request + if request.type == 'pending' then + -- do something with pending requests + track_pending(client_id, bufnr, request_id, request) + elseif request.type == 'cancel' then + -- do something with pending cancel requests + track_canceling(client_id, bufnr, request_id, request) + elseif request.type == 'complete' then + -- do something with finished requests. this pending + -- request entry is about to be removed since it is complete + track_finish(client_id, bufnr, request_id, request) + end + end, + }) +< + +Also the following |User| |autocommand| is provided: LspProgressUpdate *LspProgressUpdate* Upon receipt of a progress notification from the server. See |vim.lsp.util.get_progress_messages()|. -LspRequest *LspRequest* - After a change to the active set of pending LSP requests. See {requests} - in |vim.lsp.client|. - Example: >vim autocmd User LspProgressUpdate redrawstatus - autocmd User LspRequest redrawstatus < ============================================================================== @@ -764,7 +799,9 @@ client() *vim.lsp.client* server. Entries are key-value pairs with the key being the request ID while the value is a table with `type`, `bufnr`, and `method` key-value pairs. `type` is either "pending" for an active request, or - "cancel" for a cancel request. + "cancel" for a cancel request. It will be "complete" ephemerally while + executing |LspRequest| autocmds when replies are received from the + server. • {config} (table): copy of the table that was passed by the user to |vim.lsp.start_client()|. • {server_capabilities} (table): Response from the server sent on diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index b6839ec692..72eb182fa5 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -31,6 +31,9 @@ The following changes may require adaptations in user config or plugins. set keymodel=startsel,stopsel < +• |LspRequest| autocmd was promoted from a |User| autocmd to a first class + citizen. + ============================================================================== ADDED FEATURES *news-added* @@ -80,6 +83,9 @@ The following changes to existing APIs or features add new behavior. • The `workspace/didChangeWatchedFiles` LSP client capability is now enabled by default. +• |LspRequest| autocmd callbacks now contain additional information about the LSP + request status update that occurred. + ============================================================================== REMOVED FEATURES *news-removed* diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 5337abea25..c9ca8cd224 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -799,7 +799,9 @@ end --- to the server. Entries are key-value pairs with the key --- being the request ID while the value is a table with `type`, --- `bufnr`, and `method` key-value pairs. `type` is either "pending" ---- for an active request, or "cancel" for a cancel request. +--- for an active request, or "cancel" for a cancel request. It will +--- be "complete" ephemerally while executing |LspRequest| autocmds +--- when replies are received from the server. --- --- - {config} (table): copy of the table that was passed by the user --- to |vim.lsp.start_client()|. @@ -1408,13 +1410,24 @@ function lsp.start_client(config) { method = method, client_id = client_id, bufnr = bufnr, params = params } ) end, function(request_id) + local request = client.requests[request_id] + request.type = 'complete' + nvim_exec_autocmds('LspRequest', { + buffer = bufnr, + modeline = false, + data = { client_id = client_id, request_id = request_id, request = request }, + }) client.requests[request_id] = nil - nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false }) end) if success and request_id then - client.requests[request_id] = { type = 'pending', bufnr = bufnr, method = method } - nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false }) + local request = { type = 'pending', bufnr = bufnr, method = method } + client.requests[request_id] = request + nvim_exec_autocmds('LspRequest', { + buffer = bufnr, + modeline = false, + data = { client_id = client_id, request_id = request_id, request = request }, + }) end return success, request_id @@ -1486,7 +1499,11 @@ function lsp.start_client(config) local request = client.requests[id] if request and request.type == 'pending' then request.type = 'cancel' - nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false }) + nvim_exec_autocmds('LspRequest', { + buffer = request.bufnr, + modeline = false, + data = { client_id = client_id, request_id = id, request = request }, + }) end return rpc.notify('$/cancelRequest', { id = id }) end diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index aef08be820..048b8d6631 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -72,6 +72,7 @@ return { 'InsertLeavePre', -- just before leaving Insert mode 'LspAttach', -- after an LSP client attaches to a buffer 'LspDetach', -- after an LSP client detaches from a buffer + 'LspRequest', -- after an LSP request is started, canceled, or completed 'LspTokenUpdate', -- after a visible LSP token is updated 'MenuPopup', -- just before popup menu is displayed 'ModeChanged', -- after changing the mode @@ -152,6 +153,7 @@ return { DiagnosticChanged=true, LspAttach=true, LspDetach=true, + LspRequest=true, LspTokenUpdate=true, RecordingEnter=true, RecordingLeave=true, diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 1a7a656d1d..e0ce62c0db 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -948,7 +948,7 @@ describe('LSP', function() test_name = "check_tracked_requests_cleared"; on_init = function(_client) command('let g:requests = 0') - command('autocmd User LspRequest let g:requests+=1') + command('autocmd LspRequest * let g:requests+=1') client = _client client.request("slow_request") eq(1, eval('g:requests'))