mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			252 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			252 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
---@diagnostic disable: no-unknown
 | 
						|
local helpers = require('test.functional.helpers')(after_each)
 | 
						|
local eq = helpers.eq
 | 
						|
local exec_lua = helpers.exec_lua
 | 
						|
 | 
						|
--- Convert completion results.
 | 
						|
---
 | 
						|
---@param line string line contents. Mark cursor position with `|`
 | 
						|
---@param candidates lsp.CompletionList|lsp.CompletionItem[]
 | 
						|
---@param lnum? integer 0-based, defaults to 0
 | 
						|
---@return {items: table[], server_start_boundary: integer?}
 | 
						|
local function complete(line, candidates, lnum)
 | 
						|
  lnum = lnum or 0
 | 
						|
  -- nvim_win_get_cursor returns 0 based column, line:find returns 1 based
 | 
						|
  local cursor_col = line:find('|') - 1
 | 
						|
  line = line:gsub('|', '')
 | 
						|
  return exec_lua(
 | 
						|
    [[
 | 
						|
    local line, cursor_col, lnum, result = ...
 | 
						|
    local line_to_cursor = line:sub(1, cursor_col)
 | 
						|
    local client_start_boundary = vim.fn.match(line_to_cursor, '\\k*$')
 | 
						|
    local items, server_start_boundary = require("vim.lsp._completion")._convert_results(
 | 
						|
      line,
 | 
						|
      lnum,
 | 
						|
      cursor_col,
 | 
						|
      client_start_boundary,
 | 
						|
      nil,
 | 
						|
      result,
 | 
						|
      "utf-16"
 | 
						|
    )
 | 
						|
    return {
 | 
						|
      items = items,
 | 
						|
      server_start_boundary = server_start_boundary
 | 
						|
    }
 | 
						|
  ]],
 | 
						|
    line,
 | 
						|
    cursor_col,
 | 
						|
    lnum,
 | 
						|
    candidates
 | 
						|
  )
 | 
						|
end
 | 
						|
 | 
						|
describe('vim.lsp._completion', function()
 | 
						|
  before_each(helpers.clear)
 | 
						|
 | 
						|
  -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
 | 
						|
  it('prefers textEdit over label as word', function()
 | 
						|
    local range0 = {
 | 
						|
      start = { line = 0, character = 0 },
 | 
						|
      ['end'] = { line = 0, character = 0 },
 | 
						|
    }
 | 
						|
    local completion_list = {
 | 
						|
      -- resolves into label
 | 
						|
      { label = 'foobar', sortText = 'a', documentation = 'documentation' },
 | 
						|
      {
 | 
						|
        label = 'foobar',
 | 
						|
        sortText = 'b',
 | 
						|
        documentation = { value = 'documentation' },
 | 
						|
      },
 | 
						|
      -- resolves into insertText
 | 
						|
      { label = 'foocar', sortText = 'c', insertText = 'foobar' },
 | 
						|
      { label = 'foocar', sortText = 'd', insertText = 'foobar' },
 | 
						|
      -- resolves into textEdit.newText
 | 
						|
      {
 | 
						|
        label = 'foocar',
 | 
						|
        sortText = 'e',
 | 
						|
        insertText = 'foodar',
 | 
						|
        textEdit = { newText = 'foobar', range = range0 },
 | 
						|
      },
 | 
						|
      { label = 'foocar', sortText = 'f', textEdit = { newText = 'foobar', range = range0 } },
 | 
						|
      -- real-world snippet text
 | 
						|
      {
 | 
						|
        label = 'foocar',
 | 
						|
        sortText = 'g',
 | 
						|
        insertText = 'foodar',
 | 
						|
        insertTextFormat = 2,
 | 
						|
        textEdit = {
 | 
						|
          newText = 'foobar(${1:place holder}, ${2:more ...holder{\\}})',
 | 
						|
          range = range0,
 | 
						|
        },
 | 
						|
      },
 | 
						|
      {
 | 
						|
        label = 'foocar',
 | 
						|
        sortText = 'h',
 | 
						|
        insertText = 'foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}',
 | 
						|
        insertTextFormat = 2,
 | 
						|
      },
 | 
						|
      -- nested snippet tokens
 | 
						|
      {
 | 
						|
        label = 'foocar',
 | 
						|
        sortText = 'i',
 | 
						|
        insertText = 'foodar(${1:${2|typ1,typ2|}}) {$0\\}',
 | 
						|
        insertTextFormat = 2,
 | 
						|
      },
 | 
						|
      -- braced tabstop
 | 
						|
      { label = 'foocar', sortText = 'j', insertText = 'foodar()${0}', insertTextFormat = 2 },
 | 
						|
      -- plain text
 | 
						|
      {
 | 
						|
        label = 'foocar',
 | 
						|
        sortText = 'k',
 | 
						|
        insertText = 'foodar(${1:var1})',
 | 
						|
        insertTextFormat = 1,
 | 
						|
      },
 | 
						|
    }
 | 
						|
    local expected = {
 | 
						|
      {
 | 
						|
        abbr = 'foobar',
 | 
						|
        word = 'foobar',
 | 
						|
      },
 | 
						|
      {
 | 
						|
        abbr = 'foobar',
 | 
						|
        word = 'foobar',
 | 
						|
      },
 | 
						|
      {
 | 
						|
        abbr = 'foocar',
 | 
						|
        word = 'foobar',
 | 
						|
      },
 | 
						|
      {
 | 
						|
        abbr = 'foocar',
 | 
						|
        word = 'foobar',
 | 
						|
      },
 | 
						|
      {
 | 
						|
        abbr = 'foocar',
 | 
						|
        word = 'foobar',
 | 
						|
      },
 | 
						|
      {
 | 
						|
        abbr = 'foocar',
 | 
						|
        word = 'foobar',
 | 
						|
      },
 | 
						|
      {
 | 
						|
        abbr = 'foocar',
 | 
						|
        word = 'foobar(place holder, more ...holder{})',
 | 
						|
      },
 | 
						|
      {
 | 
						|
        abbr = 'foocar',
 | 
						|
        word = 'foodar(var1 typ1, var2 *typ2) {}',
 | 
						|
      },
 | 
						|
      {
 | 
						|
        abbr = 'foocar',
 | 
						|
        word = 'foodar(typ1) {}',
 | 
						|
      },
 | 
						|
      {
 | 
						|
        abbr = 'foocar',
 | 
						|
        word = 'foodar()',
 | 
						|
      },
 | 
						|
      {
 | 
						|
        abbr = 'foocar',
 | 
						|
        word = 'foodar(${1:var1})',
 | 
						|
      },
 | 
						|
    }
 | 
						|
    local result = complete('|', completion_list)
 | 
						|
    result = vim.tbl_map(function(x)
 | 
						|
      return {
 | 
						|
        abbr = x.abbr,
 | 
						|
        word = x.word,
 | 
						|
      }
 | 
						|
    end, result.items)
 | 
						|
    eq(expected, result)
 | 
						|
  end)
 | 
						|
  it('uses correct start boundary', function()
 | 
						|
    local completion_list = {
 | 
						|
      isIncomplete = false,
 | 
						|
      items = {
 | 
						|
        {
 | 
						|
          filterText = 'this_thread',
 | 
						|
          insertText = 'this_thread',
 | 
						|
          insertTextFormat = 1,
 | 
						|
          kind = 9,
 | 
						|
          label = ' this_thread',
 | 
						|
          score = 1.3205767869949,
 | 
						|
          sortText = '4056f757this_thread',
 | 
						|
          textEdit = {
 | 
						|
            newText = 'this_thread',
 | 
						|
            range = {
 | 
						|
              start = { line = 0, character = 7 },
 | 
						|
              ['end'] = { line = 0, character = 11 },
 | 
						|
            },
 | 
						|
          },
 | 
						|
        },
 | 
						|
      },
 | 
						|
    }
 | 
						|
    local expected = {
 | 
						|
      abbr = ' this_thread',
 | 
						|
      dup = 1,
 | 
						|
      empty = 1,
 | 
						|
      icase = 1,
 | 
						|
      kind = 'Module',
 | 
						|
      menu = '',
 | 
						|
      word = 'this_thread',
 | 
						|
    }
 | 
						|
    local result = complete('  std::this|', completion_list)
 | 
						|
    eq(7, result.server_start_boundary)
 | 
						|
    local item = result.items[1]
 | 
						|
    item.user_data = nil
 | 
						|
    eq(expected, item)
 | 
						|
  end)
 | 
						|
 | 
						|
  it('should search from start boundary to cursor position', function()
 | 
						|
    local completion_list = {
 | 
						|
      isIncomplete = false,
 | 
						|
      items = {
 | 
						|
        {
 | 
						|
          filterText = 'this_thread',
 | 
						|
          insertText = 'this_thread',
 | 
						|
          insertTextFormat = 1,
 | 
						|
          kind = 9,
 | 
						|
          label = ' this_thread',
 | 
						|
          score = 1.3205767869949,
 | 
						|
          sortText = '4056f757this_thread',
 | 
						|
          textEdit = {
 | 
						|
            newText = 'this_thread',
 | 
						|
            range = {
 | 
						|
              start = { line = 0, character = 7 },
 | 
						|
              ['end'] = { line = 0, character = 11 },
 | 
						|
            },
 | 
						|
          },
 | 
						|
        },
 | 
						|
        {
 | 
						|
          filterText = 'notthis_thread',
 | 
						|
          insertText = 'notthis_thread',
 | 
						|
          insertTextFormat = 1,
 | 
						|
          kind = 9,
 | 
						|
          label = ' notthis_thread',
 | 
						|
          score = 1.3205767869949,
 | 
						|
          sortText = '4056f757this_thread',
 | 
						|
          textEdit = {
 | 
						|
            newText = 'notthis_thread',
 | 
						|
            range = {
 | 
						|
              start = { line = 0, character = 7 },
 | 
						|
              ['end'] = { line = 0, character = 11 },
 | 
						|
            },
 | 
						|
          },
 | 
						|
        },
 | 
						|
      },
 | 
						|
    }
 | 
						|
    local expected = {
 | 
						|
      abbr = ' this_thread',
 | 
						|
      dup = 1,
 | 
						|
      empty = 1,
 | 
						|
      icase = 1,
 | 
						|
      kind = 'Module',
 | 
						|
      menu = '',
 | 
						|
      word = 'this_thread',
 | 
						|
    }
 | 
						|
    local result = complete('  std::this|is', completion_list)
 | 
						|
    eq(1, #result.items)
 | 
						|
    local item = result.items[1]
 | 
						|
    item.user_data = nil
 | 
						|
    eq(expected, item)
 | 
						|
  end)
 | 
						|
end)
 |