feat(lsp): skip or reduce debounce after idle (#16881)

The idea of the debounce is to avoid overloading a server with didChange
notifications. So far this used a constant value to group changes within
an interval together and send a single notification. A side effect of
this is that when you were idle, notifications are still delayed.

This commit changes the logic to take the time the last notification
happened into consideration, if it has been greater than the debounce
interval, the debouncing is skipped or at least reduced.
This commit is contained in:
Mathias Fußenegger
2022-01-07 11:56:09 +01:00
committed by GitHub
parent bba679c431
commit b680392687

View File

@@ -306,7 +306,6 @@ local function once(fn)
end end
end end
local changetracking = {} local changetracking = {}
do do
--@private --@private
@@ -327,6 +326,7 @@ do
if not state then if not state then
state = { state = {
pending_changes = {}; pending_changes = {};
last_flush = {};
use_incremental_sync = ( use_incremental_sync = (
if_nil(client.config.flags.allow_incremental_sync, true) if_nil(client.config.flags.allow_incremental_sync, true)
and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental
@@ -347,9 +347,12 @@ do
function changetracking.reset_buf(client, bufnr) function changetracking.reset_buf(client, bufnr)
changetracking.flush(client) changetracking.flush(client)
local state = state_by_client[client.id] local state = state_by_client[client.id]
if state and state.buffers then if state then
if state.buffers then
state.buffers[bufnr] = nil state.buffers[bufnr] = nil
end end
state.last_flush = {}
end
end end
---@private ---@private
@@ -361,6 +364,33 @@ do
end end
end end
---@private
--
-- Adjust debounce time by taking time of last didChange notification into
-- consideration. If the last didChange happened more than `debounce` time ago,
-- debounce can be skipped and otherwise maybe reduced.
--
-- This turns the debounce into a kind of client rate limiting
local function next_debounce(debounce, state, bufnr)
if debounce == 0 then
return 0
end
local ns_to_ms = 0.000001
local last_flush = state.last_flush[bufnr]
if not last_flush then
return debounce
end
local now = uv.hrtime()
local ms_since_last_flush = (now - last_flush) * ns_to_ms
local remaining_debounce = debounce - ms_since_last_flush
if remaining_debounce > 0 then
return remaining_debounce
else
state.last_flush[bufnr] = now
return 0
end
end
---@private ---@private
function changetracking.prepare(bufnr, firstline, lastline, new_lastline) function changetracking.prepare(bufnr, firstline, lastline, new_lastline)
local incremental_changes = function(client) local incremental_changes = function(client)
@@ -383,7 +413,7 @@ do
return return
end end
local state = state_by_client[client.id] local state = state_by_client[client.id]
local debounce = client.config.flags.debounce_text_changes or 150 local debounce = next_debounce(client.config.flags.debounce_text_changes or 150, state, bufnr)
if debounce == 0 then if debounce == 0 then
local changes = state.use_incremental_sync and incremental_changes(client) or full_changes() local changes = state.use_incremental_sync and incremental_changes(client) or full_changes()
client.notify("textDocument/didChange", { client.notify("textDocument/didChange", {
@@ -406,6 +436,7 @@ do
end end
state.pending_change = function() state.pending_change = function()
state.pending_change = nil state.pending_change = nil
state.last_flush[bufnr] = uv.hrtime()
if client.is_stopped() or not vim.api.nvim_buf_is_valid(bufnr) then if client.is_stopped() or not vim.api.nvim_buf_is_valid(bufnr) then
return return
end end