mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	fix(lsp): handle locations exceeding line length #30253
Problem: LSP spec [states](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position) that "if the character value is greater than the line length it defaults back to the line length", but `locations_to_items` fails in that case. Solution: Adjust locations_to_items to follow the spec. closes #28281
This commit is contained in:
		@@ -1795,8 +1795,18 @@ function M.locations_to_items(locations, offset_encoding)
 | 
			
		||||
      local row = pos.line
 | 
			
		||||
      local end_row = end_pos.line
 | 
			
		||||
      local line = lines[row] or ''
 | 
			
		||||
      local col = M._str_byteindex_enc(line, pos.character, offset_encoding)
 | 
			
		||||
      local end_col = M._str_byteindex_enc(lines[end_row] or '', end_pos.character, offset_encoding)
 | 
			
		||||
      local line_len = vim.fn.strcharlen(line)
 | 
			
		||||
      local end_line = lines[end_row] or ''
 | 
			
		||||
      local end_line_len = vim.fn.strcharlen(end_line)
 | 
			
		||||
      -- LSP spec: if character > line length, default to the line length.
 | 
			
		||||
      -- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position
 | 
			
		||||
      local col = pos.character <= line_len
 | 
			
		||||
          and M._str_byteindex_enc(line, pos.character, offset_encoding)
 | 
			
		||||
        or line_len
 | 
			
		||||
      local end_col = end_pos.character <= end_line_len
 | 
			
		||||
          and M._str_byteindex_enc(end_line, end_pos.character, offset_encoding)
 | 
			
		||||
        or end_line_len
 | 
			
		||||
 | 
			
		||||
      table.insert(items, {
 | 
			
		||||
        filename = filename,
 | 
			
		||||
        lnum = row + 1,
 | 
			
		||||
 
 | 
			
		||||
@@ -2673,7 +2673,7 @@ describe('LSP', function()
 | 
			
		||||
 | 
			
		||||
  describe('lsp.util.locations_to_items', function()
 | 
			
		||||
    it('Convert Location[] to items', function()
 | 
			
		||||
      local expected = {
 | 
			
		||||
      local expected_template = {
 | 
			
		||||
        {
 | 
			
		||||
          filename = '/fake/uri',
 | 
			
		||||
          lnum = 1,
 | 
			
		||||
@@ -2681,20 +2681,11 @@ describe('LSP', function()
 | 
			
		||||
          col = 3,
 | 
			
		||||
          end_col = 4,
 | 
			
		||||
          text = 'testing',
 | 
			
		||||
          user_data = {
 | 
			
		||||
            uri = 'file:///fake/uri',
 | 
			
		||||
            range = {
 | 
			
		||||
              start = { line = 0, character = 2 },
 | 
			
		||||
              ['end'] = { line = 1, character = 3 },
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
          user_data = {},
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
      local actual = exec_lua(function()
 | 
			
		||||
        local bufnr = vim.uri_to_bufnr('file:///fake/uri')
 | 
			
		||||
        local lines = { 'testing', '123' }
 | 
			
		||||
        vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
 | 
			
		||||
        local locations = {
 | 
			
		||||
      local test_params = {
 | 
			
		||||
        {
 | 
			
		||||
          {
 | 
			
		||||
            uri = 'file:///fake/uri',
 | 
			
		||||
            range = {
 | 
			
		||||
@@ -2702,10 +2693,29 @@ describe('LSP', function()
 | 
			
		||||
              ['end'] = { line = 1, character = 3 },
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          {
 | 
			
		||||
            uri = 'file:///fake/uri',
 | 
			
		||||
            range = {
 | 
			
		||||
              start = { line = 0, character = 2 },
 | 
			
		||||
              -- LSP spec: if character > line length, default to the line length.
 | 
			
		||||
              ['end'] = { line = 1, character = 10000 },
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
        return vim.lsp.util.locations_to_items(locations, 'utf-16')
 | 
			
		||||
      end)
 | 
			
		||||
      for _, params in ipairs(test_params) do
 | 
			
		||||
        local actual = exec_lua(function(params0)
 | 
			
		||||
          local bufnr = vim.uri_to_bufnr('file:///fake/uri')
 | 
			
		||||
          local lines = { 'testing', '123' }
 | 
			
		||||
          vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
 | 
			
		||||
          return vim.lsp.util.locations_to_items(params0, 'utf-16')
 | 
			
		||||
        end, params)
 | 
			
		||||
        local expected = vim.deepcopy(expected_template)
 | 
			
		||||
        expected[1].user_data = params[1]
 | 
			
		||||
        eq(expected, actual)
 | 
			
		||||
      end
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it('Convert LocationLink[] to items', function()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user