mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 09:44:31 +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 row = pos.line
 | 
				
			||||||
      local end_row = end_pos.line
 | 
					      local end_row = end_pos.line
 | 
				
			||||||
      local line = lines[row] or ''
 | 
					      local line = lines[row] or ''
 | 
				
			||||||
      local col = M._str_byteindex_enc(line, pos.character, offset_encoding)
 | 
					      local line_len = vim.fn.strcharlen(line)
 | 
				
			||||||
      local end_col = M._str_byteindex_enc(lines[end_row] or '', end_pos.character, offset_encoding)
 | 
					      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, {
 | 
					      table.insert(items, {
 | 
				
			||||||
        filename = filename,
 | 
					        filename = filename,
 | 
				
			||||||
        lnum = row + 1,
 | 
					        lnum = row + 1,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2673,7 +2673,7 @@ describe('LSP', function()
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  describe('lsp.util.locations_to_items', function()
 | 
					  describe('lsp.util.locations_to_items', function()
 | 
				
			||||||
    it('Convert Location[] to items', function()
 | 
					    it('Convert Location[] to items', function()
 | 
				
			||||||
      local expected = {
 | 
					      local expected_template = {
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          filename = '/fake/uri',
 | 
					          filename = '/fake/uri',
 | 
				
			||||||
          lnum = 1,
 | 
					          lnum = 1,
 | 
				
			||||||
@@ -2681,20 +2681,11 @@ describe('LSP', function()
 | 
				
			|||||||
          col = 3,
 | 
					          col = 3,
 | 
				
			||||||
          end_col = 4,
 | 
					          end_col = 4,
 | 
				
			||||||
          text = 'testing',
 | 
					          text = 'testing',
 | 
				
			||||||
          user_data = {
 | 
					          user_data = {},
 | 
				
			||||||
            uri = 'file:///fake/uri',
 | 
					 | 
				
			||||||
            range = {
 | 
					 | 
				
			||||||
              start = { line = 0, character = 2 },
 | 
					 | 
				
			||||||
              ['end'] = { line = 1, character = 3 },
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      local actual = exec_lua(function()
 | 
					      local test_params = {
 | 
				
			||||||
        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 = {
 | 
					 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            uri = 'file:///fake/uri',
 | 
					            uri = 'file:///fake/uri',
 | 
				
			||||||
            range = {
 | 
					            range = {
 | 
				
			||||||
@@ -2702,10 +2693,29 @@ describe('LSP', function()
 | 
				
			|||||||
              ['end'] = { line = 1, character = 3 },
 | 
					              ['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')
 | 
					      for _, params in ipairs(test_params) do
 | 
				
			||||||
      end)
 | 
					        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)
 | 
					        eq(expected, actual)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
    end)
 | 
					    end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('Convert LocationLink[] to items', function()
 | 
					    it('Convert LocationLink[] to items', function()
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user