mirror of
https://github.com/neovim/neovim.git
synced 2026-06-18 01:31:14 +00:00
backport fix(lsp): send didClose, didOpen when languageId changes (#39519)
fix(lsp): send didClose, didOpen when languageId changes
Problem:
If a buffer's filetype changes after the LSP client has already
attached (e.g. from json to jsonc via a modeline), but the client
supports both filetypes, it stays attached. It does not notify the
server of the new languageId, causing the server to incorrectly process
the file using the old languageId.
Solution:
Save the languageId used during textDocument/didOpen, and send
textDocument/didClose + textDocument/didOpen when buffer's languageId
changed.
Lsp spec:
0003fb53f1/_specifications/lsp/3.18/textDocument/didOpen.md (L5)
> If the language id of a document changes, the client
> needs to send a textDocument/didClose to the server followed by a
> textDocument/didOpen with the new language id if the server handles
> the new language id as well.
AI-assisted: Gemini 3.1 Pro
Co-authored-by: phanium <91544758+phanen@users.noreply.github.com>
This commit is contained in:
@@ -26,23 +26,23 @@ M.minimum_language_version = vim._ts_get_minimum_language_version()
|
||||
---
|
||||
--- It is not recommended to use this; use |get_parser()| instead.
|
||||
---
|
||||
---@param bufnr integer Buffer the parser will be tied to (0 for current buffer)
|
||||
---@param buf integer Buffer the parser will be tied to (0 for current buffer)
|
||||
---@param lang string Language of the parser
|
||||
---@param opts (table|nil) Options to pass to the created language tree
|
||||
---
|
||||
---@return vim.treesitter.LanguageTree object to use for parsing
|
||||
function M._create_parser(bufnr, lang, opts)
|
||||
bufnr = vim._resolve_bufnr(bufnr)
|
||||
function M._create_parser(buf, lang, opts)
|
||||
buf = vim._resolve_bufnr(buf)
|
||||
|
||||
local self = LanguageTree.new(bufnr, lang, opts)
|
||||
local self = LanguageTree.new(buf, lang, opts)
|
||||
|
||||
local function bytes_cb(_, ...)
|
||||
self:_on_bytes(...)
|
||||
end
|
||||
|
||||
local function detach_cb(_, ...)
|
||||
if parsers[bufnr] == self then
|
||||
parsers[bufnr] = nil
|
||||
if parsers[buf] == self then
|
||||
parsers[buf] = nil
|
||||
end
|
||||
self:_on_detach(...)
|
||||
end
|
||||
@@ -72,41 +72,41 @@ end
|
||||
---
|
||||
--- If no parser can be created, nil (and an error message) is returned.
|
||||
---
|
||||
---@param bufnr (integer|nil) Buffer the parser should be tied to (default: current buffer)
|
||||
---@param buf (integer|nil) Buffer the parser should be tied to (default: current buffer)
|
||||
---@param lang (string|nil) Language of this parser (default: from buffer filetype)
|
||||
---@param opts (table|nil) Options to pass to the created language tree
|
||||
---
|
||||
---@return vim.treesitter.LanguageTree? object to use for parsing
|
||||
---@return string? error message, if applicable
|
||||
function M.get_parser(bufnr, lang, opts)
|
||||
function M.get_parser(buf, lang, opts)
|
||||
opts = opts or {}
|
||||
|
||||
bufnr = vim._resolve_bufnr(bufnr)
|
||||
buf = vim._resolve_bufnr(buf)
|
||||
|
||||
if not valid_lang(lang) then
|
||||
lang = M.language.get_lang(vim.bo[bufnr].filetype)
|
||||
lang = M.language.get_lang(vim.bo[buf].filetype)
|
||||
end
|
||||
|
||||
if not valid_lang(lang) then
|
||||
if not parsers[bufnr] then
|
||||
if not parsers[buf] then
|
||||
return nil,
|
||||
string.format('Parser not found for buffer %s: language could not be determined', bufnr)
|
||||
string.format('Parser not found for buffer %s: language could not be determined', buf)
|
||||
end
|
||||
elseif parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then
|
||||
if not api.nvim_buf_is_loaded(bufnr) then
|
||||
return nil, string.format('Buffer %s must be loaded to create parser', bufnr)
|
||||
elseif parsers[buf] == nil or parsers[buf]:lang() ~= lang then
|
||||
if not api.nvim_buf_is_loaded(buf) then
|
||||
return nil, string.format('Buffer %s must be loaded to create parser', buf)
|
||||
end
|
||||
local parser = vim.F.npcall(M._create_parser, bufnr, lang, opts)
|
||||
local parser = vim.F.npcall(M._create_parser, buf, lang, opts)
|
||||
if not parser then
|
||||
return nil,
|
||||
string.format('Parser could not be created for buffer %s and language "%s"', bufnr, lang)
|
||||
string.format('Parser could not be created for buffer %s and language "%s"', buf, lang)
|
||||
end
|
||||
parsers[bufnr] = parser
|
||||
parsers[buf] = parser
|
||||
end
|
||||
|
||||
parsers[bufnr]:register_cbs(opts.buf_attach_cbs)
|
||||
parsers[buf]:register_cbs(opts.buf_attach_cbs)
|
||||
|
||||
return parsers[bufnr]
|
||||
return parsers[buf]
|
||||
end
|
||||
|
||||
--- Returns a string parser
|
||||
@@ -268,14 +268,14 @@ end
|
||||
--- language, a table of metadata (`priority`, `conceal`, ...; empty if none are defined), and the
|
||||
--- id of the capture.
|
||||
---
|
||||
---@param bufnr integer Buffer number (0 for current buffer)
|
||||
---@param buf integer Buffer number (0 for current buffer)
|
||||
---@param row integer Position row
|
||||
---@param col integer Position column
|
||||
---
|
||||
---@return {capture: string, lang: string, metadata: vim.treesitter.query.TSMetadata, id: integer}[]
|
||||
function M.get_captures_at_pos(bufnr, row, col)
|
||||
bufnr = vim._resolve_bufnr(bufnr)
|
||||
local buf_highlighter = M.highlighter.active[bufnr]
|
||||
function M.get_captures_at_pos(buf, row, col)
|
||||
buf = vim._resolve_bufnr(buf)
|
||||
local buf_highlighter = M.highlighter.active[buf]
|
||||
|
||||
if not buf_highlighter then
|
||||
return {}
|
||||
@@ -328,13 +328,13 @@ end
|
||||
|
||||
--- Returns a list of highlight capture names under the cursor
|
||||
---
|
||||
---@param winnr (integer|nil): |window-ID| or 0 for current window (default)
|
||||
---@param win (integer|nil): |window-ID| or 0 for current window (default)
|
||||
---
|
||||
---@return string[] List of capture names
|
||||
function M.get_captures_at_cursor(winnr)
|
||||
winnr = winnr or 0
|
||||
local bufnr = api.nvim_win_get_buf(winnr)
|
||||
local cursor = api.nvim_win_get_cursor(winnr)
|
||||
function M.get_captures_at_cursor(win)
|
||||
win = win or 0
|
||||
local bufnr = api.nvim_win_get_buf(win)
|
||||
local cursor = api.nvim_win_get_cursor(win)
|
||||
|
||||
local data = M.get_captures_at_pos(bufnr, cursor[1] - 1, cursor[2])
|
||||
|
||||
@@ -434,30 +434,30 @@ end
|
||||
--- })
|
||||
--- ```
|
||||
---
|
||||
---@param bufnr integer? Buffer to be highlighted (default: current buffer)
|
||||
---@param buf integer? Buffer to be highlighted (default: current buffer)
|
||||
---@param lang string? Language of the parser (default: from buffer filetype)
|
||||
function M.start(bufnr, lang)
|
||||
bufnr = vim._resolve_bufnr(bufnr)
|
||||
function M.start(buf, lang)
|
||||
buf = vim._resolve_bufnr(buf)
|
||||
-- Ensure buffer is loaded. `:edit` over `bufload()` to show swapfile prompt.
|
||||
if not api.nvim_buf_is_loaded(bufnr) then
|
||||
if api.nvim_buf_get_name(bufnr) ~= '' then
|
||||
pcall(api.nvim_buf_call, bufnr, vim.cmd.edit)
|
||||
if not api.nvim_buf_is_loaded(buf) then
|
||||
if api.nvim_buf_get_name(buf) ~= '' then
|
||||
pcall(api.nvim_buf_call, buf, vim.cmd.edit)
|
||||
else
|
||||
vim.fn.bufload(bufnr)
|
||||
vim.fn.bufload(buf)
|
||||
end
|
||||
end
|
||||
local parser = assert(M.get_parser(bufnr, lang))
|
||||
local parser = assert(M.get_parser(buf, lang))
|
||||
M.highlighter.new(parser)
|
||||
end
|
||||
|
||||
--- Stops treesitter highlighting for a buffer
|
||||
---
|
||||
---@param bufnr (integer|nil) Buffer to stop highlighting (default: current buffer)
|
||||
function M.stop(bufnr)
|
||||
bufnr = vim._resolve_bufnr(bufnr)
|
||||
---@param buf (integer|nil) Buffer to stop highlighting (default: current buffer)
|
||||
function M.stop(buf)
|
||||
buf = vim._resolve_bufnr(buf)
|
||||
|
||||
if M.highlighter.active[bufnr] then
|
||||
M.highlighter.active[bufnr]:destroy()
|
||||
if M.highlighter.active[buf] then
|
||||
M.highlighter.active[buf]:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user