aboutsummaryrefslogtreecommitdiff
path: root/test/functional/plugin/lsp
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional/plugin/lsp')
-rw-r--r--test/functional/plugin/lsp/codelens_spec.lua84
-rw-r--r--test/functional/plugin/lsp/completion_spec.lua547
-rw-r--r--test/functional/plugin/lsp/diagnostic_spec.lua536
-rw-r--r--test/functional/plugin/lsp/handler_spec.lua23
-rw-r--r--test/functional/plugin/lsp/incremental_sync_spec.lua91
-rw-r--r--test/functional/plugin/lsp/inlay_hint_spec.lua335
-rw-r--r--test/functional/plugin/lsp/semantic_tokens_spec.lua378
-rw-r--r--test/functional/plugin/lsp/testutil.lua125
-rw-r--r--test/functional/plugin/lsp/utils_spec.lua104
9 files changed, 1463 insertions, 760 deletions
diff --git a/test/functional/plugin/lsp/codelens_spec.lua b/test/functional/plugin/lsp/codelens_spec.lua
index cd20e95dd1..20ef1cb49e 100644
--- a/test/functional/plugin/lsp/codelens_spec.lua
+++ b/test/functional/plugin/lsp/codelens_spec.lua
@@ -13,36 +13,34 @@ describe('vim.lsp.codelens', function()
it('on_codelens_stores_and_displays_lenses', function()
local fake_uri = 'file:///fake/uri'
- local bufnr = exec_lua(
- [[
- fake_uri = ...
+ local bufnr = exec_lua(function()
local bufnr = vim.uri_to_bufnr(fake_uri)
- local lines = {'So', 'many', 'lines'}
+ local lines = { 'So', 'many', 'lines' }
vim.fn.bufload(bufnr)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
return bufnr
- ]],
- fake_uri
- )
+ end)
- exec_lua(
- [[
- local bufnr = ...
+ exec_lua(function()
local lenses = {
{
range = {
- start = { line = 0, character = 0, },
- ['end'] = { line = 0, character = 0 }
+ start = { line = 0, character = 0 },
+ ['end'] = { line = 0, character = 0 },
},
- command = { title = 'Lens1', command = 'Dummy' }
+ command = { title = 'Lens1', command = 'Dummy' },
},
}
- vim.lsp.codelens.on_codelens(nil, lenses, {method='textDocument/codeLens', client_id=1, bufnr=bufnr})
- ]],
- bufnr
- )
+ vim.lsp.codelens.on_codelens(
+ nil,
+ lenses,
+ { method = 'textDocument/codeLens', client_id = 1, bufnr = bufnr }
+ )
+ end)
- local stored_lenses = exec_lua('return vim.lsp.codelens.get(...)', bufnr)
+ local stored_lenses = exec_lua(function()
+ return vim.lsp.codelens.get(bufnr)
+ end)
local expected = {
{
range = {
@@ -57,58 +55,54 @@ describe('vim.lsp.codelens', function()
}
eq(expected, stored_lenses)
- local virtual_text_chunks = exec_lua(
- [[
- local bufnr = ...
+ local virtual_text_chunks = exec_lua(function()
local ns = vim.lsp.codelens.__namespaces[1]
local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, {})
return vim.api.nvim_buf_get_extmark_by_id(bufnr, ns, extmarks[1][1], { details = true })[3].virt_text
- ]],
- bufnr
- )
+ end)
eq({ [1] = { 'Lens1', 'LspCodeLens' } }, virtual_text_chunks)
end)
it('can clear all lens', function()
local fake_uri = 'file:///fake/uri'
- local bufnr = exec_lua(
- [[
- fake_uri = ...
+ local bufnr = exec_lua(function()
local bufnr = vim.uri_to_bufnr(fake_uri)
- local lines = {'So', 'many', 'lines'}
+ local lines = { 'So', 'many', 'lines' }
vim.fn.bufload(bufnr)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
return bufnr
- ]],
- fake_uri
- )
+ end)
- exec_lua(
- [[
- local bufnr = ...
+ exec_lua(function()
local lenses = {
{
range = {
- start = { line = 0, character = 0, },
- ['end'] = { line = 0, character = 0 }
+ start = { line = 0, character = 0 },
+ ['end'] = { line = 0, character = 0 },
},
- command = { title = 'Lens1', command = 'Dummy' }
+ command = { title = 'Lens1', command = 'Dummy' },
},
}
- vim.lsp.codelens.on_codelens(nil, lenses, {method='textDocument/codeLens', client_id=1, bufnr=bufnr})
- ]],
- bufnr
- )
+ vim.lsp.codelens.on_codelens(
+ nil,
+ lenses,
+ { method = 'textDocument/codeLens', client_id = 1, bufnr = bufnr }
+ )
+ end)
- local stored_lenses = exec_lua('return vim.lsp.codelens.get(...)', bufnr)
+ local stored_lenses = exec_lua(function()
+ return vim.lsp.codelens.get(bufnr)
+ end)
eq(1, #stored_lenses)
- exec_lua([[
+ exec_lua(function()
vim.lsp.codelens.clear()
- ]])
+ end)
- stored_lenses = exec_lua('return vim.lsp.codelens.get(...)', bufnr)
+ stored_lenses = exec_lua(function()
+ return vim.lsp.codelens.get(bufnr)
+ end)
eq(0, #stored_lenses)
end)
end)
diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua
index 2798d57381..4df8d77d44 100644
--- a/test/functional/plugin/lsp/completion_spec.lua
+++ b/test/functional/plugin/lsp/completion_spec.lua
@@ -1,9 +1,16 @@
---@diagnostic disable: no-unknown
local t = require('test.testutil')
+local t_lsp = require('test.functional.plugin.lsp.testutil')
local n = require('test.functional.testnvim')()
+local clear = n.clear
local eq = t.eq
+local neq = t.neq
local exec_lua = n.exec_lua
+local feed = n.feed
+local retry = t.retry
+
+local create_server_definition = t_lsp.create_server_definition
--- Convert completion results.
---
@@ -11,38 +18,32 @@ local exec_lua = n.exec_lua
---@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)
+local function complete(line, candidates, lnum, server_boundary)
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 = ...
+ return exec_lua(function(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(
+ local items, new_server_boundary = require('vim.lsp.completion')._convert_results(
line,
lnum,
cursor_col,
+ 1,
client_start_boundary,
- nil,
+ server_boundary,
result,
- "utf-16"
+ 'utf-16'
)
return {
items = items,
- server_start_boundary = server_start_boundary
+ server_start_boundary = new_server_boundary,
}
- ]],
- line,
- cursor_col,
- lnum,
- candidates
- )
+ end, candidates)
end
-describe('vim.lsp._completion', function()
+describe('vim.lsp.completion: item conversion', function()
before_each(n.clear)
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
@@ -70,39 +71,24 @@ describe('vim.lsp._completion', function()
textEdit = { newText = 'foobar', range = range0 },
},
{ label = 'foocar', sortText = 'f', textEdit = { newText = 'foobar', range = range0 } },
- -- real-world snippet text
+ -- plain text
{
label = 'foocar',
sortText = 'g',
- insertText = 'foodar',
+ insertText = 'foodar(${1:var1})',
+ insertTextFormat = 1,
+ },
+ {
+ label = '•INT16_C(c)',
+ insertText = 'INT16_C(${1:c})',
insertTextFormat = 2,
+ filterText = 'INT16_C',
+ sortText = 'h',
textEdit = {
- newText = 'foobar(${1:place holder}, ${2:more ...holder{\\}})',
+ newText = 'INT16_C(${1:c})',
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 = {
{
@@ -131,23 +117,167 @@ describe('vim.lsp._completion', function()
},
{
abbr = 'foocar',
- word = 'foobar(place holder, more ...holder{})',
+ word = 'foodar(${1:var1})', -- marked as PlainText, text is used as is
},
{
- abbr = 'foocar',
- word = 'foodar(var1 typ1, var2 *typ2) {}',
+ abbr = '•INT16_C(c)',
+ word = 'INT16_C',
},
+ }
+ 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('filters on label if filterText is missing', function()
+ local completion_list = {
+ { label = 'foo' },
+ { label = 'bar' },
+ }
+ local result = complete('fo|', completion_list)
+ local expected = {
{
- abbr = 'foocar',
- word = 'foodar(typ1) {}',
+ abbr = 'foo',
+ word = 'foo',
},
+ }
+ result = vim.tbl_map(function(x)
+ return {
+ abbr = x.abbr,
+ word = x.word,
+ }
+ end, result.items)
+ eq(expected, result)
+ end)
+
+ it('works on non word prefix', function()
+ local completion_list = {
+ { label = ' foo', insertText = '->foo' },
+ }
+ local result = complete('wp.|', completion_list, 0, 2)
+ local expected = {
{
- abbr = 'foocar',
- word = 'foodar()',
+ abbr = ' foo',
+ word = '->foo',
},
+ }
+ result = vim.tbl_map(function(x)
+ return {
+ abbr = x.abbr,
+ word = x.word,
+ }
+ end, result.items)
+ eq(expected, result)
+ end)
+
+ it('trims trailing newline or tab from textEdit', function()
+ local range0 = {
+ start = { line = 0, character = 0 },
+ ['end'] = { line = 0, character = 0 },
+ }
+ local items = {
{
- abbr = 'foocar',
- word = 'foodar(${1:var1})',
+ detail = 'ansible.builtin',
+ filterText = 'lineinfile ansible.builtin.lineinfile builtin ansible',
+ kind = 7,
+ label = 'ansible.builtin.lineinfile',
+ sortText = '2_ansible.builtin.lineinfile',
+ textEdit = {
+ newText = 'ansible.builtin.lineinfile:\n ',
+ range = range0,
+ },
+ },
+ }
+ local result = complete('|', items)
+ result = vim.tbl_map(function(x)
+ return {
+ abbr = x.abbr,
+ word = x.word,
+ }
+ end, result.items)
+
+ local expected = {
+ {
+ abbr = 'ansible.builtin.lineinfile',
+ word = 'ansible.builtin.lineinfile:',
+ },
+ }
+ eq(expected, result)
+ end)
+
+ it('prefers wordlike components for snippets', function()
+ -- There are two goals here:
+ --
+ -- 1. The `word` should match what the user started typing, so that vim.fn.complete() doesn't
+ -- filter it away, preventing snippet expansion
+ --
+ -- For example, if they type `items@ins`, luals returns `table.insert(items, $0)` as
+ -- textEdit.newText and `insert` as label.
+ -- There would be no prefix match if textEdit.newText is used as `word`
+ --
+ -- 2. If users do not expand a snippet, but continue typing, they should see a somewhat reasonable
+ -- `word` getting inserted.
+ --
+ -- For example in:
+ --
+ -- insertText: "testSuites ${1:Env}"
+ -- label: "testSuites"
+ --
+ -- "testSuites" should have priority as `word`, as long as the full snippet gets expanded on accept (<c-y>)
+ local range0 = {
+ start = { line = 0, character = 0 },
+ ['end'] = { line = 0, character = 0 },
+ }
+ local completion_list = {
+ -- luals postfix snippet (typed text: items@ins|)
+ {
+ label = 'insert',
+ insertTextFormat = 2,
+ textEdit = {
+ newText = 'table.insert(items, $0)',
+ range = range0,
+ },
+ },
+
+ -- eclipse.jdt.ls `new` snippet
+ {
+ label = 'new',
+ insertTextFormat = 2,
+ textEdit = {
+ newText = '${1:Object} ${2:foo} = new ${1}(${3});\n${0}',
+ range = range0,
+ },
+ textEditText = '${1:Object} ${2:foo} = new ${1}(${3});\n${0}',
+ },
+
+ -- eclipse.jdt.ls `List.copyO` function call completion
+ {
+ label = 'copyOf(Collection<? extends E> coll) : List<E>',
+ insertTextFormat = 2,
+ insertText = 'copyOf',
+ textEdit = {
+ newText = 'copyOf(${1:coll})',
+ range = range0,
+ },
+ },
+ }
+ local expected = {
+ {
+ abbr = 'copyOf(Collection<? extends E> coll) : List<E>',
+ word = 'copyOf',
+ },
+ {
+ abbr = 'insert',
+ word = 'insert',
+ },
+ {
+ abbr = 'new',
+ word = 'new',
},
}
local result = complete('|', completion_list)
@@ -159,6 +289,7 @@ describe('vim.lsp._completion', function()
end, result.items)
eq(expected, result)
end)
+
it('uses correct start boundary', function()
local completion_list = {
isIncomplete = false,
@@ -186,8 +317,10 @@ describe('vim.lsp._completion', function()
dup = 1,
empty = 1,
icase = 1,
+ info = '',
kind = 'Module',
menu = '',
+ hl_group = '',
word = 'this_thread',
}
local result = complete(' std::this|', completion_list)
@@ -218,7 +351,7 @@ describe('vim.lsp._completion', function()
},
},
{
- filterText = 'notthis_thread',
+ filterText = 'no_match',
insertText = 'notthis_thread',
insertTextFormat = 1,
kind = 9,
@@ -240,8 +373,10 @@ describe('vim.lsp._completion', function()
dup = 1,
empty = 1,
icase = 1,
+ info = '',
kind = 'Module',
menu = '',
+ hl_group = '',
word = 'this_thread',
}
local result = complete(' std::this|is', completion_list)
@@ -278,4 +413,316 @@ describe('vim.lsp._completion', function()
eq('item-property-has-priority', item.data)
eq({ line = 1, character = 1 }, item.textEdit.range.start)
end)
+
+ it(
+ 'uses insertText as textEdit.newText if there are editRange defaults but no textEditText',
+ function()
+ --- @type lsp.CompletionList
+ local completion_list = {
+ isIncomplete = false,
+ itemDefaults = {
+ editRange = {
+ start = { line = 1, character = 1 },
+ ['end'] = { line = 1, character = 4 },
+ },
+ insertTextFormat = 2,
+ data = 'foobar',
+ },
+ items = {
+ {
+ insertText = 'the-insertText',
+ label = 'hello',
+ data = 'item-property-has-priority',
+ },
+ },
+ }
+ local result = complete('|', completion_list)
+ eq(1, #result.items)
+ local text = result.items[1].user_data.nvim.lsp.completion_item.textEdit.newText
+ eq('the-insertText', text)
+ end
+ )
+
+ it(
+ 'defaults to label as textEdit.newText if insertText or textEditText are not present',
+ function()
+ local completion_list = {
+ isIncomplete = false,
+ itemDefaults = {
+ editRange = {
+ start = { line = 1, character = 1 },
+ ['end'] = { line = 1, character = 4 },
+ },
+ insertTextFormat = 2,
+ data = 'foobar',
+ },
+ items = {
+ {
+ label = 'hello',
+ data = 'item-property-has-priority',
+ },
+ },
+ }
+ local result = complete('|', completion_list)
+ eq(1, #result.items)
+ local text = result.items[1].user_data.nvim.lsp.completion_item.textEdit.newText
+ eq('hello', text)
+ end
+ )
+end)
+
+describe('vim.lsp.completion: protocol', function()
+ before_each(function()
+ clear()
+ exec_lua(create_server_definition)
+ exec_lua(function()
+ _G.capture = {}
+ --- @diagnostic disable-next-line:duplicate-set-field
+ vim.fn.complete = function(col, matches)
+ _G.capture.col = col
+ _G.capture.matches = matches
+ end
+ end)
+ end)
+
+ after_each(clear)
+
+ --- @param completion_result lsp.CompletionList
+ --- @return integer
+ local function create_server(completion_result)
+ return exec_lua(function()
+ local server = _G._create_server({
+ capabilities = {
+ completionProvider = {
+ triggerCharacters = { '.' },
+ },
+ },
+ handlers = {
+ ['textDocument/completion'] = function(_, _, callback)
+ callback(nil, completion_result)
+ end,
+ },
+ })
+
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.api.nvim_win_set_buf(0, bufnr)
+ return vim.lsp.start({
+ name = 'dummy',
+ cmd = server.cmd,
+ on_attach = function(client, bufnr0)
+ vim.lsp.completion.enable(true, client.id, bufnr0, {
+ convert = function(item)
+ return { abbr = item.label:gsub('%b()', '') }
+ end,
+ })
+ end,
+ })
+ end)
+ end
+
+ local function assert_matches(fn)
+ retry(nil, nil, function()
+ fn(exec_lua('return _G.capture.matches'))
+ end)
+ end
+
+ --- @param pos [integer, integer]
+ local function trigger_at_pos(pos)
+ exec_lua(function()
+ local win = vim.api.nvim_get_current_win()
+ vim.api.nvim_win_set_cursor(win, pos)
+ vim.lsp.completion.trigger()
+ end)
+
+ retry(nil, nil, function()
+ neq(nil, exec_lua('return _G.capture.col'))
+ end)
+ end
+
+ it('fetches completions and shows them using complete on trigger', function()
+ create_server({
+ isIncomplete = false,
+ items = {
+ {
+ label = 'hello',
+ },
+ {
+ label = 'hercules',
+ tags = { 1 }, -- 1 represents Deprecated tag
+ },
+ {
+ label = 'hero',
+ deprecated = true,
+ },
+ },
+ })
+
+ feed('ih')
+ trigger_at_pos({ 1, 1 })
+
+ assert_matches(function(matches)
+ eq({
+ {
+ abbr = 'hello',
+ dup = 1,
+ empty = 1,
+ icase = 1,
+ info = '',
+ kind = 'Unknown',
+ menu = '',
+ hl_group = '',
+ user_data = {
+ nvim = {
+ lsp = {
+ client_id = 1,
+ completion_item = {
+ label = 'hello',
+ },
+ },
+ },
+ },
+ word = 'hello',
+ },
+ {
+ abbr = 'hercules',
+ dup = 1,
+ empty = 1,
+ icase = 1,
+ info = '',
+ kind = 'Unknown',
+ menu = '',
+ hl_group = 'DiagnosticDeprecated',
+ user_data = {
+ nvim = {
+ lsp = {
+ client_id = 1,
+ completion_item = {
+ label = 'hercules',
+ tags = { 1 },
+ },
+ },
+ },
+ },
+ word = 'hercules',
+ },
+ {
+ abbr = 'hero',
+ dup = 1,
+ empty = 1,
+ icase = 1,
+ info = '',
+ kind = 'Unknown',
+ menu = '',
+ hl_group = 'DiagnosticDeprecated',
+ user_data = {
+ nvim = {
+ lsp = {
+ client_id = 1,
+ completion_item = {
+ label = 'hero',
+ deprecated = true,
+ },
+ },
+ },
+ },
+ word = 'hero',
+ },
+ }, matches)
+ end)
+ end)
+
+ it('merges results from multiple clients', function()
+ create_server({
+ isIncomplete = false,
+ items = {
+ {
+ label = 'hello',
+ },
+ },
+ })
+ create_server({
+ isIncomplete = false,
+ items = {
+ {
+ label = 'hallo',
+ },
+ },
+ })
+
+ feed('ih')
+ trigger_at_pos({ 1, 1 })
+
+ assert_matches(function(matches)
+ eq(2, #matches)
+ eq('hello', matches[1].word)
+ eq('hallo', matches[2].word)
+ end)
+ end)
+
+ it('executes commands', function()
+ local completion_list = {
+ isIncomplete = false,
+ items = {
+ {
+ label = 'hello',
+ command = {
+ arguments = { '1', '0' },
+ command = 'dummy',
+ title = '',
+ },
+ },
+ },
+ }
+ local client_id = create_server(completion_list)
+
+ exec_lua(function()
+ _G.called = false
+ local client = assert(vim.lsp.get_client_by_id(client_id))
+ client.commands.dummy = function()
+ _G.called = true
+ end
+ end)
+
+ feed('ih')
+ trigger_at_pos({ 1, 1 })
+
+ local item = completion_list.items[1]
+ exec_lua(function()
+ vim.v.completed_item = {
+ user_data = {
+ nvim = {
+ lsp = {
+ client_id = client_id,
+ completion_item = item,
+ },
+ },
+ },
+ }
+ end)
+
+ feed('<C-x><C-o><C-y>')
+
+ assert_matches(function(matches)
+ eq(1, #matches)
+ eq('hello', matches[1].word)
+ eq(true, exec_lua('return _G.called'))
+ end)
+ end)
+
+ it('enable(…,{convert=fn}) custom word/abbr format', function()
+ create_server({
+ isIncomplete = false,
+ items = {
+ {
+ label = 'foo(bar)',
+ },
+ },
+ })
+
+ feed('ifo')
+ trigger_at_pos({ 1, 1 })
+ assert_matches(function(matches)
+ eq('foo', matches[1].abbr)
+ end)
+ end)
end)
diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua
index c5e14ffdc2..78c684083b 100644
--- a/test/functional/plugin/lsp/diagnostic_spec.lua
+++ b/test/functional/plugin/lsp/diagnostic_spec.lua
@@ -11,7 +11,9 @@ local neq = t.neq
local create_server_definition = t_lsp.create_server_definition
describe('vim.lsp.diagnostic', function()
- local fake_uri
+ local fake_uri --- @type string
+ local client_id --- @type integer
+ local diagnostic_bufnr --- @type integer
before_each(function()
clear { env = {
@@ -19,198 +21,174 @@ describe('vim.lsp.diagnostic', function()
VIMRUNTIME = os.getenv 'VIMRUNTIME',
} }
- exec_lua [[
+ exec_lua(function()
require('vim.lsp')
- make_range = function(x1, y1, x2, y2)
+ _G.make_range = function(x1, y1, x2, y2)
return { start = { line = x1, character = y1 }, ['end'] = { line = x2, character = y2 } }
end
- make_error = function(msg, x1, y1, x2, y2)
+ _G.make_error = function(msg, x1, y1, x2, y2)
return {
- range = make_range(x1, y1, x2, y2),
+ range = _G.make_range(x1, y1, x2, y2),
message = msg,
severity = 1,
}
end
- make_warning = function(msg, x1, y1, x2, y2)
+ _G.make_warning = function(msg, x1, y1, x2, y2)
return {
- range = make_range(x1, y1, x2, y2),
+ range = _G.make_range(x1, y1, x2, y2),
message = msg,
severity = 2,
}
end
- make_information = function(msg, x1, y1, x2, y2)
+ _G.make_information = function(msg, x1, y1, x2, y2)
return {
- range = make_range(x1, y1, x2, y2),
+ range = _G.make_range(x1, y1, x2, y2),
message = msg,
severity = 3,
}
end
- function get_extmarks(bufnr, client_id)
- local namespace = vim.lsp.diagnostic.get_namespace(client_id)
+ function _G.get_extmarks(bufnr, client_id0)
+ local namespace = vim.lsp.diagnostic.get_namespace(client_id0)
local ns = vim.diagnostic.get_namespace(namespace)
local extmarks = {}
if ns.user_data.virt_text_ns then
- for _, e in pairs(vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.virt_text_ns, 0, -1, {details=true})) do
+ for _, e in
+ pairs(
+ vim.api.nvim_buf_get_extmarks(
+ bufnr,
+ ns.user_data.virt_text_ns,
+ 0,
+ -1,
+ { details = true }
+ )
+ )
+ do
table.insert(extmarks, e)
end
end
if ns.user_data.underline_ns then
- for _, e in pairs(vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.underline_ns, 0, -1, {details=true})) do
+ for _, e in
+ pairs(
+ vim.api.nvim_buf_get_extmarks(
+ bufnr,
+ ns.user_data.underline_ns,
+ 0,
+ -1,
+ { details = true }
+ )
+ )
+ do
table.insert(extmarks, e)
end
end
return extmarks
end
- client_id = vim.lsp.start_client {
+ client_id = assert(vim.lsp.start_client {
cmd_env = {
- NVIM_LUA_NOTRACK = "1";
- };
+ NVIM_LUA_NOTRACK = '1',
+ },
cmd = {
- vim.v.progpath, '-es', '-u', 'NONE', '--headless'
- };
- offset_encoding = "utf-16";
- }
- ]]
+ vim.v.progpath,
+ '-es',
+ '-u',
+ 'NONE',
+ '--headless',
+ },
+ offset_encoding = 'utf-16',
+ })
+ end)
fake_uri = 'file:///fake/uri'
- exec_lua(
- [[
- fake_uri = ...
+ exec_lua(function()
diagnostic_bufnr = vim.uri_to_bufnr(fake_uri)
- local lines = {"1st line of text", "2nd line of text", "wow", "cool", "more", "lines"}
+ local lines = { '1st line of text', '2nd line of text', 'wow', 'cool', 'more', 'lines' }
vim.fn.bufload(diagnostic_bufnr)
vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, 1, false, lines)
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
- return diagnostic_bufnr
- ]],
- fake_uri
- )
+ end)
end)
after_each(function()
clear()
end)
- describe('vim.lsp.diagnostic', function()
- it('maintains LSP information when translating diagnostics', function()
- local result = exec_lua [[
- local diagnostics = {
- make_error("Error 1", 1, 1, 1, 5),
- }
-
- diagnostics[1].code = 42
- diagnostics[1].data = "Hello world"
-
- vim.lsp.diagnostic.on_publish_diagnostics(nil, {
- uri = fake_uri,
- diagnostics = diagnostics,
- }, {client_id=client_id})
-
- return {
- vim.diagnostic.get(diagnostic_bufnr, {lnum=1})[1],
- vim.lsp.diagnostic.get_line_diagnostics(diagnostic_bufnr, 1)[1],
- }
- ]]
- eq({ code = 42, data = 'Hello world' }, result[1].user_data.lsp)
- eq(42, result[1].code)
- eq(42, result[2].code)
- eq('Hello world', result[2].data)
- end)
- end)
-
describe('vim.lsp.diagnostic.on_publish_diagnostics', function()
it('allows configuring the virtual text via vim.lsp.with', function()
local expected_spacing = 10
- local extmarks = exec_lua(
- [[
- PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
+ local extmarks = exec_lua(function()
+ _G.PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
virtual_text = {
- spacing = ...,
+ spacing = expected_spacing,
},
})
- PublishDiagnostics(nil, {
- uri = fake_uri,
- diagnostics = {
- make_error('Delayed Diagnostic', 4, 4, 4, 4),
- }
- }, {client_id=client_id}
- )
+ _G.PublishDiagnostics(nil, {
+ uri = fake_uri,
+ diagnostics = {
+ _G.make_error('Delayed Diagnostic', 4, 4, 4, 4),
+ },
+ }, { client_id = client_id })
- return get_extmarks(diagnostic_bufnr, client_id)
- ]],
- expected_spacing
- )
+ return _G.get_extmarks(diagnostic_bufnr, client_id)
+ end)
- local virt_text = extmarks[1][4].virt_text
- local spacing = virt_text[1][1]
+ local spacing = extmarks[1][4].virt_text[1][1]
eq(expected_spacing, #spacing)
end)
it('allows configuring the virtual text via vim.lsp.with using a function', function()
local expected_spacing = 10
- local extmarks = exec_lua(
- [[
- spacing = ...
-
- PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
+ local extmarks = exec_lua(function()
+ _G.PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
virtual_text = function()
return {
- spacing = spacing,
+ spacing = expected_spacing,
}
end,
})
- PublishDiagnostics(nil, {
- uri = fake_uri,
- diagnostics = {
- make_error('Delayed Diagnostic', 4, 4, 4, 4),
- }
- }, {client_id=client_id}
- )
+ _G.PublishDiagnostics(nil, {
+ uri = fake_uri,
+ diagnostics = {
+ _G.make_error('Delayed Diagnostic', 4, 4, 4, 4),
+ },
+ }, { client_id = client_id })
- return get_extmarks(diagnostic_bufnr, client_id)
- ]],
- expected_spacing
- )
+ return _G.get_extmarks(diagnostic_bufnr, client_id)
+ end)
- local virt_text = extmarks[1][4].virt_text
- local spacing = virt_text[1][1]
+ local spacing = extmarks[1][4].virt_text[1][1]
eq(expected_spacing, #spacing)
end)
it('allows filtering via severity limit', function()
local get_extmark_count_with_severity = function(severity_limit)
- return exec_lua(
- [[
- PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
+ return exec_lua(function()
+ _G.PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
underline = false,
virtual_text = {
- severity = { min = ... }
+ severity = { min = severity_limit },
},
})
- PublishDiagnostics(nil, {
- uri = fake_uri,
- diagnostics = {
- make_warning('Delayed Diagnostic', 4, 4, 4, 4),
- }
- }, {client_id=client_id}
- )
-
- return #get_extmarks(diagnostic_bufnr, client_id)
- ]],
- severity_limit
- )
+ _G.PublishDiagnostics(nil, {
+ uri = fake_uri,
+ diagnostics = {
+ _G.make_warning('Delayed Diagnostic', 4, 4, 4, 4),
+ },
+ }, { client_id = client_id })
+
+ return #_G.get_extmarks(diagnostic_bufnr, client_id)
+ end, client_id, fake_uri, severity_limit)
end
-- No messages with Error or higher
@@ -223,218 +201,284 @@ describe('vim.lsp.diagnostic', function()
it('correctly handles UTF-16 offsets', function()
local line = 'All 💼 and no 🎉 makes Jack a dull 👦'
- local result = exec_lua(
- [[
- local line = ...
- vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, -1, false, {line})
+ local result = exec_lua(function()
+ vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, -1, false, { line })
vim.lsp.diagnostic.on_publish_diagnostics(nil, {
- uri = fake_uri,
- diagnostics = {
- make_error('UTF-16 Diagnostic', 0, 7, 0, 8),
- }
- }, {client_id=client_id}
- )
+ uri = fake_uri,
+ diagnostics = {
+ _G.make_error('UTF-16 Diagnostic', 0, 7, 0, 8),
+ },
+ }, { client_id = client_id })
local diags = vim.diagnostic.get(diagnostic_bufnr)
vim.lsp.stop_client(client_id)
vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })
return diags
- ]],
- line
- )
+ end)
eq(1, #result)
- eq(exec_lua([[return vim.str_byteindex(..., 7, true)]], line), result[1].col)
- eq(exec_lua([[return vim.str_byteindex(..., 8, true)]], line), result[1].end_col)
+ eq(
+ exec_lua(function()
+ return vim.str_byteindex(line, 7, true)
+ end),
+ result[1].col
+ )
+ eq(
+ exec_lua(function()
+ return vim.str_byteindex(line, 8, true)
+ end),
+ result[1].end_col
+ )
end)
it('does not create buffer on empty diagnostics', function()
- local bufnr
-
-- No buffer is created without diagnostics
- bufnr = exec_lua [[
- vim.lsp.diagnostic.on_publish_diagnostics(nil, {
- uri = "file:///fake/uri2",
- diagnostics = {},
- }, {client_id=client_id})
- return vim.fn.bufnr(vim.uri_to_fname("file:///fake/uri2"))
- ]]
- eq(-1, bufnr)
+ eq(
+ -1,
+ exec_lua(function()
+ vim.lsp.diagnostic.on_publish_diagnostics(nil, {
+ uri = 'file:///fake/uri2',
+ diagnostics = {},
+ }, { client_id = client_id })
+ return vim.fn.bufnr(vim.uri_to_fname('file:///fake/uri2'))
+ end)
+ )
-- Create buffer on diagnostics
- bufnr = exec_lua [[
- vim.lsp.diagnostic.on_publish_diagnostics(nil, {
- uri = "file:///fake/uri2",
- diagnostics = {
- make_error('Diagnostic', 0, 0, 0, 0),
- },
- }, {client_id=client_id})
- return vim.fn.bufnr(vim.uri_to_fname("file:///fake/uri2"))
- ]]
- neq(-1, bufnr)
- eq(1, exec_lua([[return #vim.diagnostic.get(...)]], bufnr))
+ neq(
+ -1,
+ exec_lua(function()
+ vim.lsp.diagnostic.on_publish_diagnostics(nil, {
+ uri = 'file:///fake/uri2',
+ diagnostics = {
+ _G.make_error('Diagnostic', 0, 0, 0, 0),
+ },
+ }, { client_id = client_id })
+ return vim.fn.bufnr(vim.uri_to_fname('file:///fake/uri2'))
+ end)
+ )
+ eq(
+ 1,
+ exec_lua(function()
+ return #vim.diagnostic.get(_G.bufnr)
+ end)
+ )
-- Clear diagnostics after buffer was created
- bufnr = exec_lua [[
- vim.lsp.diagnostic.on_publish_diagnostics(nil, {
- uri = "file:///fake/uri2",
- diagnostics = {},
- }, {client_id=client_id})
- return vim.fn.bufnr(vim.uri_to_fname("file:///fake/uri2"))
- ]]
- neq(-1, bufnr)
- eq(0, exec_lua([[return #vim.diagnostic.get(...)]], bufnr))
+ neq(
+ -1,
+ exec_lua(function()
+ vim.lsp.diagnostic.on_publish_diagnostics(nil, {
+ uri = 'file:///fake/uri2',
+ diagnostics = {},
+ }, { client_id = client_id })
+ return vim.fn.bufnr(vim.uri_to_fname('file:///fake/uri2'))
+ end)
+ )
+ eq(
+ 0,
+ exec_lua(function()
+ return #vim.diagnostic.get(_G.bufnr)
+ end)
+ )
end)
end)
describe('vim.lsp.diagnostic.on_diagnostic', function()
before_each(function()
exec_lua(create_server_definition)
- exec_lua([[
- server = _create_server({
+ exec_lua(function()
+ _G.server = _G._create_server({
capabilities = {
- diagnosticProvider = {
- }
- }
+ diagnosticProvider = {},
+ },
})
- function get_extmarks(bufnr, client_id)
- local namespace = vim.lsp.diagnostic.get_namespace(client_id, true)
+ function _G.get_extmarks(bufnr, client_id0)
+ local namespace = vim.lsp.diagnostic.get_namespace(client_id0, true)
local ns = vim.diagnostic.get_namespace(namespace)
local extmarks = {}
if ns.user_data.virt_text_ns then
- for _, e in pairs(vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.virt_text_ns, 0, -1, {details=true})) do
+ for _, e in
+ pairs(
+ vim.api.nvim_buf_get_extmarks(
+ bufnr,
+ ns.user_data.virt_text_ns,
+ 0,
+ -1,
+ { details = true }
+ )
+ )
+ do
table.insert(extmarks, e)
end
end
if ns.user_data.underline_ns then
- for _, e in pairs(vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.underline_ns, 0, -1, {details=true})) do
+ for _, e in
+ pairs(
+ vim.api.nvim_buf_get_extmarks(
+ bufnr,
+ ns.user_data.underline_ns,
+ 0,
+ -1,
+ { details = true }
+ )
+ )
+ do
table.insert(extmarks, e)
end
end
return extmarks
end
- client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
- ]])
+ client_id = vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
+ end)
end)
it('adds diagnostics to vim.diagnostics', function()
- local diags = exec_lua([[
- vim.lsp.diagnostic.on_diagnostic(nil,
- {
- kind = 'full',
- items = {
- make_error('Pull Diagnostic', 4, 4, 4, 4),
- }
+ local diags = exec_lua(function()
+ vim.lsp.diagnostic.on_diagnostic(nil, {
+ kind = 'full',
+ items = {
+ _G.make_error('Pull Diagnostic', 4, 4, 4, 4),
},
- {
- params = {
- textDocument = { uri = fake_uri },
- },
- uri = fake_uri,
- client_id = client_id,
+ }, {
+ params = {
+ textDocument = { uri = fake_uri },
},
- {}
- )
+ uri = fake_uri,
+ client_id = client_id,
+ }, {})
return vim.diagnostic.get(diagnostic_bufnr)
- ]])
+ end)
eq(1, #diags)
eq('Pull Diagnostic', diags[1].message)
end)
+ it('severity defaults to error if missing', function()
+ ---@type vim.Diagnostic[]
+ local diagnostics = exec_lua(function()
+ vim.lsp.diagnostic.on_diagnostic(nil, {
+ kind = 'full',
+ items = {
+ {
+ range = _G.make_range(4, 4, 4, 4),
+ message = 'bad!',
+ },
+ },
+ }, {
+ params = {
+ textDocument = { uri = fake_uri },
+ },
+ uri = fake_uri,
+ client_id = client_id,
+ }, {})
+ return vim.diagnostic.get(diagnostic_bufnr)
+ end)
+ eq(1, #diagnostics)
+ eq(1, diagnostics[1].severity)
+ end)
+
it('allows configuring the virtual text via vim.lsp.with', function()
local expected_spacing = 10
- local extmarks = exec_lua(
- [[
- Diagnostic = vim.lsp.with(vim.lsp.diagnostic.on_diagnostic, {
+ local extmarks = exec_lua(function()
+ _G.Diagnostic = vim.lsp.with(vim.lsp.diagnostic.on_diagnostic, {
virtual_text = {
- spacing = ...,
+ spacing = expected_spacing,
},
})
- Diagnostic(nil,
- {
- kind = 'full',
- items = {
- make_error('Pull Diagnostic', 4, 4, 4, 4),
- }
+ _G.Diagnostic(nil, {
+ kind = 'full',
+ items = {
+ _G.make_error('Pull Diagnostic', 4, 4, 4, 4),
},
- {
- params = {
- textDocument = { uri = fake_uri },
- },
- uri = fake_uri,
- client_id = client_id,
+ }, {
+ params = {
+ textDocument = { uri = fake_uri },
},
- {}
- )
+ uri = fake_uri,
+ client_id = client_id,
+ }, {})
- return get_extmarks(diagnostic_bufnr, client_id)
- ]],
- expected_spacing
- )
+ return _G.get_extmarks(diagnostic_bufnr, client_id)
+ end)
eq(2, #extmarks)
eq(expected_spacing, #extmarks[1][4].virt_text[1][1])
end)
it('clears diagnostics when client detaches', function()
- exec_lua([[
- vim.lsp.diagnostic.on_diagnostic(nil,
- {
- kind = 'full',
- items = {
- make_error('Pull Diagnostic', 4, 4, 4, 4),
- }
+ exec_lua(function()
+ vim.lsp.diagnostic.on_diagnostic(nil, {
+ kind = 'full',
+ items = {
+ _G.make_error('Pull Diagnostic', 4, 4, 4, 4),
},
- {
- params = {
- textDocument = { uri = fake_uri },
- },
- uri = fake_uri,
- client_id = client_id,
+ }, {
+ params = {
+ textDocument = { uri = fake_uri },
},
- {}
- )
- ]])
- local diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]])
- eq(1, #diags)
+ uri = fake_uri,
+ client_id = client_id,
+ }, {})
+ end)
+
+ eq(
+ 1,
+ exec_lua(function()
+ return #vim.diagnostic.get(diagnostic_bufnr)
+ end)
+ )
- exec_lua([[ vim.lsp.stop_client(client_id) ]])
+ exec_lua(function()
+ vim.lsp.stop_client(client_id)
+ end)
- diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]])
- eq(0, #diags)
+ eq(
+ 0,
+ exec_lua(function()
+ return #vim.diagnostic.get(diagnostic_bufnr)
+ end)
+ )
end)
it('keeps diagnostics when one client detaches and others still are attached', function()
- exec_lua([[
- client_id2 = vim.lsp.start({ name = 'dummy2', cmd = server.cmd })
-
- vim.lsp.diagnostic.on_diagnostic(nil,
- {
- kind = 'full',
- items = {
- make_error('Pull Diagnostic', 4, 4, 4, 4),
- }
+ local client_id2
+ exec_lua(function()
+ client_id2 = vim.lsp.start({ name = 'dummy2', cmd = _G.server.cmd })
+
+ vim.lsp.diagnostic.on_diagnostic(nil, {
+ kind = 'full',
+ items = {
+ _G.make_error('Pull Diagnostic', 4, 4, 4, 4),
},
- {
- params = {
- textDocument = { uri = fake_uri },
- },
- uri = fake_uri,
- client_id = client_id,
+ }, {
+ params = {
+ textDocument = { uri = fake_uri },
},
- {}
- )
- ]])
- local diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]])
- eq(1, #diags)
+ uri = fake_uri,
+ client_id = client_id,
+ }, {})
+ end)
+
+ eq(
+ 1,
+ exec_lua(function()
+ return #vim.diagnostic.get(diagnostic_bufnr)
+ end)
+ )
- exec_lua([[ vim.lsp.stop_client(client_id2) ]])
+ exec_lua(function()
+ vim.lsp.stop_client(client_id2)
+ end)
- diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]])
- eq(1, #diags)
+ eq(
+ 1,
+ exec_lua(function()
+ return #vim.diagnostic.get(diagnostic_bufnr)
+ end)
+ )
end)
end)
end)
diff --git a/test/functional/plugin/lsp/handler_spec.lua b/test/functional/plugin/lsp/handler_spec.lua
index 013a5fb5e7..4b05b676a8 100644
--- a/test/functional/plugin/lsp/handler_spec.lua
+++ b/test/functional/plugin/lsp/handler_spec.lua
@@ -11,28 +11,31 @@ describe('lsp-handlers', function()
it('should return a table with the default keys', function()
eq(
{ hello = 'world' },
- exec_lua [[
- return vim.lsp._with_extend('test', { hello = 'world' })
- ]]
+ exec_lua(function()
+ return vim.lsp._with_extend('test', { hello = 'world' })
+ end)
)
end)
it('should override with config keys', function()
eq(
{ hello = 'universe', other = true },
- exec_lua [[
- return vim.lsp._with_extend('test', { other = true, hello = 'world' }, { hello = 'universe' })
- ]]
+ exec_lua(function()
+ return vim.lsp._with_extend(
+ 'test',
+ { other = true, hello = 'world' },
+ { hello = 'universe' }
+ )
+ end)
)
end)
it('should not allow invalid keys', function()
matches(
'.*Invalid option for `test`.*',
- pcall_err(
- exec_lua,
- "return vim.lsp._with_extend('test', { hello = 'world' }, { invalid = true })"
- )
+ pcall_err(exec_lua, function()
+ return vim.lsp._with_extend('test', { hello = 'world' }, { invalid = true })
+ end)
)
end)
end)
diff --git a/test/functional/plugin/lsp/incremental_sync_spec.lua b/test/functional/plugin/lsp/incremental_sync_spec.lua
index 238b90b57d..f60e159d64 100644
--- a/test/functional/plugin/lsp/incremental_sync_spec.lua
+++ b/test/functional/plugin/lsp/incremental_sync_spec.lua
@@ -10,11 +10,9 @@ local feed = n.feed
before_each(function()
clear()
- exec_lua [[
- local evname = ...
+ exec_lua(function()
local sync = require('vim.lsp.sync')
local events = {}
- local buffer_cache = {}
-- local format_line_ending = {
-- ["unix"] = '\n',
@@ -24,35 +22,43 @@ before_each(function()
-- local line_ending = format_line_ending[vim.api.nvim_get_option_value('fileformat', {})]
-
- function test_register(bufnr, id, offset_encoding, line_ending)
- local curr_lines
+ --- @diagnostic disable-next-line:duplicate-set-field
+ function _G.test_register(bufnr, id, offset_encoding, line_ending)
local prev_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)
- local function callback(_, bufnr, changedtick, firstline, lastline, new_lastline)
- if test_unreg == id then
+ local function callback(_, bufnr0, _changedtick, firstline, lastline, new_lastline)
+ if _G.test_unreg == id then
return true
end
- local curr_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)
+ local curr_lines = vim.api.nvim_buf_get_lines(bufnr0, 0, -1, true)
local incremental_change = sync.compute_diff(
- prev_lines, curr_lines, firstline, lastline, new_lastline, offset_encoding, line_ending)
+ prev_lines,
+ curr_lines,
+ firstline,
+ lastline,
+ new_lastline,
+ offset_encoding,
+ line_ending
+ )
table.insert(events, incremental_change)
prev_lines = curr_lines
end
- local opts = {on_lines=callback, on_detach=callback, on_reload=callback}
+ local opts = { on_lines = callback, on_detach = callback, on_reload = callback }
vim.api.nvim_buf_attach(bufnr, false, opts)
end
- function get_events()
+ --- @diagnostic disable-next-line:duplicate-set-field
+ function _G.get_events()
local ret_events = events
events = {}
return ret_events
end
- ]]
+ end)
end)
+--- @param edit_operations string[]
local function test_edit(
prev_buffer,
edit_operations,
@@ -64,13 +70,22 @@ local function test_edit(
line_ending = line_ending or '\n'
api.nvim_buf_set_lines(0, 0, -1, true, prev_buffer)
- exec_lua('return test_register(...)', 0, 'test1', offset_encoding, line_ending)
+ exec_lua(function()
+ return _G.test_register(0, 'test1', offset_encoding, line_ending)
+ end)
for _, edit in ipairs(edit_operations) do
feed(edit)
end
- eq(expected_text_changes, exec_lua('return get_events(...)'))
- exec_lua("test_unreg = 'test1'")
+ eq(
+ expected_text_changes,
+ exec_lua(function()
+ return _G.get_events()
+ end)
+ )
+ exec_lua(function()
+ _G.test_unreg = 'test1'
+ end)
end
describe('incremental synchronization', function()
@@ -170,7 +185,7 @@ describe('incremental synchronization', function()
}
test_edit({ 'a' }, { 'rb' }, expected_text_changes, 'utf-16', '\n')
end)
- it('deleting a line', function()
+ it('deleting the first line', function()
local expected_text_changes = {
{
range = {
@@ -183,11 +198,49 @@ describe('incremental synchronization', function()
line = 1,
},
},
- rangeLength = 12,
+ rangeLength = 6,
+ text = '',
+ },
+ }
+ test_edit({ 'hello', 'world' }, { 'ggdd' }, expected_text_changes, 'utf-16', '\n')
+ end)
+ it('deleting the last line', function()
+ local expected_text_changes = {
+ {
+ range = {
+ ['start'] = {
+ character = 0,
+ line = 1,
+ },
+ ['end'] = {
+ character = 0,
+ line = 2,
+ },
+ },
+ rangeLength = 6,
+ text = '',
+ },
+ }
+ test_edit({ 'hello', 'world' }, { '2ggdd' }, expected_text_changes, 'utf-16', '\n')
+ end)
+ it('deleting all lines', function()
+ local expected_text_changes = {
+ {
+ range = {
+ ['start'] = {
+ character = 0,
+ line = 0,
+ },
+ ['end'] = {
+ character = 5,
+ line = 1,
+ },
+ },
+ rangeLength = 11,
text = '',
},
}
- test_edit({ 'hello world' }, { 'dd' }, expected_text_changes, 'utf-16', '\n')
+ test_edit({ 'hello', 'world' }, { 'ggdG' }, expected_text_changes, 'utf-16', '\n')
end)
it('deleting an empty line', function()
local expected_text_changes = {
diff --git a/test/functional/plugin/lsp/inlay_hint_spec.lua b/test/functional/plugin/lsp/inlay_hint_spec.lua
index d3b5ae0e4e..471f2cc3e8 100644
--- a/test/functional/plugin/lsp/inlay_hint_spec.lua
+++ b/test/functional/plugin/lsp/inlay_hint_spec.lua
@@ -12,7 +12,8 @@ local api = n.api
local clear_notrace = t_lsp.clear_notrace
local create_server_definition = t_lsp.create_server_definition
-local text = dedent([[
+describe('vim.lsp.inlay_hint', function()
+ local text = dedent([[
auto add(int a, int b) { return a + b; }
int main() {
@@ -22,7 +23,7 @@ int main() {
}
}]])
-local response = [==[
+ local response = [==[
[
{"kind":1,"paddingLeft":false,"label":"-> int","position":{"character":22,"line":0},"paddingRight":false},
{"kind":2,"paddingLeft":false,"label":"a:","position":{"character":15,"line":5},"paddingRight":true},
@@ -30,7 +31,7 @@ local response = [==[
]
]==]
-local grid_without_inlay_hints = [[
+ local grid_without_inlay_hints = [[
auto add(int a, int b) { return a + b; } |
|
int main() { |
@@ -42,7 +43,7 @@ local grid_without_inlay_hints = [[
|
]]
-local grid_with_inlay_hints = [[
+ local grid_with_inlay_hints = [[
auto add(int a, int b){1:-> int} { return a + b; } |
|
int main() { |
@@ -54,54 +55,58 @@ local grid_with_inlay_hints = [[
|
]]
---- @type test.functional.ui.screen
-local screen
-before_each(function()
- clear_notrace()
- screen = Screen.new(50, 9)
- screen:attach()
-
- exec_lua(create_server_definition)
- exec_lua(
- [[
- local response = ...
- server = _create_server({
- capabilities = {
- inlayHintProvider = true,
- },
- handlers = {
- ['textDocument/inlayHint'] = function(_, _, callback)
- callback(nil, vim.json.decode(response))
- end,
- }
- })
+ --- @type test.functional.ui.screen
+ local screen
- bufnr = vim.api.nvim_get_current_buf()
- vim.api.nvim_win_set_buf(0, bufnr)
+ --- @type integer
+ local client_id
- client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
- ]],
- response
- )
+ --- @type integer
+ local bufnr
- insert(text)
- exec_lua([[vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })]])
- screen:expect({ grid = grid_with_inlay_hints })
-end)
+ before_each(function()
+ clear_notrace()
+ screen = Screen.new(50, 9)
+ screen:attach()
-after_each(function()
- api.nvim_exec_autocmds('VimLeavePre', { modeline = false })
-end)
+ bufnr = n.api.nvim_get_current_buf()
+ exec_lua(create_server_definition)
+ client_id = exec_lua(function()
+ _G.server = _G._create_server({
+ capabilities = {
+ inlayHintProvider = true,
+ },
+ handlers = {
+ ['textDocument/inlayHint'] = function(_, _, callback)
+ callback(nil, vim.json.decode(response))
+ end,
+ },
+ })
+
+ return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
+ end)
+
+ insert(text)
+ exec_lua(function()
+ vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })
+ end)
+ screen:expect({ grid = grid_with_inlay_hints })
+ end)
+
+ after_each(function()
+ api.nvim_exec_autocmds('VimLeavePre', { modeline = false })
+ end)
-describe('vim.lsp.inlay_hint', function()
it('clears inlay hints when sole client detaches', function()
- exec_lua([[vim.lsp.stop_client(client_id)]])
+ exec_lua(function()
+ vim.lsp.stop_client(client_id)
+ end)
screen:expect({ grid = grid_without_inlay_hints, unchanged = true })
end)
it('does not clear inlay hints when one of several clients detaches', function()
- exec_lua([[
- server2 = _create_server({
+ local client_id2 = exec_lua(function()
+ _G.server2 = _G._create_server({
capabilities = {
inlayHintProvider = true,
},
@@ -109,13 +114,16 @@ describe('vim.lsp.inlay_hint', function()
['textDocument/inlayHint'] = function(_, _, callback)
callback(nil, {})
end,
- }
+ },
})
- client2 = vim.lsp.start({ name = 'dummy2', cmd = server2.cmd })
+ local client_id2 = vim.lsp.start({ name = 'dummy2', cmd = _G.server2.cmd })
vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })
- ]])
+ return client_id2
+ end)
- exec_lua([[ vim.lsp.stop_client(client2) ]])
+ exec_lua(function()
+ vim.lsp.stop_client(client_id2)
+ end)
screen:expect({ grid = grid_with_inlay_hints, unchanged = true })
end)
@@ -123,61 +131,85 @@ describe('vim.lsp.inlay_hint', function()
it('validation', function()
t.matches(
'enable: expected boolean, got table',
- t.pcall_err(exec_lua, [[vim.lsp.inlay_hint.enable({}, { bufnr = bufnr })]])
+ t.pcall_err(exec_lua, function()
+ --- @diagnostic disable-next-line:param-type-mismatch
+ vim.lsp.inlay_hint.enable({}, { bufnr = bufnr })
+ end)
)
t.matches(
'enable: expected boolean, got number',
- t.pcall_err(exec_lua, [[vim.lsp.inlay_hint.enable(42)]])
+ t.pcall_err(exec_lua, function()
+ --- @diagnostic disable-next-line:param-type-mismatch
+ vim.lsp.inlay_hint.enable(42)
+ end)
)
t.matches(
'filter: expected table, got number',
- t.pcall_err(exec_lua, [[vim.lsp.inlay_hint.enable(true, 42)]])
+ t.pcall_err(exec_lua, function()
+ --- @diagnostic disable-next-line:param-type-mismatch
+ vim.lsp.inlay_hint.enable(true, 42)
+ end)
)
end)
describe('clears/applies inlay hints when passed false/true/nil', function()
+ local bufnr2 --- @type integer
before_each(function()
- exec_lua([[
- bufnr2 = vim.api.nvim_create_buf(true, false)
- vim.lsp.buf_attach_client(bufnr2, client_id)
- vim.api.nvim_win_set_buf(0, bufnr2)
- ]])
+ bufnr2 = exec_lua(function()
+ local bufnr2_0 = vim.api.nvim_create_buf(true, false)
+ vim.lsp.buf_attach_client(bufnr2_0, client_id)
+ vim.api.nvim_win_set_buf(0, bufnr2_0)
+ return bufnr2_0
+ end)
insert(text)
- exec_lua([[vim.lsp.inlay_hint.enable(true, { bufnr = bufnr2 })]])
- exec_lua([[vim.api.nvim_win_set_buf(0, bufnr)]])
+ exec_lua(function()
+ vim.lsp.inlay_hint.enable(true, { bufnr = bufnr2 })
+ end)
+ n.api.nvim_win_set_buf(0, bufnr)
screen:expect({ grid = grid_with_inlay_hints })
end)
it('for one single buffer', function()
- exec_lua([[
+ exec_lua(function()
vim.lsp.inlay_hint.enable(false, { bufnr = bufnr })
vim.api.nvim_win_set_buf(0, bufnr2)
- ]])
+ end)
screen:expect({ grid = grid_with_inlay_hints, unchanged = true })
- exec_lua([[vim.api.nvim_win_set_buf(0, bufnr)]])
+ n.api.nvim_win_set_buf(0, bufnr)
screen:expect({ grid = grid_without_inlay_hints, unchanged = true })
- exec_lua([[vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })]])
+ exec_lua(function()
+ vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })
+ end)
screen:expect({ grid = grid_with_inlay_hints, unchanged = true })
- exec_lua(
- [[vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled({ bufnr = bufnr }), { bufnr = bufnr })]]
- )
+ exec_lua(function()
+ vim.lsp.inlay_hint.enable(
+ not vim.lsp.inlay_hint.is_enabled({ bufnr = bufnr }),
+ { bufnr = bufnr }
+ )
+ end)
screen:expect({ grid = grid_without_inlay_hints, unchanged = true })
- exec_lua([[vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })]])
+ exec_lua(function()
+ vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })
+ end)
screen:expect({ grid = grid_with_inlay_hints, unchanged = true })
end)
it('for all buffers', function()
- exec_lua([[vim.lsp.inlay_hint.enable(false)]])
+ exec_lua(function()
+ vim.lsp.inlay_hint.enable(false)
+ end)
screen:expect({ grid = grid_without_inlay_hints, unchanged = true })
- exec_lua([[vim.api.nvim_win_set_buf(0, bufnr2)]])
+ n.api.nvim_win_set_buf(0, bufnr2)
screen:expect({ grid = grid_without_inlay_hints, unchanged = true })
- exec_lua([[vim.lsp.inlay_hint.enable(true)]])
+ exec_lua(function()
+ vim.lsp.inlay_hint.enable(true)
+ end)
screen:expect({ grid = grid_with_inlay_hints, unchanged = true })
- exec_lua([[vim.api.nvim_win_set_buf(0, bufnr)]])
+ n.api.nvim_win_set_buf(0, bufnr)
screen:expect({ grid = grid_with_inlay_hints, unchanged = true })
end)
end)
@@ -198,10 +230,8 @@ describe('vim.lsp.inlay_hint', function()
paddingRight = false,
}
- exec_lua(
- [[
- local expected2 = ...
- server2 = _create_server({
+ exec_lua(function()
+ _G.server2 = _G._create_server({
capabilities = {
inlayHintProvider = true,
},
@@ -209,52 +239,139 @@ describe('vim.lsp.inlay_hint', function()
['textDocument/inlayHint'] = function(_, _, callback)
callback(nil, { expected2 })
end,
- }
+ },
})
- client2 = vim.lsp.start({ name = 'dummy2', cmd = server2.cmd })
+ _G.client2 = vim.lsp.start({ name = 'dummy2', cmd = _G.server2.cmd })
vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })
- ]],
- expected2
- )
+ end)
--- @type vim.lsp.inlay_hint.get.ret
- local res = exec_lua([[return vim.lsp.inlay_hint.get()]])
- eq({
- { bufnr = 1, client_id = 1, inlay_hint = expected[1] },
- { bufnr = 1, client_id = 1, inlay_hint = expected[2] },
- { bufnr = 1, client_id = 1, inlay_hint = expected[3] },
- { bufnr = 1, client_id = 2, inlay_hint = expected2 },
- }, res)
+ eq(
+ {
+ { bufnr = 1, client_id = 1, inlay_hint = expected[1] },
+ { bufnr = 1, client_id = 1, inlay_hint = expected[2] },
+ { bufnr = 1, client_id = 1, inlay_hint = expected[3] },
+ { bufnr = 1, client_id = 2, inlay_hint = expected2 },
+ },
+ exec_lua(function()
+ return vim.lsp.inlay_hint.get()
+ end)
+ )
- --- @type vim.lsp.inlay_hint.get.ret
- res = exec_lua([[return vim.lsp.inlay_hint.get({
- range = {
- start = { line = 2, character = 10 },
- ["end"] = { line = 2, character = 10 },
+ eq(
+ {
+ { bufnr = 1, client_id = 2, inlay_hint = expected2 },
},
- })]])
- eq({
- { bufnr = 1, client_id = 2, inlay_hint = expected2 },
- }, res)
+ exec_lua(function()
+ return vim.lsp.inlay_hint.get({
+ range = {
+ start = { line = 2, character = 10 },
+ ['end'] = { line = 2, character = 10 },
+ },
+ })
+ end)
+ )
- --- @type vim.lsp.inlay_hint.get.ret
- res = exec_lua([[return vim.lsp.inlay_hint.get({
- bufnr = vim.api.nvim_get_current_buf(),
- range = {
- start = { line = 4, character = 18 },
- ["end"] = { line = 5, character = 17 },
+ eq(
+ {
+ { bufnr = 1, client_id = 1, inlay_hint = expected[2] },
+ { bufnr = 1, client_id = 1, inlay_hint = expected[3] },
},
- })]])
- eq({
- { bufnr = 1, client_id = 1, inlay_hint = expected[2] },
- { bufnr = 1, client_id = 1, inlay_hint = expected[3] },
- }, res)
+ exec_lua(function()
+ return vim.lsp.inlay_hint.get({
+ bufnr = vim.api.nvim_get_current_buf(),
+ range = {
+ start = { line = 4, character = 18 },
+ ['end'] = { line = 5, character = 17 },
+ },
+ })
+ end)
+ )
- --- @type vim.lsp.inlay_hint.get.ret
- res = exec_lua([[return vim.lsp.inlay_hint.get({
- bufnr = vim.api.nvim_get_current_buf() + 1,
- })]])
- eq({}, res)
+ eq(
+ {},
+ exec_lua(function()
+ return vim.lsp.inlay_hint.get({
+ bufnr = vim.api.nvim_get_current_buf() + 1,
+ })
+ end)
+ )
+ end)
+ end)
+end)
+
+describe('Inlay hints handler', function()
+ local text = dedent([[
+test text
+ ]])
+
+ local response = [==[
+ [
+ { "position": { "line": 0, "character": 0 }, "label": "0" },
+ { "position": { "line": 0, "character": 0 }, "label": "1" },
+ { "position": { "line": 0, "character": 0 }, "label": "2" },
+ { "position": { "line": 0, "character": 0 }, "label": "3" },
+ { "position": { "line": 0, "character": 0 }, "label": "4" }
+ ]
+ ]==]
+
+ local grid_without_inlay_hints = [[
+ test text |
+ ^ |
+ |
+]]
+
+ local grid_with_inlay_hints = [[
+ {1:01234}test text |
+ ^ |
+ |
+]]
+
+ --- @type test.functional.ui.screen
+ local screen
+
+ --- @type integer
+ local client_id
+
+ --- @type integer
+ local bufnr
+
+ before_each(function()
+ clear_notrace()
+ screen = Screen.new(50, 3)
+ screen:attach()
+
+ exec_lua(create_server_definition)
+ bufnr = n.api.nvim_get_current_buf()
+ client_id = exec_lua(function()
+ _G.server = _G._create_server({
+ capabilities = {
+ inlayHintProvider = true,
+ },
+ handlers = {
+ ['textDocument/inlayHint'] = function(_, _, callback)
+ callback(nil, vim.json.decode(response))
+ end,
+ },
+ })
+
+ vim.api.nvim_win_set_buf(0, bufnr)
+
+ return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
+ end)
+ insert(text)
+ end)
+
+ it('renders hints with same position in received order', function()
+ exec_lua([[vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })]])
+ screen:expect({ grid = grid_with_inlay_hints })
+ exec_lua(function()
+ vim.lsp.stop_client(client_id)
end)
+ screen:expect({ grid = grid_without_inlay_hints, unchanged = true })
+ end)
+
+ after_each(function()
+ api.nvim_exec_autocmds('VimLeavePre', { modeline = false })
end)
end)
diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua
index 7908c5d2e7..f72aab7e0b 100644
--- a/test/functional/plugin/lsp/semantic_tokens_spec.lua
+++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua
@@ -25,7 +25,7 @@ after_each(function()
end)
describe('semantic token highlighting', function()
- local screen
+ local screen --- @type test.functional.ui.screen
before_each(function()
screen = Screen.new(40, 16)
screen:attach()
@@ -84,10 +84,8 @@ describe('semantic token highlighting', function()
before_each(function()
exec_lua(create_server_definition)
- exec_lua(
- [[
- local legend, response, edit_response = ...
- server = _create_server({
+ exec_lua(function()
+ _G.server = _G._create_server({
capabilities = {
semanticTokensProvider = {
full = { delta = true },
@@ -101,24 +99,19 @@ describe('semantic token highlighting', function()
['textDocument/semanticTokens/full/delta'] = function(_, _, callback)
callback(nil, vim.fn.json_decode(edit_response))
end,
- }
+ },
})
- ]],
- legend,
- response,
- edit_response
- )
+ end, legend, response, edit_response)
end)
it('buffer is highlighted when attached', function()
- exec_lua([[
- bufnr = vim.api.nvim_get_current_buf()
+ insert(text)
+ exec_lua(function()
+ local bufnr = vim.api.nvim_get_current_buf()
vim.api.nvim_win_set_buf(0, bufnr)
vim.bo[bufnr].filetype = 'some-filetype'
- client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
- ]])
-
- insert(text)
+ vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
+ end)
screen:expect {
grid = [[
@@ -141,23 +134,20 @@ describe('semantic token highlighting', function()
end)
it('use LspTokenUpdate and highlight_token', function()
- exec_lua([[
- vim.api.nvim_create_autocmd("LspTokenUpdate", {
+ insert(text)
+ exec_lua(function()
+ vim.api.nvim_create_autocmd('LspTokenUpdate', {
callback = function(args)
- local token = args.data.token
- if token.type == "function" and token.modifiers.declaration then
- vim.lsp.semantic_tokens.highlight_token(
- token, args.buf, args.data.client_id, "Macro"
- )
+ local token = args.data.token --- @type STTokenRange
+ if token.type == 'function' and token.modifiers.declaration then
+ vim.lsp.semantic_tokens.highlight_token(token, args.buf, args.data.client_id, 'Macro')
end
end,
})
- bufnr = vim.api.nvim_get_current_buf()
+ local bufnr = vim.api.nvim_get_current_buf()
vim.api.nvim_win_set_buf(0, bufnr)
- client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
- ]])
-
- insert(text)
+ vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
+ end)
screen:expect {
grid = [[
@@ -180,18 +170,23 @@ describe('semantic token highlighting', function()
end)
it('buffer is unhighlighted when client is detached', function()
- exec_lua([[
- bufnr = vim.api.nvim_get_current_buf()
- vim.api.nvim_win_set_buf(0, bufnr)
- client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
- ]])
-
insert(text)
- exec_lua([[
+ local bufnr = n.api.nvim_get_current_buf()
+ local client_id = exec_lua(function()
+ vim.api.nvim_win_set_buf(0, bufnr)
+ local client_id = vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
+ vim.wait(1000, function()
+ return #_G.server.messages > 1
+ end)
+ return client_id
+ end)
+
+ exec_lua(function()
+ --- @diagnostic disable-next-line:duplicate-set-field
vim.notify = function() end
vim.lsp.buf_detach_client(bufnr, client_id)
- ]])
+ end)
screen:expect {
grid = [[
@@ -216,18 +211,19 @@ describe('semantic token highlighting', function()
it(
'buffer is highlighted and unhighlighted when semantic token highlighting is started and stopped',
function()
- exec_lua([[
- bufnr = vim.api.nvim_get_current_buf()
- vim.api.nvim_win_set_buf(0, bufnr)
- client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
- ]])
+ local bufnr = n.api.nvim_get_current_buf()
+ local client_id = exec_lua(function()
+ vim.api.nvim_win_set_buf(0, bufnr)
+ return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
+ end)
insert(text)
- exec_lua([[
- vim.notify = function() end
- vim.lsp.semantic_tokens.stop(bufnr, client_id)
- ]])
+ exec_lua(function()
+ --- @diagnostic disable-next-line:duplicate-set-field
+ vim.notify = function() end
+ vim.lsp.semantic_tokens.stop(bufnr, client_id)
+ end)
screen:expect {
grid = [[
@@ -248,9 +244,9 @@ describe('semantic token highlighting', function()
]],
}
- exec_lua([[
- vim.lsp.semantic_tokens.start(bufnr, client_id)
- ]])
+ exec_lua(function()
+ vim.lsp.semantic_tokens.start(bufnr, client_id)
+ end)
screen:expect {
grid = [[
@@ -274,18 +270,17 @@ describe('semantic token highlighting', function()
)
it('highlights start and stop when using "0" for current buffer', function()
- exec_lua([[
- bufnr = vim.api.nvim_get_current_buf()
- vim.api.nvim_win_set_buf(0, bufnr)
- client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
- ]])
+ local client_id = exec_lua(function()
+ return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
+ end)
insert(text)
- exec_lua([[
+ exec_lua(function()
+ --- @diagnostic disable-next-line:duplicate-set-field
vim.notify = function() end
vim.lsp.semantic_tokens.stop(0, client_id)
- ]])
+ end)
screen:expect {
grid = [[
@@ -306,9 +301,9 @@ describe('semantic token highlighting', function()
]],
}
- exec_lua([[
+ exec_lua(function()
vim.lsp.semantic_tokens.start(0, client_id)
- ]])
+ end)
screen:expect {
grid = [[
@@ -331,13 +326,10 @@ describe('semantic token highlighting', function()
end)
it('buffer is re-highlighted when force refreshed', function()
- exec_lua([[
- bufnr = vim.api.nvim_get_current_buf()
- vim.api.nvim_win_set_buf(0, bufnr)
- client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
- ]])
-
insert(text)
+ exec_lua(function()
+ vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
+ end)
screen:expect {
grid = [[
@@ -358,9 +350,9 @@ describe('semantic token highlighting', function()
]],
}
- exec_lua([[
- vim.lsp.semantic_tokens.force_refresh(bufnr)
- ]])
+ exec_lua(function()
+ vim.lsp.semantic_tokens.force_refresh()
+ end)
screen:expect {
grid = [[
@@ -384,7 +376,9 @@ describe('semantic token highlighting', function()
local messages = exec_lua('return server.messages')
local token_request_count = 0
- for _, message in ipairs(messages) do
+ for _, message in
+ ipairs(messages --[[@as {method:string,params:table}[] ]])
+ do
assert(message.method ~= 'textDocument/semanticTokens/full/delta', 'delta request received')
if message.method == 'textDocument/semanticTokens/full' then
token_request_count = token_request_count + 1
@@ -394,31 +388,29 @@ describe('semantic token highlighting', function()
end)
it('destroys the highlighter if the buffer is deleted', function()
- exec_lua([[
- bufnr = vim.api.nvim_get_current_buf()
- vim.api.nvim_win_set_buf(0, bufnr)
- client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
- ]])
+ exec_lua(function()
+ vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
+ end)
insert(text)
- local highlighters = exec_lua([[
- vim.api.nvim_buf_delete(bufnr, { force = true })
- local semantic_tokens = vim.lsp.semantic_tokens
- return semantic_tokens.__STHighlighter.active
- ]])
-
- eq({}, highlighters)
+ eq(
+ {},
+ exec_lua(function()
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.api.nvim_buf_delete(bufnr, { force = true })
+ return vim.lsp.semantic_tokens.__STHighlighter.active
+ end)
+ )
end)
it('updates highlights with delta request on buffer change', function()
- exec_lua([[
- bufnr = vim.api.nvim_get_current_buf()
- vim.api.nvim_win_set_buf(0, bufnr)
- client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
- ]])
-
insert(text)
+
+ exec_lua(function()
+ vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
+ end)
+
screen:expect {
grid = [[
#include <iostream> |
@@ -459,45 +451,49 @@ describe('semantic token highlighting', function()
end)
it('prevents starting semantic token highlighting with invalid conditions', function()
- exec_lua([[
- bufnr = vim.api.nvim_get_current_buf()
- vim.api.nvim_win_set_buf(0, bufnr)
- client_id = vim.lsp.start_client({ name = 'dummy', cmd = server.cmd })
- notifications = {}
- vim.notify = function(...) table.insert(notifications, 1, {...}) end
- ]])
- eq(false, exec_lua('return vim.lsp.buf_is_attached(bufnr, client_id)'))
+ local client_id = exec_lua(function()
+ _G.notifications = {}
+ --- @diagnostic disable-next-line:duplicate-set-field
+ vim.notify = function(...)
+ table.insert(_G.notifications, 1, { ... })
+ end
+ return vim.lsp.start_client({ name = 'dummy', cmd = _G.server.cmd })
+ end)
+ eq(false, exec_lua('return vim.lsp.buf_is_attached(0, ...)', client_id))
insert(text)
- local notifications = exec_lua([[
- vim.lsp.semantic_tokens.start(bufnr, client_id)
- return notifications
- ]])
- matches('%[LSP%] Client with id %d not attached to buffer %d', notifications[1][1])
-
- notifications = exec_lua([[
- vim.lsp.semantic_tokens.start(bufnr, client_id + 1)
- return notifications
- ]])
- matches('%[LSP%] No client with id %d', notifications[1][1])
+ matches(
+ '%[LSP%] Client with id %d not attached to buffer %d',
+ exec_lua(function()
+ vim.lsp.semantic_tokens.start(0, client_id)
+ return _G.notifications[1][1]
+ end)
+ )
+
+ matches(
+ '%[LSP%] No client with id %d',
+ exec_lua(function()
+ vim.lsp.semantic_tokens.start(0, client_id + 1)
+ return _G.notifications[1][1]
+ end)
+ )
end)
it(
'opt-out: does not activate semantic token highlighting if disabled in client attach',
function()
- exec_lua([[
- bufnr = vim.api.nvim_get_current_buf()
- vim.api.nvim_win_set_buf(0, bufnr)
- client_id = vim.lsp.start({
+ local client_id = exec_lua(function()
+ return vim.lsp.start({
name = 'dummy',
- cmd = server.cmd,
- on_attach = vim.schedule_wrap(function(client, bufnr)
+ cmd = _G.server.cmd,
+ --- @param client vim.lsp.Client
+ on_attach = vim.schedule_wrap(function(client, _bufnr)
client.server_capabilities.semanticTokensProvider = nil
end),
})
- ]])
- eq(true, exec_lua('return vim.lsp.buf_is_attached(bufnr, client_id)'))
+ end)
+ eq(true, exec_lua('return vim.lsp.buf_is_attached(0, ...)', client_id))
insert(text)
@@ -520,13 +516,18 @@ describe('semantic token highlighting', function()
]],
}
- local notifications = exec_lua([[
- local notifications = {}
- vim.notify = function(...) table.insert(notifications, 1, {...}) end
- vim.lsp.semantic_tokens.start(bufnr, client_id)
- return notifications
- ]])
- eq('[LSP] Server does not support semantic tokens', notifications[1][1])
+ eq(
+ '[LSP] Server does not support semantic tokens',
+ exec_lua(function()
+ local notifications = {}
+ --- @diagnostic disable-next-line:duplicate-set-field
+ vim.notify = function(...)
+ table.insert(notifications, 1, { ... })
+ end
+ vim.lsp.semantic_tokens.start(0, client_id)
+ return notifications[1][1]
+ end)
+ )
screen:expect {
grid = [[
@@ -551,28 +552,32 @@ describe('semantic token highlighting', function()
)
it('ignores null responses from the server', function()
- exec_lua([[
- local legend, response, edit_response = ...
- server2 = _create_server({
+ local client_id = exec_lua(function()
+ _G.server2 = _G._create_server({
capabilities = {
semanticTokensProvider = {
full = { delta = false },
},
},
handlers = {
+ --- @param callback function
['textDocument/semanticTokens/full'] = function(_, _, callback)
callback(nil, nil)
end,
- ['textDocument/semanticTokens/full/delta'] = function()
+ --- @param callback function
+ ['textDocument/semanticTokens/full/delta'] = function(_, _, callback)
callback(nil, nil)
end,
- }
+ },
})
- bufnr = vim.api.nvim_get_current_buf()
- vim.api.nvim_win_set_buf(0, bufnr)
- client_id = vim.lsp.start({ name = 'dummy', cmd = server2.cmd })
- ]])
- eq(true, exec_lua('return vim.lsp.buf_is_attached(bufnr, client_id)'))
+ return vim.lsp.start({ name = 'dummy', cmd = _G.server2.cmd })
+ end)
+ eq(
+ true,
+ exec_lua(function()
+ return vim.lsp.buf_is_attached(0, client_id)
+ end)
+ )
insert(text)
@@ -597,10 +602,9 @@ describe('semantic token highlighting', function()
end)
it('does not send delta requests if not supported by server', function()
- exec_lua(
- [[
- local legend, response, edit_response = ...
- server2 = _create_server({
+ insert(text)
+ exec_lua(function()
+ _G.server2 = _G._create_server({
capabilities = {
semanticTokensProvider = {
full = { delta = false },
@@ -614,18 +618,11 @@ describe('semantic token highlighting', function()
['textDocument/semanticTokens/full/delta'] = function(_, _, callback)
callback(nil, vim.fn.json_decode(edit_response))
end,
- }
+ },
})
- bufnr = vim.api.nvim_get_current_buf()
- vim.api.nvim_win_set_buf(0, bufnr)
- client_id = vim.lsp.start({ name = 'dummy', cmd = server2.cmd })
- ]],
- legend,
- response,
- edit_response
- )
+ return vim.lsp.start({ name = 'dummy', cmd = _G.server2.cmd })
+ end)
- insert(text)
screen:expect {
grid = [[
#include <iostream> |
@@ -669,7 +666,9 @@ describe('semantic token highlighting', function()
}
local messages = exec_lua('return server2.messages')
local token_request_count = 0
- for _, message in ipairs(messages) do
+ for _, message in
+ ipairs(messages --[[@as {method:string,params:table}[] ]])
+ do
assert(message.method ~= 'textDocument/semanticTokens/full/delta', 'delta request received')
if message.method == 'textDocument/semanticTokens/full' then
token_request_count = token_request_count + 1
@@ -1064,10 +1063,8 @@ b = "as"]],
}) do
it(test.it, function()
exec_lua(create_server_definition)
- exec_lua(
- [[
- local legend, resp = ...
- server = _create_server({
+ local client_id = exec_lua(function(legend, resp)
+ _G.server = _G._create_server({
capabilities = {
semanticTokensProvider = {
full = { delta = false },
@@ -1078,25 +1075,22 @@ b = "as"]],
['textDocument/semanticTokens/full'] = function(_, _, callback)
callback(nil, vim.fn.json_decode(resp))
end,
- }
+ },
})
- bufnr = vim.api.nvim_get_current_buf()
- vim.api.nvim_win_set_buf(0, bufnr)
- client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
- ]],
- test.legend,
- test.response
- )
+ return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
+ end, test.legend, test.response)
insert(test.text)
test.expected_screen()
- local highlights = exec_lua([[
- local semantic_tokens = vim.lsp.semantic_tokens
- return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
- ]])
- eq(test.expected, highlights)
+ eq(
+ test.expected,
+ exec_lua(function()
+ local bufnr = vim.api.nvim_get_current_buf()
+ return vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
+ end)
+ )
end)
end
end)
@@ -1449,11 +1443,11 @@ int main()
},
}) do
it(test.it, function()
+ local bufnr = n.api.nvim_get_current_buf()
+ insert(test.text1)
exec_lua(create_server_definition)
- exec_lua(
- [[
- local legend, resp1, resp2 = ...
- server = _create_server({
+ local client_id = exec_lua(function(legend, resp1, resp2)
+ _G.server = _G._create_server({
capabilities = {
semanticTokensProvider = {
full = { delta = true },
@@ -1467,54 +1461,44 @@ int main()
['textDocument/semanticTokens/full/delta'] = function(_, _, callback)
callback(nil, vim.fn.json_decode(resp2))
end,
- }
+ },
})
- bufnr = vim.api.nvim_get_current_buf()
- vim.api.nvim_win_set_buf(0, bufnr)
- client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
+ local client_id = assert(vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }))
-- speed up vim.api.nvim_buf_set_lines calls by changing debounce to 10 for these tests
- semantic_tokens = vim.lsp.semantic_tokens
vim.schedule(function()
- semantic_tokens.stop(bufnr, client_id)
- semantic_tokens.start(bufnr, client_id, { debounce = 10 })
+ vim.lsp.semantic_tokens.stop(bufnr, client_id)
+ vim.lsp.semantic_tokens.start(bufnr, client_id, { debounce = 10 })
end)
- ]],
- test.legend,
- test.response1,
- test.response2
- )
-
- insert(test.text1)
+ return client_id
+ end, test.legend, test.response1, test.response2)
test.expected_screen1()
- local highlights = exec_lua([[
- return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
- ]])
-
- eq(test.expected1, highlights)
+ eq(
+ test.expected1,
+ exec_lua(function()
+ return vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
+ end)
+ )
if test.edit then
feed(test.edit)
else
- exec_lua(
- [[
- local text = ...
- vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, vim.fn.split(text, "\n"))
+ exec_lua(function(text)
+ vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, vim.fn.split(text, '\n'))
vim.wait(15) -- wait for debounce
- ]],
- test.text2
- )
+ end, test.text2)
end
test.expected_screen2()
- highlights = exec_lua([[
- return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
- ]])
-
- eq(test.expected2, highlights)
+ eq(
+ test.expected2,
+ exec_lua(function()
+ return vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
+ end)
+ )
end)
end
end)
diff --git a/test/functional/plugin/lsp/testutil.lua b/test/functional/plugin/lsp/testutil.lua
index 3430a1e1a3..a36cbac568 100644
--- a/test/functional/plugin/lsp/testutil.lua
+++ b/test/functional/plugin/lsp/testutil.lua
@@ -21,8 +21,35 @@ function M.clear_notrace()
}
end
-M.create_server_definition = [[
- function _create_server(opts)
+M.create_tcp_echo_server = function()
+ --- Create a TCP server that echos the first message it receives.
+ --- @param host string
+ ---@return uv.uv_tcp_t
+ ---@return integer
+ ---@return fun():string|nil
+ function _G._create_tcp_server(host)
+ local uv = vim.uv
+ local server = assert(uv.new_tcp())
+ local init = nil
+ server:bind(host, 0)
+ server:listen(127, function(err)
+ assert(not err, err)
+ local socket = assert(uv.new_tcp())
+ server:accept(socket)
+ socket:read_start(require('vim.lsp.rpc').create_read_loop(function(body)
+ init = body
+ socket:close()
+ end))
+ end)
+ local port = server:getsockname().port
+ return server, port, function()
+ return init
+ end
+ end
+end
+
+M.create_server_definition = function()
+ function _G._create_server(opts)
opts = opts or {}
local server = {}
server.messages = {}
@@ -42,7 +69,7 @@ M.create_server_definition = [[
handler(method, params, callback)
elseif method == 'initialize' then
callback(nil, {
- capabilities = opts.capabilities or {}
+ capabilities = opts.capabilities or {},
})
elseif method == 'shutdown' then
callback(nil, nil)
@@ -54,7 +81,7 @@ M.create_server_definition = [[
function srv.notify(method, params)
table.insert(server.messages, {
method = method,
- params = params
+ params = params,
})
if method == 'exit' then
dispatchers.on_exit(0, 15)
@@ -74,63 +101,62 @@ M.create_server_definition = [[
return server
end
-]]
+end
-- Fake LSP server.
M.fake_lsp_code = 'test/functional/fixtures/fake-lsp-server.lua'
M.fake_lsp_logfile = 'Xtest-fake-lsp.log'
local function fake_lsp_server_setup(test_name, timeout_ms, options, settings)
- exec_lua(
- [=[
- lsp = require('vim.lsp')
- local test_name, fake_lsp_code, fake_lsp_logfile, timeout, options, settings = ...
- TEST_RPC_CLIENT_ID = lsp.start_client {
+ exec_lua(function(fake_lsp_code, fake_lsp_logfile, timeout)
+ options = options or {}
+ settings = settings or {}
+ _G.lsp = require('vim.lsp')
+ _G.TEST_RPC_CLIENT_ID = _G.lsp.start_client {
cmd_env = {
- NVIM_LOG_FILE = fake_lsp_logfile;
- NVIM_LUA_NOTRACK = "1";
- NVIM_APPNAME = "nvim_lsp_test";
- };
+ NVIM_LOG_FILE = fake_lsp_logfile,
+ NVIM_LUA_NOTRACK = '1',
+ NVIM_APPNAME = 'nvim_lsp_test',
+ },
cmd = {
- vim.v.progpath, '-l', fake_lsp_code, test_name, tostring(timeout),
- };
+ vim.v.progpath,
+ '-l',
+ fake_lsp_code,
+ test_name,
+ tostring(timeout),
+ },
handlers = setmetatable({}, {
- __index = function(t, method)
+ __index = function(_t, _method)
return function(...)
return vim.rpcrequest(1, 'handler', ...)
end
- end;
- });
- workspace_folders = {{
+ end,
+ }),
+ workspace_folders = {
+ {
uri = 'file://' .. vim.uv.cwd(),
name = 'test_folder',
- }};
- before_init = function(params, config)
+ },
+ },
+ before_init = function(_params, _config)
vim.schedule(function()
- vim.rpcrequest(1, "setup")
+ vim.rpcrequest(1, 'setup')
end)
end,
on_init = function(client, result)
- TEST_RPC_CLIENT = client
- vim.rpcrequest(1, "init", result)
- end;
+ _G.TEST_RPC_CLIENT = client
+ vim.rpcrequest(1, 'init', result)
+ end,
flags = {
- allow_incremental_sync = options.allow_incremental_sync or false;
- debounce_text_changes = options.debounce_text_changes or 0;
- };
- settings = settings;
+ allow_incremental_sync = options.allow_incremental_sync or false,
+ debounce_text_changes = options.debounce_text_changes or 0,
+ },
+ settings = settings,
on_exit = function(...)
- vim.rpcnotify(1, "exit", ...)
- end;
+ vim.rpcnotify(1, 'exit', ...)
+ end,
}
- ]=],
- test_name,
- M.fake_lsp_code,
- M.fake_lsp_logfile,
- timeout_ms or 1e3,
- options or {},
- settings or {}
- )
+ end, M.fake_lsp_code, M.fake_lsp_logfile, timeout_ms or 1e3)
end
--- @class test.lsp.Config
@@ -160,18 +186,13 @@ function M.test_rpc_server(config)
-- Workaround for not being able to yield() inside __index for Lua 5.1 :(
-- Otherwise I would just return the value here.
return function(...)
- return exec_lua(
- [=[
- local name = ...
- if type(TEST_RPC_CLIENT[name]) == 'function' then
- return TEST_RPC_CLIENT[name](select(2, ...))
- else
- return TEST_RPC_CLIENT[name]
- end
- ]=],
- name,
- ...
- )
+ return exec_lua(function(...)
+ if type(_G.TEST_RPC_CLIENT[name]) == 'function' then
+ return _G.TEST_RPC_CLIENT[name](...)
+ else
+ return _G.TEST_RPC_CLIENT[name]
+ end
+ end, ...)
end
end,
})
diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua
index 6c6dec0667..64d58eeffd 100644
--- a/test/functional/plugin/lsp/utils_spec.lua
+++ b/test/functional/plugin/lsp/utils_spec.lua
@@ -11,21 +11,11 @@ describe('vim.lsp.util', function()
describe('stylize_markdown', function()
local stylize_markdown = function(content, opts)
- return exec_lua(
- [[
- local bufnr = vim.uri_to_bufnr("file:///fake/uri")
+ return exec_lua(function()
+ local bufnr = vim.uri_to_bufnr('file:///fake/uri')
vim.fn.bufload(bufnr)
-
- local args = { ... }
- local content = args[1]
- local opts = args[2]
- local stripped_content = vim.lsp.util.stylize_markdown(bufnr, content, opts)
-
- return stripped_content
- ]],
- content,
- opts
- )
+ return vim.lsp.util.stylize_markdown(bufnr, content, opts)
+ end)
end
it('code fences', function()
@@ -93,9 +83,64 @@ describe('vim.lsp.util', function()
end)
end)
- describe('normalize_markdown', function()
+ it('convert_input_to_markdown_lines', function()
+ local r = exec_lua(function()
+ local hover_data = {
+ kind = 'markdown',
+ value = '```lua\nfunction vim.api.nvim_buf_attach(buffer: integer, send_buffer: boolean, opts: vim.api.keyset.buf_attach)\n -> boolean\n```\n\n---\n\n Activates buffer-update events. Example:\n\n\n\n ```lua\n events = {}\n vim.api.nvim_buf_attach(0, false, {\n on_lines = function(...)\n table.insert(events, {...})\n end,\n })\n ```\n\n\n @see `nvim_buf_detach()`\n @see `api-buffer-updates-lua`\n@*param* `buffer` — Buffer handle, or 0 for current buffer\n\n\n\n@*param* `send_buffer` — True if whole buffer.\n Else the first notification will be `nvim_buf_changedtick_event`.\n\n\n@*param* `opts` — Optional parameters.\n\n - on_lines: Lua callback. Args:\n - the string "lines"\n - buffer handle\n - b:changedtick\n@*return* — False if foo;\n\n otherwise True.\n\n@see foo\n@see bar\n\n',
+ }
+ return vim.lsp.util.convert_input_to_markdown_lines(hover_data)
+ end)
+ local expected = {
+ '```lua',
+ 'function vim.api.nvim_buf_attach(buffer: integer, send_buffer: boolean, opts: vim.api.keyset.buf_attach)',
+ ' -> boolean',
+ '```',
+ '',
+ '---',
+ '',
+ ' Activates buffer-update events. Example:',
+ '',
+ '',
+ '',
+ ' ```lua',
+ ' events = {}',
+ ' vim.api.nvim_buf_attach(0, false, {',
+ ' on_lines = function(...)',
+ ' table.insert(events, {...})',
+ ' end,',
+ ' })',
+ ' ```',
+ '',
+ '',
+ ' @see `nvim_buf_detach()`',
+ ' @see `api-buffer-updates-lua`',
+ '',
+ -- For each @param/@return: #30695
+ -- - Separate each by one empty line.
+ -- - Remove all other blank lines.
+ '@*param* `buffer` — Buffer handle, or 0 for current buffer',
+ '',
+ '@*param* `send_buffer` — True if whole buffer.',
+ ' Else the first notification will be `nvim_buf_changedtick_event`.',
+ '',
+ '@*param* `opts` — Optional parameters.',
+ ' - on_lines: Lua callback. Args:',
+ ' - the string "lines"',
+ ' - buffer handle',
+ ' - b:changedtick',
+ '',
+ '@*return* — False if foo;',
+ ' otherwise True.',
+ '@see foo',
+ '@see bar',
+ }
+ eq(expected, r)
+ end)
+
+ describe('_normalize_markdown', function()
it('collapses consecutive blank lines', function()
- local result = exec_lua [[
+ local result = exec_lua(function()
local lines = {
'foo',
'',
@@ -103,25 +148,25 @@ describe('vim.lsp.util', function()
'',
'bar',
'',
- 'baz'
+ 'baz',
}
return vim.lsp.util._normalize_markdown(lines)
- ]]
+ end)
local expected = { 'foo', '', 'bar', '', 'baz' }
eq(expected, result)
end)
it('removes preceding and trailing empty lines', function()
- local result = exec_lua [[
+ local result = exec_lua(function()
local lines = {
'',
'foo',
'bar',
'',
- ''
+ '',
}
return vim.lsp.util._normalize_markdown(lines)
- ]]
+ end)
local expected = { 'foo', 'bar' }
eq(expected, result)
end)
@@ -129,19 +174,14 @@ describe('vim.lsp.util', function()
describe('make_floating_popup_options', function()
local function assert_anchor(anchor_bias, expected_anchor)
- local opts = exec_lua(
- [[
- local args = { ... }
- local anchor_bias = args[1]
- return vim.lsp.util.make_floating_popup_options(30, 10, { anchor_bias = anchor_bias })
- ]],
- anchor_bias
- )
+ local opts = exec_lua(function()
+ return vim.lsp.util.make_floating_popup_options(30, 10, { anchor_bias = anchor_bias })
+ end)
eq(expected_anchor, string.sub(opts.anchor, 1, 1))
end
- local screen
+ local screen --- @type test.functional.ui.screen
before_each(function()
n.clear()
screen = Screen.new(80, 80)
@@ -221,9 +261,9 @@ describe('vim.lsp.util', function()
end)
it('bordered window truncates dimensions correctly', function()
- local opts = exec_lua([[
+ local opts = exec_lua(function()
return vim.lsp.util.make_floating_popup_options(100, 100, { border = 'single' })
- ]])
+ end)
eq(56, opts.height)
end)