fix(lsp): use buffer scheme for files not stored on disk (#22407)

Sending `didOpen` with a `file` scheme causes problems with some
language servers because they expect the file to exist on disk.

See https://github.com/microsoft/language-server-protocol/pull/1679
This commit is contained in:
Mathias Fußenegger 2023-03-01 15:33:13 +01:00 committed by GitHub
parent d66832c76d
commit 896d672736
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 70 additions and 30 deletions

View File

@ -367,7 +367,7 @@ do
--- @field offset_encoding "utf-8"|"utf-16"|"utf-32" --- @field offset_encoding "utf-8"|"utf-16"|"utf-32"
--- ---
--- @class CTBufferState --- @class CTBufferState
--- @field name string name of the buffer --- @field uri string uri of the buffer
--- @field lines string[] snapshot of buffer lines from last didChange --- @field lines string[] snapshot of buffer lines from last didChange
--- @field lines_tmp string[] --- @field lines_tmp string[]
--- @field pending_changes table[] List of debounced changes in incremental sync mode --- @field pending_changes table[] List of debounced changes in incremental sync mode
@ -486,8 +486,12 @@ do
if buf_state then if buf_state then
buf_state.refs = buf_state.refs + 1 buf_state.refs = buf_state.refs + 1
else else
local uri = vim.uri_from_bufnr(bufnr)
if not uv.fs_stat(api.nvim_buf_get_name(bufnr)) then
uri = uri:gsub('^file://', 'buffer://')
end
buf_state = { buf_state = {
name = api.nvim_buf_get_name(bufnr), uri = uri,
lines = {}, lines = {},
lines_tmp = {}, lines_tmp = {},
pending_changes = {}, pending_changes = {},
@ -502,12 +506,26 @@ do
end end
---@private ---@private
function changetracking._get_and_set_name(client, bufnr, name) ---@param client table
---@param bufnr integer
---@return string uri
function changetracking._get_uri(client, bufnr)
local state = state_by_group[get_group(client)] or {} local state = state_by_group[get_group(client)] or {}
local buf_state = (state.buffers or {})[bufnr] local buf_state = (state.buffers or {})[bufnr]
local old_name = buf_state.name return assert(buf_state.uri, 'Must have an URI set')
buf_state.name = name end
return old_name
---@private
---@param client table
---@param bufnr integer
---@param uri string
---@return string uri
function changetracking._get_and_set_uri(client, bufnr, uri)
local state = state_by_group[get_group(client)] or {}
local buf_state = (state.buffers or {})[bufnr]
local old_uri = buf_state.uri
buf_state.uri = uri
return old_uri
end end
---@private ---@private
@ -594,7 +612,7 @@ do
{ text = buf_get_full_text(bufnr) }, { text = buf_get_full_text(bufnr) },
} }
end end
local uri = vim.uri_from_bufnr(bufnr) local uri = buf_state.uri
for _, client in pairs(state.clients) do for _, client in pairs(state.clients) do
if not client.is_stopped() and lsp.buf_is_attached(bufnr, client.id) then if not client.is_stopped() and lsp.buf_is_attached(bufnr, client.id) then
client.notify('textDocument/didChange', { client.notify('textDocument/didChange', {
@ -707,11 +725,14 @@ local function text_document_did_open_handler(bufnr, client)
return return
end end
local filetype = nvim_buf_get_option(bufnr, 'filetype') local filetype = nvim_buf_get_option(bufnr, 'filetype')
local uri = vim.uri_from_bufnr(bufnr)
if not uv.fs_stat(api.nvim_buf_get_name(bufnr)) then
uri = uri:gsub('^file://', 'buffer://')
end
local params = { local params = {
textDocument = { textDocument = {
version = 0, version = 0,
uri = vim.uri_from_bufnr(bufnr), uri = uri,
languageId = client.config.get_language_id(bufnr, filetype), languageId = client.config.get_language_id(bufnr, filetype),
text = buf_get_full_text(bufnr), text = buf_get_full_text(bufnr),
}, },
@ -1560,8 +1581,13 @@ local function text_document_did_save_handler(bufnr)
local text = once(buf_get_full_text) local text = once(buf_get_full_text)
for_each_buffer_client(bufnr, function(client) for_each_buffer_client(bufnr, function(client)
local name = api.nvim_buf_get_name(bufnr) local name = api.nvim_buf_get_name(bufnr)
local old_name = changetracking._get_and_set_name(client, bufnr, name) local old_uri = changetracking._get_and_set_uri(client, bufnr, uri)
if old_name and name ~= old_name then if old_uri and name ~= old_uri then
client.notify('textDocument/didClose', {
textDocument = {
uri = old_uri,
},
})
client.notify('textDocument/didOpen', { client.notify('textDocument/didOpen', {
textDocument = { textDocument = {
version = 0, version = 0,
@ -1664,8 +1690,12 @@ function lsp.buf_attach_client(bufnr, client_id)
end) end)
end, end,
on_detach = function() on_detach = function()
local params = { textDocument = { uri = uri } }
for_each_buffer_client(bufnr, function(client, _) for_each_buffer_client(bufnr, function(client, _)
local params = {
textDocument = {
uri = changetracking._get_uri(client, bufnr),
},
}
changetracking.reset_buf(client, bufnr) changetracking.reset_buf(client, bufnr)
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
client.notify('textDocument/didClose', params) client.notify('textDocument/didClose', params)

View File

@ -2032,7 +2032,12 @@ end
---@returns `TextDocumentIdentifier` ---@returns `TextDocumentIdentifier`
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier
function M.make_text_document_params(bufnr) function M.make_text_document_params(bufnr)
return { uri = vim.uri_from_bufnr(bufnr or 0) } bufnr = bufnr or 0
local uri = vim.uri_from_bufnr(bufnr)
if not uv.fs_stat(api.nvim_buf_get_name(bufnr)) then
uri = uri:gsub('^file://', 'buffer://')
end
return { uri = uri }
end end
--- Create the workspace params --- Create the workspace params
@ -2065,7 +2070,7 @@ function M.make_formatting_params(options)
insertSpaces = vim.bo.expandtab, insertSpaces = vim.bo.expandtab,
}) })
return { return {
textDocument = { uri = vim.uri_from_bufnr(0) }, textDocument = M.make_text_document_params(0),
options = options, options = options,
} }
end end

View File

@ -272,6 +272,7 @@ function tests.text_document_save_did_open()
end; end;
body = function() body = function()
notify('start') notify('start')
expect_notification('textDocument/didClose')
expect_notification('textDocument/didOpen') expect_notification('textDocument/didOpen')
expect_notification('textDocument/didSave') expect_notification('textDocument/didSave')
notify('shutdown') notify('shutdown')
@ -292,6 +293,8 @@ function tests.text_document_sync_save_bool()
end; end;
body = function() body = function()
notify('start') notify('start')
expect_notification('textDocument/didClose')
expect_notification('textDocument/didOpen')
expect_notification('textDocument/didSave', {textDocument = { uri = "file://" }}) expect_notification('textDocument/didSave', {textDocument = { uri = "file://" }})
notify('shutdown') notify('shutdown')
end; end;
@ -313,6 +316,8 @@ function tests.text_document_sync_save_includeText()
end; end;
body = function() body = function()
notify('start') notify('start')
expect_notification('textDocument/didClose')
expect_notification('textDocument/didOpen')
expect_notification('textDocument/didSave', { expect_notification('textDocument/didSave', {
textDocument = { textDocument = {
uri = "file://" uri = "file://"
@ -459,7 +464,7 @@ function tests.basic_check_buffer_open()
textDocument = { textDocument = {
languageId = ""; languageId = "";
text = table.concat({"testing"; "123"}, "\n") .. '\n'; text = table.concat({"testing"; "123"}, "\n") .. '\n';
uri = "file://"; uri = "buffer://";
version = 0; version = 0;
}; };
}) })
@ -486,13 +491,13 @@ function tests.basic_check_buffer_open_and_change()
textDocument = { textDocument = {
languageId = ""; languageId = "";
text = table.concat({"testing"; "123"}, "\n") .. '\n'; text = table.concat({"testing"; "123"}, "\n") .. '\n';
uri = "file://"; uri = "buffer://";
version = 0; version = 0;
}; };
}) })
expect_notification('textDocument/didChange', { expect_notification('textDocument/didChange', {
textDocument = { textDocument = {
uri = "file://"; uri = "buffer://";
version = 3; version = 3;
}; };
contentChanges = { contentChanges = {
@ -522,13 +527,13 @@ function tests.basic_check_buffer_open_and_change_noeol()
textDocument = { textDocument = {
languageId = ""; languageId = "";
text = table.concat({"testing"; "123"}, "\n"); text = table.concat({"testing"; "123"}, "\n");
uri = "file://"; uri = "buffer://";
version = 0; version = 0;
}; };
}) })
expect_notification('textDocument/didChange', { expect_notification('textDocument/didChange', {
textDocument = { textDocument = {
uri = "file://"; uri = "buffer://";
version = 3; version = 3;
}; };
contentChanges = { contentChanges = {
@ -557,13 +562,13 @@ function tests.basic_check_buffer_open_and_change_multi()
textDocument = { textDocument = {
languageId = ""; languageId = "";
text = table.concat({"testing"; "123"}, "\n") .. '\n'; text = table.concat({"testing"; "123"}, "\n") .. '\n';
uri = "file://"; uri = "buffer://";
version = 0; version = 0;
}; };
}) })
expect_notification('textDocument/didChange', { expect_notification('textDocument/didChange', {
textDocument = { textDocument = {
uri = "file://"; uri = "buffer://";
version = 3; version = 3;
}; };
contentChanges = { contentChanges = {
@ -572,7 +577,7 @@ function tests.basic_check_buffer_open_and_change_multi()
}) })
expect_notification('textDocument/didChange', { expect_notification('textDocument/didChange', {
textDocument = { textDocument = {
uri = "file://"; uri = "buffer://";
version = 4; version = 4;
}; };
contentChanges = { contentChanges = {
@ -602,13 +607,13 @@ function tests.basic_check_buffer_open_and_change_multi_and_close()
textDocument = { textDocument = {
languageId = ""; languageId = "";
text = table.concat({"testing"; "123"}, "\n") .. '\n'; text = table.concat({"testing"; "123"}, "\n") .. '\n';
uri = "file://"; uri = "buffer://";
version = 0; version = 0;
}; };
}) })
expect_notification('textDocument/didChange', { expect_notification('textDocument/didChange', {
textDocument = { textDocument = {
uri = "file://"; uri = "buffer://";
version = 3; version = 3;
}; };
contentChanges = { contentChanges = {
@ -617,7 +622,7 @@ function tests.basic_check_buffer_open_and_change_multi_and_close()
}) })
expect_notification('textDocument/didChange', { expect_notification('textDocument/didChange', {
textDocument = { textDocument = {
uri = "file://"; uri = "buffer://";
version = 4; version = 4;
}; };
contentChanges = { contentChanges = {
@ -626,7 +631,7 @@ function tests.basic_check_buffer_open_and_change_multi_and_close()
}) })
expect_notification('textDocument/didClose', { expect_notification('textDocument/didClose', {
textDocument = { textDocument = {
uri = "file://"; uri = "buffer://";
}; };
}) })
expect_notification("finish") expect_notification("finish")
@ -660,13 +665,13 @@ function tests.basic_check_buffer_open_and_change_incremental()
textDocument = { textDocument = {
languageId = ""; languageId = "";
text = table.concat({"testing"; "123"}, "\n") .. '\n'; text = table.concat({"testing"; "123"}, "\n") .. '\n';
uri = "file://"; uri = "buffer://";
version = 0; version = 0;
}; };
}) })
expect_notification('textDocument/didChange', { expect_notification('textDocument/didChange', {
textDocument = { textDocument = {
uri = "file://"; uri = "buffer://";
version = 3; version = 3;
}; };
contentChanges = { contentChanges = {
@ -703,13 +708,13 @@ function tests.basic_check_buffer_open_and_change_incremental_editing()
textDocument = { textDocument = {
languageId = ""; languageId = "";
text = table.concat({"testing"; "123"}, "\n"); text = table.concat({"testing"; "123"}, "\n");
uri = "file://"; uri = "buffer://";
version = 0; version = 0;
}; };
}) })
expect_notification('textDocument/didChange', { expect_notification('textDocument/didChange', {
textDocument = { textDocument = {
uri = "file://"; uri = "buffer://";
version = 3; version = 3;
}; };
contentChanges = { contentChanges = {