diff options
author | Viktor Kojouharov <developer@sugr.org> | 2020-05-28 14:31:56 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-28 08:31:56 -0400 |
commit | 5a9226c800d3075821203952da7c38626180680d (patch) | |
tree | 6d36d84f9c16561d12de8fd7aafcce23a72a294f | |
parent | 2ca8f02a6461fd4710c4ecc555fbe7ee9f75a70a (diff) | |
download | rneovim-5a9226c800d3075821203952da7c38626180680d.tar.gz rneovim-5a9226c800d3075821203952da7c38626180680d.tar.bz2 rneovim-5a9226c800d3075821203952da7c38626180680d.zip |
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
-rw-r--r-- | runtime/lua/vim/lsp/protocol.lua | 3 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 72 | ||||
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 13 |
3 files changed, 83 insertions, 5 deletions
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 877d11411b..7d5f8f5ef1 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -633,8 +633,7 @@ function protocol.make_client_capabilities() dynamicRegistration = false; completionItem = { - -- TODO(tjdevries): Is it possible to implement this in plain lua? - snippetSupport = false; + snippetSupport = true; commitCharactersSupport = false; preselectSupport = false; deprecatedSupport = false; diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index b6eaae1fef..752d4ff439 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -199,6 +199,66 @@ function M.get_current_line_to_cursor() return line:sub(pos[2]+1) 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 -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion local function sort_completion_items(items) @@ -213,9 +273,17 @@ end -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion local function get_completion_word(item) 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 - return item.insertText + if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then + return item.insertText + else + return M.parse_snippet(item.insertText) + end end return item.label end diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index e39511d0ea..f1478c782d 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -973,7 +973,14 @@ describe('LSP', function() { label='foocar', insertText='foobar', textEdit={} }, -- resolves into textEdit.newText { 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 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='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(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)) |