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:
Justin M. Keyes
2026-04-30 09:09:55 -04:00
committed by GitHub
parent d147d0434d
commit 4b424a06c5
20 changed files with 243 additions and 283 deletions

View File

@@ -69,24 +69,20 @@ end
--- Create a new treesitter view.
---
---@param bufnr integer Source buffer number
---@param buf integer Source buffer number
---@param lang string|nil Language of source buffer
---
---@return vim.treesitter.dev.TSTreeView|nil
---@return string|nil Error message, if any
---
---@package
function TSTreeView:new(bufnr, lang)
bufnr = bufnr or 0
lang = lang or vim.treesitter.language.get_lang(vim.bo[bufnr].filetype)
local parser = vim.treesitter.get_parser(bufnr, lang)
function TSTreeView:new(buf, lang)
buf = buf or 0
lang = lang or vim.treesitter.language.get_lang(vim.bo[buf].filetype)
local parser = vim.treesitter.get_parser(buf, lang)
if not parser then
return nil,
string.format(
'Failed to create TSTreeView for buffer %s: no parser for lang "%s"',
bufnr,
lang
)
string.format('Failed to create TSTreeView for buffer %s: no parser for lang "%s"', buf, lang)
end
-- For each child tree (injected language), find the root of the tree and locate the node within
@@ -232,10 +228,10 @@ end
---
--- Calling this function computes the text that is displayed for each node.
---
---@param bufnr integer Buffer number to write into.
---@param buf integer Buffer number to write into.
---@package
function TSTreeView:draw(bufnr)
vim.bo[bufnr].modifiable = true
function TSTreeView:draw(buf)
vim.bo[buf].modifiable = true
local lines = {} ---@type string[]
local lang_hl_marks = {} ---@type table[]
@@ -284,18 +280,18 @@ function TSTreeView:draw(bufnr)
lines[i] = line
end
api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
api.nvim_buf_set_lines(buf, 0, -1, false, lines)
api.nvim_buf_clear_namespace(bufnr, decor_ns, 0, -1)
api.nvim_buf_clear_namespace(buf, decor_ns, 0, -1)
for i, m in ipairs(lang_hl_marks) do
api.nvim_buf_set_extmark(bufnr, decor_ns, i - 1, m.col, {
api.nvim_buf_set_extmark(buf, decor_ns, i - 1, m.col, {
hl_group = 'Title',
end_col = m.end_col,
})
end
vim.bo[bufnr].modifiable = false
vim.bo[buf].modifiable = false
end
--- Get node {i} from this View.