aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaria José Solano <majosolano99@gmail.com>2023-10-01 09:54:04 -0700
committerChristian Clason <c.clason@uni-graz.at>2023-10-02 22:21:35 +0200
commiteb1f0e8fcca756a00d287e23bf87554e0e7f6dfd (patch)
tree1715876f90bfc7de32a4f5b7a16e61bb42ae12fe
parentf736b075d371dd15fa6fffc907db7952bc19bb9d (diff)
downloadrneovim-eb1f0e8fcca756a00d287e23bf87554e0e7f6dfd.tar.gz
rneovim-eb1f0e8fcca756a00d287e23bf87554e0e7f6dfd.tar.bz2
rneovim-eb1f0e8fcca756a00d287e23bf87554e0e7f6dfd.zip
feat(lsp)!: replace snippet parser by lpeg grammar
-rw-r--r--runtime/doc/news.txt4
-rw-r--r--runtime/lua/vim/lsp/_snippet.lua500
-rw-r--r--runtime/lua/vim/lsp/util.lua35
-rw-r--r--test/functional/plugin/lsp/snippet_spec.lua243
-rw-r--r--test/functional/plugin/lsp_spec.lua4
5 files changed, 118 insertions, 668 deletions
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index db0c7b4407..3c20e52155 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -68,6 +68,10 @@ The following changes may require adaptations in user config or plugins.
• Float window support hide and show by setting `hide` on `nvim_open_win` and
`nvim_win_set_config`.
+• |vim.lsp.util.parse_snippet()| will now strictly follow the snippet grammar
+ defined by LSP, and hence previously parsed snippets might now be considered
+ invalid input.
+
==============================================================================
NEW FEATURES *news-features*
diff --git a/runtime/lua/vim/lsp/_snippet.lua b/runtime/lua/vim/lsp/_snippet.lua
deleted file mode 100644
index e7ada5415f..0000000000
--- a/runtime/lua/vim/lsp/_snippet.lua
+++ /dev/null
@@ -1,500 +0,0 @@
-local P = {}
-
----Take characters until the target characters (The escape sequence is '\' + char)
----@param targets string[] The character list for stop consuming text.
----@param specials string[] If the character isn't contained in targets/specials, '\' will be left.
-P.take_until = function(targets, specials)
- targets = targets or {}
- specials = specials or {}
-
- return function(input, pos)
- local new_pos = pos
- local raw = {}
- local esc = {}
- while new_pos <= #input do
- local c = string.sub(input, new_pos, new_pos)
- if c == '\\' then
- table.insert(raw, '\\')
- new_pos = new_pos + 1
- c = string.sub(input, new_pos, new_pos)
- if not vim.list_contains(targets, c) and not vim.list_contains(specials, c) then
- table.insert(esc, '\\')
- end
- table.insert(raw, c)
- table.insert(esc, c)
- new_pos = new_pos + 1
- else
- if vim.list_contains(targets, c) then
- break
- end
- table.insert(raw, c)
- table.insert(esc, c)
- new_pos = new_pos + 1
- end
- end
-
- if new_pos == pos then
- return P.unmatch(pos)
- end
-
- return {
- parsed = true,
- value = {
- raw = table.concat(raw, ''),
- esc = table.concat(esc, ''),
- },
- pos = new_pos,
- }
- end
-end
-
-P.unmatch = function(pos)
- return {
- parsed = false,
- value = nil,
- pos = pos,
- }
-end
-
-P.map = function(parser, map)
- return function(input, pos)
- local result = parser(input, pos)
- if result.parsed then
- return {
- parsed = true,
- value = map(result.value),
- pos = result.pos,
- }
- end
- return P.unmatch(pos)
- end
-end
-
-P.lazy = function(factory)
- return function(input, pos)
- return factory()(input, pos)
- end
-end
-
-P.token = function(token)
- return function(input, pos)
- local maybe_token = string.sub(input, pos, pos + #token - 1)
- if token == maybe_token then
- return {
- parsed = true,
- value = maybe_token,
- pos = pos + #token,
- }
- end
- return P.unmatch(pos)
- end
-end
-
-P.pattern = function(p)
- return function(input, pos)
- local maybe_match = string.match(string.sub(input, pos), '^' .. p)
- if maybe_match then
- return {
- parsed = true,
- value = maybe_match,
- pos = pos + #maybe_match,
- }
- end
- return P.unmatch(pos)
- end
-end
-
-P.many = function(parser)
- return function(input, pos)
- local values = {}
- local new_pos = pos
- while new_pos <= #input do
- local result = parser(input, new_pos)
- if not result.parsed then
- break
- end
- table.insert(values, result.value)
- new_pos = result.pos
- end
- if #values > 0 then
- return {
- parsed = true,
- value = values,
- pos = new_pos,
- }
- end
- return P.unmatch(pos)
- end
-end
-
-P.any = function(...)
- local parsers = { ... }
- return function(input, pos)
- for _, parser in ipairs(parsers) do
- local result = parser(input, pos)
- if result.parsed then
- return result
- end
- end
- return P.unmatch(pos)
- end
-end
-
-P.opt = function(parser)
- return function(input, pos)
- local result = parser(input, pos)
- return {
- parsed = true,
- value = result.value,
- pos = result.pos,
- }
- end
-end
-
-P.seq = function(...)
- local parsers = { ... }
- return function(input, pos)
- local values = {}
- local new_pos = pos
- for i, parser in ipairs(parsers) do
- local result = parser(input, new_pos)
- if result.parsed then
- values[i] = result.value
- new_pos = result.pos
- else
- return P.unmatch(pos)
- end
- end
- return {
- parsed = true,
- value = values,
- pos = new_pos,
- }
- end
-end
-
-local Node = {}
-
-Node.Type = {
- SNIPPET = 0,
- TABSTOP = 1,
- PLACEHOLDER = 2,
- VARIABLE = 3,
- CHOICE = 4,
- TRANSFORM = 5,
- FORMAT = 6,
- TEXT = 7,
-}
-
-function Node:__tostring()
- local insert_text = {}
- if self.type == Node.Type.SNIPPET then
- for _, c in ipairs(self.children) do
- table.insert(insert_text, tostring(c))
- end
- elseif self.type == Node.Type.CHOICE then
- table.insert(insert_text, self.items[1])
- elseif self.type == Node.Type.PLACEHOLDER then
- for _, c in ipairs(self.children or {}) do
- table.insert(insert_text, tostring(c))
- end
- elseif self.type == Node.Type.TEXT then
- table.insert(insert_text, self.esc)
- end
- return table.concat(insert_text, '')
-end
-
---@see https://code.visualstudio.com/docs/editor/userdefinedsnippets#_grammar
-
-local S = {}
-S.dollar = P.token('$')
-S.open = P.token('{')
-S.close = P.token('}')
-S.colon = P.token(':')
-S.slash = P.token('/')
-S.comma = P.token(',')
-S.pipe = P.token('|')
-S.plus = P.token('+')
-S.minus = P.token('-')
-S.question = P.token('?')
-S.int = P.map(P.pattern('[0-9]+'), function(value)
- return tonumber(value, 10)
-end)
-S.var = P.pattern('[%a_][%w_]+')
-S.text = function(targets, specials)
- return P.map(P.take_until(targets, specials), function(value)
- return setmetatable({
- type = Node.Type.TEXT,
- raw = value.raw,
- esc = value.esc,
- }, Node)
- end)
-end
-
-S.toplevel = P.lazy(function()
- return P.any(S.placeholder, S.tabstop, S.variable, S.choice)
-end)
-
-S.format = P.any(
- P.map(P.seq(S.dollar, S.int), function(values)
- return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[2],
- }, Node)
- end),
- P.map(P.seq(S.dollar, S.open, S.int, S.close), function(values)
- return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[3],
- }, Node)
- end),
- P.map(
- P.seq(
- S.dollar,
- S.open,
- S.int,
- S.colon,
- S.slash,
- P.any(
- P.token('upcase'),
- P.token('downcase'),
- P.token('capitalize'),
- P.token('camelcase'),
- P.token('pascalcase')
- ),
- S.close
- ),
- function(values)
- return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[3],
- modifier = values[6],
- }, Node)
- end
- ),
- P.map(
- P.seq(
- S.dollar,
- S.open,
- S.int,
- S.colon,
- P.seq(
- S.question,
- P.opt(P.take_until({ ':' }, { '\\' })),
- S.colon,
- P.opt(P.take_until({ '}' }, { '\\' }))
- ),
- S.close
- ),
- function(values)
- return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[3],
- if_text = values[5][2] and values[5][2].esc or '',
- else_text = values[5][4] and values[5][4].esc or '',
- }, Node)
- end
- ),
- P.map(
- P.seq(
- S.dollar,
- S.open,
- S.int,
- S.colon,
- P.seq(S.plus, P.opt(P.take_until({ '}' }, { '\\' }))),
- S.close
- ),
- function(values)
- return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[3],
- if_text = values[5][2] and values[5][2].esc or '',
- else_text = '',
- }, Node)
- end
- ),
- P.map(
- P.seq(
- S.dollar,
- S.open,
- S.int,
- S.colon,
- S.minus,
- P.opt(P.take_until({ '}' }, { '\\' })),
- S.close
- ),
- function(values)
- return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[3],
- if_text = '',
- else_text = values[6] and values[6].esc or '',
- }, Node)
- end
- ),
- P.map(
- P.seq(S.dollar, S.open, S.int, S.colon, P.opt(P.take_until({ '}' }, { '\\' })), S.close),
- function(values)
- return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[3],
- if_text = '',
- else_text = values[5] and values[5].esc or '',
- }, Node)
- end
- )
-)
-
-S.transform = P.map(
- P.seq(
- S.slash,
- P.take_until({ '/' }, { '\\' }),
- S.slash,
- P.many(P.any(S.format, S.text({ '$', '/' }, { '\\' }))),
- S.slash,
- P.opt(P.pattern('[ig]+'))
- ),
- function(values)
- return setmetatable({
- type = Node.Type.TRANSFORM,
- pattern = values[2].raw,
- format = values[4],
- option = values[6],
- }, Node)
- end
-)
-
-S.tabstop = P.any(
- P.map(P.seq(S.dollar, S.int), function(values)
- return setmetatable({
- type = Node.Type.TABSTOP,
- tabstop = values[2],
- }, Node)
- end),
- P.map(P.seq(S.dollar, S.open, S.int, S.close), function(values)
- return setmetatable({
- type = Node.Type.TABSTOP,
- tabstop = values[3],
- }, Node)
- end),
- P.map(P.seq(S.dollar, S.open, S.int, S.transform, S.close), function(values)
- return setmetatable({
- type = Node.Type.TABSTOP,
- tabstop = values[3],
- transform = values[4],
- }, Node)
- end)
-)
-
-S.placeholder = P.any(
- P.map(
- P.seq(
- S.dollar,
- S.open,
- S.int,
- S.colon,
- P.opt(P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' })))),
- S.close
- ),
- function(values)
- return setmetatable({
- type = Node.Type.PLACEHOLDER,
- tabstop = values[3],
- -- insert empty text if opt did not match.
- children = values[5] or {
- setmetatable({
- type = Node.Type.TEXT,
- raw = '',
- esc = '',
- }, Node),
- },
- }, Node)
- end
- )
-)
-
-S.choice = P.map(
- P.seq(
- S.dollar,
- S.open,
- S.int,
- S.pipe,
- P.many(P.map(P.seq(S.text({ ',', '|' }), P.opt(S.comma)), function(values)
- return values[1].esc
- end)),
- S.pipe,
- S.close
- ),
- function(values)
- return setmetatable({
- type = Node.Type.CHOICE,
- tabstop = values[3],
- items = values[5],
- }, Node)
- end
-)
-
-S.variable = P.any(
- P.map(P.seq(S.dollar, S.var), function(values)
- return setmetatable({
- type = Node.Type.VARIABLE,
- name = values[2],
- }, Node)
- end),
- P.map(P.seq(S.dollar, S.open, S.var, S.close), function(values)
- return setmetatable({
- type = Node.Type.VARIABLE,
- name = values[3],
- }, Node)
- end),
- P.map(P.seq(S.dollar, S.open, S.var, S.transform, S.close), function(values)
- return setmetatable({
- type = Node.Type.VARIABLE,
- name = values[3],
- transform = values[4],
- }, Node)
- end),
- P.map(
- P.seq(
- S.dollar,
- S.open,
- S.var,
- S.colon,
- P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))),
- S.close
- ),
- function(values)
- return setmetatable({
- type = Node.Type.VARIABLE,
- name = values[3],
- children = values[5],
- }, Node)
- end
- )
-)
-
-S.snippet = P.map(P.many(P.any(S.toplevel, S.text({ '$' }, { '}', '\\' }))), function(values)
- return setmetatable({
- type = Node.Type.SNIPPET,
- children = values,
- }, Node)
-end)
-
-local M = {}
-
----The snippet node type enum
----@types table<string, integer>
-M.NodeType = Node.Type
-
----Parse snippet string and returns the AST
----@param input string
----@return table
-function M.parse(input)
- local result = S.snippet(input, 1)
- if not result.parsed then
- error('snippet parsing failed.')
- end
- return result.value
-end
-
-return M
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 51ed87219c..a4c8959b99 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -1,5 +1,5 @@
local protocol = require('vim.lsp.protocol')
-local snippet = require('vim.lsp._snippet')
+local snippet = require('vim.lsp._snippet_grammar')
local validate = vim.validate
local api = vim.api
local list_extend = vim.list_extend
@@ -610,12 +610,41 @@ end
---@return string parsed snippet
function M.parse_snippet(input)
local ok, parsed = pcall(function()
- return tostring(snippet.parse(input))
+ return snippet.parse(input)
end)
if not ok then
return input
end
- return parsed
+
+ --- @param node vim.snippet.Node<any>
+ --- @return string
+ local function node_to_string(node)
+ local insert_text = {}
+ if node.type == snippet.NodeType.Snippet then
+ for _, child in
+ ipairs((node.data --[[@as vim.snippet.SnippetData]]).children)
+ do
+ table.insert(insert_text, node_to_string(child))
+ end
+ elseif node.type == snippet.NodeType.Choice then
+ table.insert(insert_text, (node.data --[[@as vim.snippet.ChoiceData]]).values[1])
+ elseif node.type == snippet.NodeType.Placeholder then
+ table.insert(
+ insert_text,
+ node_to_string((node.data --[[@as vim.snippet.PlaceholderData]]).value)
+ )
+ elseif node.type == snippet.NodeType.Text then
+ table.insert(
+ insert_text,
+ node
+ .data --[[@as vim.snippet.TextData]]
+ .text
+ )
+ end
+ return table.concat(insert_text)
+ end
+
+ return node_to_string(parsed)
end
--- Sorts by CompletionItem.sortText.
diff --git a/test/functional/plugin/lsp/snippet_spec.lua b/test/functional/plugin/lsp/snippet_spec.lua
index 7903885420..13df861b91 100644
--- a/test/functional/plugin/lsp/snippet_spec.lua
+++ b/test/functional/plugin/lsp/snippet_spec.lua
@@ -1,130 +1,70 @@
local helpers = require('test.functional.helpers')(after_each)
-local snippet = require('vim.lsp._snippet')
+local snippet = require('vim.lsp._snippet_grammar')
local eq = helpers.eq
local exec_lua = helpers.exec_lua
-describe('vim.lsp._snippet', function()
+describe('vim.lsp._snippet_grammar', function()
before_each(helpers.clear)
after_each(helpers.clear)
local parse = function(...)
- return exec_lua('return require("vim.lsp._snippet").parse(...)', ...)
+ local res = exec_lua('return require("vim.lsp._snippet_grammar").parse(...)', ...)
+ return res.data.children
end
- it('should parse only text', function()
+ it('parses only text', function()
eq({
- type = snippet.NodeType.SNIPPET,
- children = {
- {
- type = snippet.NodeType.TEXT,
- raw = 'TE\\$\\}XT',
- esc = 'TE$}XT',
- },
- },
+ { type = snippet.NodeType.Text, data = { text = 'TE$}XT' } },
}, parse('TE\\$\\}XT'))
end)
- it('should parse tabstop', function()
+ it('parses tabstops', function()
eq({
- type = snippet.NodeType.SNIPPET,
- children = {
- {
- type = snippet.NodeType.TABSTOP,
- tabstop = 1,
- },
- {
- type = snippet.NodeType.TABSTOP,
- tabstop = 2,
- },
- },
+ { type = snippet.NodeType.Tabstop, data = { tabstop = 1 } },
+ { type = snippet.NodeType.Tabstop, data = { tabstop = 2 } },
}, parse('$1${2}'))
end)
- it('should parse placeholders', function()
+ it('parses nested placeholders', function()
eq({
- type = snippet.NodeType.SNIPPET,
- children = {
- {
- type = snippet.NodeType.PLACEHOLDER,
+ {
+ type = snippet.NodeType.Placeholder,
+ data = {
tabstop = 1,
- children = {
- {
- type = snippet.NodeType.PLACEHOLDER,
+ value = {
+ type = snippet.NodeType.Placeholder,
+ data = {
tabstop = 2,
- children = {
- {
- type = snippet.NodeType.TEXT,
- raw = 'TE\\$\\}XT',
- esc = 'TE$}XT',
- },
- {
- type = snippet.NodeType.TABSTOP,
- tabstop = 3,
- },
- {
- type = snippet.NodeType.TABSTOP,
- tabstop = 1,
- transform = {
- type = snippet.NodeType.TRANSFORM,
- pattern = 'regex',
- option = 'i',
- format = {
- {
- type = snippet.NodeType.FORMAT,
- capture_index = 1,
- modifier = 'upcase',
- },
- },
- },
- },
- {
- type = snippet.NodeType.TEXT,
- raw = 'TE\\$\\}XT',
- esc = 'TE$}XT',
- },
- },
+ value = { type = snippet.NodeType.Tabstop, data = { tabstop = 3 } },
},
},
},
},
- }, parse('${1:${2:TE\\$\\}XT$3${1/regex/${1:/upcase}/i}TE\\$\\}XT}}'))
+ }, parse('${1:${2:${3}}}'))
end)
- it('should parse variables', function()
+ it('parses variables', function()
eq({
- type = snippet.NodeType.SNIPPET,
- children = {
- {
- type = snippet.NodeType.VARIABLE,
- name = 'VAR',
- },
- {
- type = snippet.NodeType.VARIABLE,
+ { type = snippet.NodeType.Variable, data = { name = 'VAR' } },
+ { type = snippet.NodeType.Variable, data = { name = 'VAR' } },
+ {
+ type = snippet.NodeType.Variable,
+ data = {
name = 'VAR',
+ default = { type = snippet.NodeType.Tabstop, data = { tabstop = 1 } },
},
- {
- type = snippet.NodeType.VARIABLE,
+ },
+ {
+ type = snippet.NodeType.Variable,
+ data = {
name = 'VAR',
- children = {
+ regex = 'regex',
+ options = '',
+ format = {
{
- type = snippet.NodeType.TABSTOP,
- tabstop = 1,
- },
- },
- },
- {
- type = snippet.NodeType.VARIABLE,
- name = 'VAR',
- transform = {
- type = snippet.NodeType.TRANSFORM,
- pattern = 'regex',
- format = {
- {
- type = snippet.NodeType.FORMAT,
- capture_index = 1,
- modifier = 'upcase',
- },
+ type = snippet.NodeType.Format,
+ data = { capture = 1, modifier = 'upcase' },
},
},
},
@@ -132,105 +72,82 @@ describe('vim.lsp._snippet', function()
}, parse('$VAR${VAR}${VAR:$1}${VAR/regex/${1:/upcase}/}'))
end)
- it('should parse choice', function()
+ it('parses choice', function()
eq({
- type = snippet.NodeType.SNIPPET,
- children = {
- {
- type = snippet.NodeType.CHOICE,
- tabstop = 1,
- items = {
- ',',
- '|',
- },
- },
+ {
+ type = snippet.NodeType.Choice,
+ data = { tabstop = 1, values = { ',', '|' } },
},
}, parse('${1|\\,,\\||}'))
end)
- it('should parse format', function()
- eq({
- type = snippet.NodeType.SNIPPET,
- children = {
+ it('parses format', function()
+ eq(
+ {
{
- type = snippet.NodeType.VARIABLE,
- name = 'VAR',
- transform = {
- type = snippet.NodeType.TRANSFORM,
- pattern = 'regex',
+ type = snippet.NodeType.Variable,
+ data = {
+ name = 'VAR',
+ regex = 'regex',
+ options = '',
format = {
{
- type = snippet.NodeType.FORMAT,
- capture_index = 1,
- modifier = 'upcase',
+ type = snippet.NodeType.Format,
+ data = { capture = 1, modifier = 'upcase' },
},
{
- type = snippet.NodeType.FORMAT,
- capture_index = 1,
- if_text = 'if_text',
- else_text = '',
+ type = snippet.NodeType.Format,
+ data = { capture = 1, if_text = 'if_text' },
},
{
- type = snippet.NodeType.FORMAT,
- capture_index = 1,
- if_text = '',
- else_text = 'else_text',
+ type = snippet.NodeType.Format,
+ data = { capture = 1, else_text = 'else_text' },
},
{
- type = snippet.NodeType.FORMAT,
- capture_index = 1,
- else_text = 'else_text',
- if_text = 'if_text',
+ type = snippet.NodeType.Format,
+ data = { capture = 1, if_text = 'if_text', else_text = 'else_text' },
},
{
- type = snippet.NodeType.FORMAT,
- capture_index = 1,
- if_text = '',
- else_text = 'else_text',
+ type = snippet.NodeType.Format,
+ data = { capture = 1, else_text = 'else_text' },
},
},
},
},
},
- }, parse('${VAR/regex/${1:/upcase}${1:+if_text}${1:-else_text}${1:?if_text:else_text}${1:else_text}/}'))
+ parse(
+ '${VAR/regex/${1:/upcase}${1:+if_text}${1:-else_text}${1:?if_text:else_text}${1:else_text}/}'
+ )
+ )
end)
- it('should parse empty strings', function()
+ it('parses empty strings', function()
eq({
- children = {
- {
- children = { {
- esc = '',
- raw = '',
- type = 7,
- } },
+ {
+ type = snippet.NodeType.Placeholder,
+ data = {
tabstop = 1,
- type = 2,
- },
- {
- esc = ' ',
- raw = ' ',
- type = 7,
+ value = { type = snippet.NodeType.Text, data = { text = '' } },
},
- {
+ },
+ {
+ type = snippet.NodeType.Text,
+ data = { text = ' ' },
+ },
+ {
+ type = snippet.NodeType.Variable,
+ data = {
name = 'VAR',
- transform = {
- format = {
- {
- capture_index = 1,
- else_text = '',
- if_text = '',
- type = 6,
- },
+ regex = 'erg',
+ format = {
+ {
+ type = snippet.NodeType.Format,
+ data = { capture = 1, if_text = '' },
},
- option = 'g',
- pattern = 'erg',
- type = 5,
},
- type = 3,
+ options = 'g',
},
},
- type = 0,
- }, parse('${1:} ${VAR/erg/${1:?:}/g}'))
+ }, parse('${1:} ${VAR/erg/${1:+}/g}'))
end)
end)
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index ef08860f10..73e05d8d11 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -2301,7 +2301,7 @@ describe('LSP', function()
{ label='foocar', sortText="g", insertText='foodar', insertTextFormat=2, textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} },
{ label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} },
-- nested snippet tokens
- { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} },
+ { label='foocar', sortText="i", insertText='foodar(${1:${2|typ1,typ2|}}) {$0\\}', insertTextFormat=2, textEdit={} },
-- braced tabstop
{ label='foocar', sortText="j", insertText='foodar()${0}', insertTextFormat=2, textEdit={} },
-- plain text
@@ -2317,7 +2317,7 @@ describe('LSP', function()
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="f", textEdit={newText='foobar'} } } } } },
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="g", insertText='foodar', insertTextFormat=2, textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } },
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, kind = 'Unknown', menu = '', word = 'foodar(var1 typ2 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, kind = 'Unknown', menu = '', word = 'foodar(typ1) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:${2|typ1,typ2|}}) {$0\\}', insertTextFormat=2, textEdit={} } } } } },
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, kind = 'Unknown', menu = '', word = 'foodar()', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="j", insertText='foodar()${0}', insertTextFormat=2, textEdit={} } } } } },
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="k", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } },
}