mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	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:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							d66832c76d
						
					
				
				
					commit
					896d672736
				
			@@ -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)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 = {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user