mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
lua: simple snippet support in the completion items (#12118)
Old behavior is: foo(${placeholder: bar, ...) with lots of random garbage you'd never want inserted. New behavior is: foo(bar, baz) (which maybe is good, maybe is bad [depends on user], but definitely better than it was). ----- * Implement rudimentary snippet parsing Add support for parsing and discarding snippet tokens from the completion items. Fixes #11982 * Enable snippet support * Functional tests for snippet parsing Add simplified real-world snippet text examples to the completion items test * Add a test for nested snippet tokens * Remove TODO comment * Return the unmodified item if the format is plain text * Add a plain text completion item
This commit is contained in:
parent
2ca8f02a64
commit
5a9226c800
@ -633,8 +633,7 @@ function protocol.make_client_capabilities()
|
|||||||
dynamicRegistration = false;
|
dynamicRegistration = false;
|
||||||
completionItem = {
|
completionItem = {
|
||||||
|
|
||||||
-- TODO(tjdevries): Is it possible to implement this in plain lua?
|
snippetSupport = true;
|
||||||
snippetSupport = false;
|
|
||||||
commitCharactersSupport = false;
|
commitCharactersSupport = false;
|
||||||
preselectSupport = false;
|
preselectSupport = false;
|
||||||
deprecatedSupport = false;
|
deprecatedSupport = false;
|
||||||
|
@ -199,6 +199,66 @@ function M.get_current_line_to_cursor()
|
|||||||
return line:sub(pos[2]+1)
|
return line:sub(pos[2]+1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function parse_snippet_rec(input, inner)
|
||||||
|
local res = ""
|
||||||
|
|
||||||
|
local close, closeend = nil, nil
|
||||||
|
if inner then
|
||||||
|
close, closeend = input:find("}", 1, true)
|
||||||
|
while close ~= nil and input:sub(close-1,close-1) == "\\" do
|
||||||
|
close, closeend = input:find("}", closeend+1, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local didx = input:find('$', 1, true)
|
||||||
|
if didx == nil and close == nil then
|
||||||
|
return input, ""
|
||||||
|
elseif close ~=nil and (didx == nil or close < didx) then
|
||||||
|
-- No inner placeholders
|
||||||
|
return input:sub(0, close-1), input:sub(closeend+1)
|
||||||
|
end
|
||||||
|
|
||||||
|
res = res .. input:sub(0, didx-1)
|
||||||
|
input = input:sub(didx+1)
|
||||||
|
|
||||||
|
local tabstop, tabstopend = input:find('^%d+')
|
||||||
|
local placeholder, placeholderend = input:find('^{%d+:')
|
||||||
|
local choice, choiceend = input:find('^{%d+|')
|
||||||
|
|
||||||
|
if tabstop then
|
||||||
|
input = input:sub(tabstopend+1)
|
||||||
|
elseif choice then
|
||||||
|
input = input:sub(choiceend+1)
|
||||||
|
close, closeend = input:find("|}", 1, true)
|
||||||
|
|
||||||
|
res = res .. input:sub(0, close-1)
|
||||||
|
input = input:sub(closeend+1)
|
||||||
|
elseif placeholder then
|
||||||
|
-- TODO: add support for variables
|
||||||
|
input = input:sub(placeholderend+1)
|
||||||
|
|
||||||
|
-- placeholders and variables are recursive
|
||||||
|
while input ~= "" do
|
||||||
|
local r, tail = parse_snippet_rec(input, true)
|
||||||
|
r = r:gsub("\\}", "}")
|
||||||
|
|
||||||
|
res = res .. r
|
||||||
|
input = tail
|
||||||
|
end
|
||||||
|
else
|
||||||
|
res = res .. "$"
|
||||||
|
end
|
||||||
|
|
||||||
|
return res, input
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Parse completion entries, consuming snippet tokens
|
||||||
|
function M.parse_snippet(input)
|
||||||
|
local res, _ = parse_snippet_rec(input, false)
|
||||||
|
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
-- Sort by CompletionItem.sortText
|
-- Sort by CompletionItem.sortText
|
||||||
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
|
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
|
||||||
local function sort_completion_items(items)
|
local function sort_completion_items(items)
|
||||||
@ -213,9 +273,17 @@ end
|
|||||||
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
|
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
|
||||||
local function get_completion_word(item)
|
local function get_completion_word(item)
|
||||||
if item.textEdit ~= nil and item.textEdit.newText ~= nil then
|
if item.textEdit ~= nil and item.textEdit.newText ~= nil then
|
||||||
return item.textEdit.newText
|
if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then
|
||||||
|
return item.textEdit.newText
|
||||||
|
else
|
||||||
|
return M.parse_snippet(item.textEdit.newText)
|
||||||
|
end
|
||||||
elseif item.insertText ~= nil then
|
elseif item.insertText ~= nil then
|
||||||
return item.insertText
|
if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then
|
||||||
|
return item.insertText
|
||||||
|
else
|
||||||
|
return M.parse_snippet(item.insertText)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return item.label
|
return item.label
|
||||||
end
|
end
|
||||||
|
@ -973,7 +973,14 @@ describe('LSP', function()
|
|||||||
{ label='foocar', insertText='foobar', textEdit={} },
|
{ label='foocar', insertText='foobar', textEdit={} },
|
||||||
-- resolves into textEdit.newText
|
-- resolves into textEdit.newText
|
||||||
{ label='foocar', insertText='foodar', textEdit={newText='foobar'} },
|
{ label='foocar', insertText='foodar', textEdit={newText='foobar'} },
|
||||||
{ label='foocar', textEdit={newText='foobar'} }
|
{ label='foocar', textEdit={newText='foobar'} },
|
||||||
|
-- real-world snippet text
|
||||||
|
{ label='foocar', insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} },
|
||||||
|
{ label='foocar', insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} },
|
||||||
|
-- nested snippet tokens
|
||||||
|
{ label='foocar', insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} },
|
||||||
|
-- plain text
|
||||||
|
{ label='foocar', insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} },
|
||||||
}
|
}
|
||||||
local completion_list_items = {items=completion_list}
|
local completion_list_items = {items=completion_list}
|
||||||
local expected = {
|
local expected = {
|
||||||
@ -983,6 +990,10 @@ describe('LSP', function()
|
|||||||
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foobar', textEdit={} } } } } },
|
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foobar', textEdit={} } } } } },
|
||||||
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar', textEdit={newText='foobar'} } } } } },
|
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar', textEdit={newText='foobar'} } } } } },
|
||||||
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', textEdit={newText='foobar'} } } } } },
|
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', textEdit={newText='foobar'} } } } } },
|
||||||
|
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } },
|
||||||
|
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} } } } } },
|
||||||
|
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2,typ3 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} } } } } },
|
||||||
|
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } },
|
||||||
}
|
}
|
||||||
|
|
||||||
eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list, prefix))
|
eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list, prefix))
|
||||||
|
Loading…
Reference in New Issue
Block a user