From f1775da07fe48da629468bcfcc2a8a6c4c3f40ed Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Fri, 20 Oct 2023 23:51:26 -0700 Subject: feat(lsp): add snippet API (#25301) --- test/functional/lua/snippet_spec.lua | 157 +++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 test/functional/lua/snippet_spec.lua (limited to 'test/functional/lua/snippet_spec.lua') diff --git a/test/functional/lua/snippet_spec.lua b/test/functional/lua/snippet_spec.lua new file mode 100644 index 0000000000..1ae1bc71d5 --- /dev/null +++ b/test/functional/lua/snippet_spec.lua @@ -0,0 +1,157 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local feed = helpers.feed +local matches = helpers.matches +local pcall_err = helpers.pcall_err + +describe('vim.snippet', function() + before_each(function() + clear() + + exec_lua([[ + vim.keymap.set({ 'i', 's' }, '', function() vim.snippet.jump(1) end, { buffer = true }) + vim.keymap.set({ 'i', 's' }, '', function() vim.snippet.jump(-1) end, { buffer = true }) + ]]) + end) + after_each(clear) + + --- @param snippet string[] + --- @param expected string[] + --- @param settings? string + --- @param prefix? string + local function test_success(snippet, expected, settings, prefix) + if settings then + exec_lua(settings) + end + if prefix then + feed('i' .. prefix) + end + exec_lua('vim.snippet.expand(...)', table.concat(snippet, '\n')) + eq(expected, helpers.buf_lines(0)) + end + + --- @param snippet string + --- @param err string + local function test_fail(snippet, err) + matches(err, pcall_err(exec_lua, string.format('vim.snippet.expand("%s")', snippet))) + end + + it('adds base indentation to inserted text', function() + test_success( + { 'function $1($2)', ' $0', 'end' }, + { ' function ()', ' ', ' end' }, + '', + ' ' + ) + end) + + it('replaces tabs with spaces when expandtab is set', function() + test_success( + { 'function $1($2)', '\t$0', 'end' }, + { 'function ()', ' ', 'end' }, + [[ + vim.o.expandtab = true + vim.o.shiftwidth = 2 + ]] + ) + end) + + it('respects tabs when expandtab is not set', function() + test_success( + { 'function $1($2)', '\t$0', 'end' }, + { 'function ()', '\t', 'end' }, + 'vim.o.expandtab = false' + ) + end) + + it('inserts known variable value', function() + test_success({ '; print($TM_CURRENT_LINE)' }, { 'foo; print(foo)' }, nil, 'foo') + end) + + it('uses default when variable is not set', function() + test_success({ 'print(${TM_CURRENT_WORD:foo})' }, { 'print(foo)' }) + end) + + it('replaces unknown variables by placeholders', function() + test_success({ 'print($UNKNOWN)' }, { 'print(UNKNOWN)' }) + end) + + it('does not jump outside snippet range', function() + test_success({ 'function $1($2)', ' $0', 'end' }, { 'function ()', ' ', 'end' }) + eq(false, exec_lua('return vim.snippet.jumpable(-1)')) + feed('i') + eq(false, exec_lua('return vim.snippet.jumpable(1)')) + end) + + it('navigates backwards', function() + test_success({ 'function $1($2) end' }, { 'function () end' }) + feed('foo') + eq({ 'function foo() end' }, helpers.buf_lines(0)) + end) + + it('visits all tabstops', function() + local function cursor() + return exec_lua('return vim.api.nvim_win_get_cursor(0)') + end + + test_success({ 'function $1($2)', ' $0', 'end' }, { 'function ()', ' ', 'end' }) + eq({ 1, 9 }, cursor()) + feed('') + eq({ 1, 10 }, cursor()) + feed('') + eq({ 2, 2 }, cursor()) + end) + + it('syncs text of tabstops with equal indexes', function() + test_success({ 'var double = ${1:x} + ${1:x}' }, { 'var double = x + x' }) + feed('123') + eq({ 'var double = 123 + 123' }, helpers.buf_lines(0)) + end) + + it('cancels session with changes outside the snippet', function() + test_success({ 'print($1)' }, { 'print()' }) + feed('O-- A comment') + eq(false, exec_lua('return vim.snippet.active()')) + eq({ '-- A comment', 'print()' }, helpers.buf_lines(0)) + end) + + it('handles non-consecutive tabstops', function() + test_success({ 'class $1($3) {', ' $0', '}' }, { 'class () {', ' ', '}' }) + feed('Foo') -- First tabstop + feed('') -- Jump to $0 + feed('// Inside') -- Insert text + eq({ 'class Foo() {', ' // Inside', '}' }, helpers.buf_lines(0)) + end) + + it('handles multiline placeholders', function() + test_success( + { 'public void foo() {', ' ${0:// TODO Auto-generated', ' throw;}', '}' }, + { 'public void foo() {', ' // TODO Auto-generated', ' throw;', '}' } + ) + end) + + it('inserts placeholder in all tabstops when the first tabstop has the placeholder', function() + test_success( + { 'for (${1:int} ${2:x} = ${3:0}; $2 < ${4:N}; $2++) {', ' $0', '}' }, + { 'for (int x = 0; x < N; x++) {', ' ', '}' } + ) + end) + + it('inserts placeholder in all tabstops when a later tabstop has the placeholder', function() + test_success( + { 'for (${1:int} $2 = ${3:0}; ${2:x} < ${4:N}; $2++) {', ' $0', '}' }, + { 'for (int x = 0; x < N; x++) {', ' ', '}' } + ) + end) + + it('errors with multiple placeholders for the same index', function() + test_fail('class ${1:Foo} { void ${1:foo}() {} }', 'multiple placeholders for tabstop $1') + end) + + it('errors with multiple $0 tabstops', function() + test_fail('function $1() { $0 }$0', 'multiple $0 tabstops') + end) +end) -- cgit From 370232dbefb91b7ee773ee9a61a9b1ad77d7f1af Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sun, 22 Oct 2023 21:21:02 -0700 Subject: fix(lsp): track snippet deletion --- test/functional/lua/snippet_spec.lua | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'test/functional/lua/snippet_spec.lua') diff --git a/test/functional/lua/snippet_spec.lua b/test/functional/lua/snippet_spec.lua index 1ae1bc71d5..fea9d1e982 100644 --- a/test/functional/lua/snippet_spec.lua +++ b/test/functional/lua/snippet_spec.lua @@ -154,4 +154,10 @@ describe('vim.snippet', function() it('errors with multiple $0 tabstops', function() test_fail('function $1() { $0 }$0', 'multiple $0 tabstops') end) + + it('cancels session when deleting the snippet', function() + test_success({ 'local function $1()', ' $0', 'end' }, { 'local function ()', ' ', 'end' }) + feed('Vjjd') + eq(false, exec_lua('return vim.snippet.active()')) + end) end) -- cgit From 94127cb5df0a513e66777d18a2c7fa6219404280 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sun, 22 Oct 2023 22:38:11 -0700 Subject: fix(lsp): do not add extra indentation --- test/functional/lua/snippet_spec.lua | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'test/functional/lua/snippet_spec.lua') diff --git a/test/functional/lua/snippet_spec.lua b/test/functional/lua/snippet_spec.lua index fea9d1e982..738420d87d 100644 --- a/test/functional/lua/snippet_spec.lua +++ b/test/functional/lua/snippet_spec.lua @@ -48,6 +48,10 @@ describe('vim.snippet', function() ) end) + it('adds indentation based on the start of snippet lines', function() + test_success({ 'if $1 then', ' $0', 'end' }, { 'if then', ' ', 'end' }) + end) + it('replaces tabs with spaces when expandtab is set', function() test_success( { 'function $1($2)', '\t$0', 'end' }, -- cgit From 15983cf2c64c527fc13681925d0d00c070c30640 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Wed, 25 Oct 2023 22:29:05 -0700 Subject: fix(lsp): cancel session when leaving snippet region (#25762) --- test/functional/lua/snippet_spec.lua | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'test/functional/lua/snippet_spec.lua') diff --git a/test/functional/lua/snippet_spec.lua b/test/functional/lua/snippet_spec.lua index 738420d87d..390f268925 100644 --- a/test/functional/lua/snippet_spec.lua +++ b/test/functional/lua/snippet_spec.lua @@ -164,4 +164,11 @@ describe('vim.snippet', function() feed('Vjjd') eq(false, exec_lua('return vim.snippet.active()')) end) + + it('cancels session when leaving snippet region', function() + feed('i') + test_success({ 'local function $1()', ' $0', 'end' }, { '', 'local function ()', ' ', 'end' }) + feed('k') + eq(false, exec_lua('return vim.snippet.active()')) + end) end) -- cgit From 0fe0cf5adaab06b92250eb350306de63c4d4f36f Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Mon, 30 Oct 2023 04:58:28 -0700 Subject: fix(lsp): do not cancel snippet when selecting placeholder (#25835) --- test/functional/lua/snippet_spec.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'test/functional/lua/snippet_spec.lua') diff --git a/test/functional/lua/snippet_spec.lua b/test/functional/lua/snippet_spec.lua index 390f268925..70337d1572 100644 --- a/test/functional/lua/snippet_spec.lua +++ b/test/functional/lua/snippet_spec.lua @@ -165,10 +165,10 @@ describe('vim.snippet', function() eq(false, exec_lua('return vim.snippet.active()')) end) - it('cancels session when leaving snippet region', function() + it('cancels session when inserting outside snippet region', function() feed('i') test_success({ 'local function $1()', ' $0', 'end' }, { '', 'local function ()', ' ', 'end' }) - feed('k') + feed('O-- A comment') eq(false, exec_lua('return vim.snippet.active()')) end) end) -- cgit From 7e36c8e972f0b2e07c6186aa5dca2f70d95a77f2 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Thu, 26 Oct 2023 22:53:38 -0700 Subject: feat(lsp): support for choice snippet nodes --- test/functional/lua/snippet_spec.lua | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'test/functional/lua/snippet_spec.lua') diff --git a/test/functional/lua/snippet_spec.lua b/test/functional/lua/snippet_spec.lua index 70337d1572..bf43d5114f 100644 --- a/test/functional/lua/snippet_spec.lua +++ b/test/functional/lua/snippet_spec.lua @@ -6,6 +6,7 @@ local exec_lua = helpers.exec_lua local feed = helpers.feed local matches = helpers.matches local pcall_err = helpers.pcall_err +local sleep = helpers.sleep describe('vim.snippet', function() before_each(function() @@ -171,4 +172,30 @@ describe('vim.snippet', function() feed('O-- A comment') eq(false, exec_lua('return vim.snippet.active()')) end) + + it('inserts choice', function () + test_success({ 'console.${1|assert,log,error|}()' }, { 'console.()' }) + sleep(100) + feed('') + eq({ 'console.log()' }, helpers.buf_lines(0)) + end) + + it('closes the choice completion menu when jumping', function () + test_success({ 'console.${1|assert,log,error|}($2)' }, { 'console.()' }) + sleep(100) + exec_lua('vim.snippet.jump(1)') + eq(0, exec_lua('return vim.fn.pumvisible()')) + end) + + it('jumps to next tabstop after inserting choice', function() + test_success( + { '${1|public,protected,private|} function ${2:name}() {', '\t$0', '}' }, + { ' function name() {', '\t', '}' } + ) + sleep(100) + feed('') + sleep(10) + feed('foo') + eq({ 'public function foo() {', '\t', '}' }, helpers.buf_lines(0)) + end) end) -- cgit From 4972c80489af263c96865c43615bea0977a18b77 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sat, 11 Nov 2023 18:07:46 -0800 Subject: refactor(snippet): rename test utilities --- test/functional/lua/snippet_spec.lua | 65 ++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 32 deletions(-) (limited to 'test/functional/lua/snippet_spec.lua') diff --git a/test/functional/lua/snippet_spec.lua b/test/functional/lua/snippet_spec.lua index bf43d5114f..f0b3b44139 100644 --- a/test/functional/lua/snippet_spec.lua +++ b/test/functional/lua/snippet_spec.lua @@ -1,5 +1,6 @@ local helpers = require('test.functional.helpers')(after_each) +local buf_lines = helpers.buf_lines local clear = helpers.clear local eq = helpers.eq local exec_lua = helpers.exec_lua @@ -23,7 +24,7 @@ describe('vim.snippet', function() --- @param expected string[] --- @param settings? string --- @param prefix? string - local function test_success(snippet, expected, settings, prefix) + local function test_expand_success(snippet, expected, settings, prefix) if settings then exec_lua(settings) end @@ -31,17 +32,17 @@ describe('vim.snippet', function() feed('i' .. prefix) end exec_lua('vim.snippet.expand(...)', table.concat(snippet, '\n')) - eq(expected, helpers.buf_lines(0)) + eq(expected, buf_lines(0)) end --- @param snippet string --- @param err string - local function test_fail(snippet, err) + local function test_expand_fail(snippet, err) matches(err, pcall_err(exec_lua, string.format('vim.snippet.expand("%s")', snippet))) end it('adds base indentation to inserted text', function() - test_success( + test_expand_success( { 'function $1($2)', ' $0', 'end' }, { ' function ()', ' ', ' end' }, '', @@ -50,11 +51,11 @@ describe('vim.snippet', function() end) it('adds indentation based on the start of snippet lines', function() - test_success({ 'if $1 then', ' $0', 'end' }, { 'if then', ' ', 'end' }) + test_expand_success({ 'if $1 then', ' $0', 'end' }, { 'if then', ' ', 'end' }) end) it('replaces tabs with spaces when expandtab is set', function() - test_success( + test_expand_success( { 'function $1($2)', '\t$0', 'end' }, { 'function ()', ' ', 'end' }, [[ @@ -65,7 +66,7 @@ describe('vim.snippet', function() end) it('respects tabs when expandtab is not set', function() - test_success( + test_expand_success( { 'function $1($2)', '\t$0', 'end' }, { 'function ()', '\t', 'end' }, 'vim.o.expandtab = false' @@ -73,28 +74,28 @@ describe('vim.snippet', function() end) it('inserts known variable value', function() - test_success({ '; print($TM_CURRENT_LINE)' }, { 'foo; print(foo)' }, nil, 'foo') + test_expand_success({ '; print($TM_CURRENT_LINE)' }, { 'foo; print(foo)' }, nil, 'foo') end) it('uses default when variable is not set', function() - test_success({ 'print(${TM_CURRENT_WORD:foo})' }, { 'print(foo)' }) + test_expand_success({ 'print(${TM_CURRENT_WORD:foo})' }, { 'print(foo)' }) end) it('replaces unknown variables by placeholders', function() - test_success({ 'print($UNKNOWN)' }, { 'print(UNKNOWN)' }) + test_expand_success({ 'print($UNKNOWN)' }, { 'print(UNKNOWN)' }) end) it('does not jump outside snippet range', function() - test_success({ 'function $1($2)', ' $0', 'end' }, { 'function ()', ' ', 'end' }) + test_expand_success({ 'function $1($2)', ' $0', 'end' }, { 'function ()', ' ', 'end' }) eq(false, exec_lua('return vim.snippet.jumpable(-1)')) feed('i') eq(false, exec_lua('return vim.snippet.jumpable(1)')) end) it('navigates backwards', function() - test_success({ 'function $1($2) end' }, { 'function () end' }) + test_expand_success({ 'function $1($2) end' }, { 'function () end' }) feed('foo') - eq({ 'function foo() end' }, helpers.buf_lines(0)) + eq({ 'function foo() end' }, buf_lines(0)) end) it('visits all tabstops', function() @@ -102,7 +103,7 @@ describe('vim.snippet', function() return exec_lua('return vim.api.nvim_win_get_cursor(0)') end - test_success({ 'function $1($2)', ' $0', 'end' }, { 'function ()', ' ', 'end' }) + test_expand_success({ 'function $1($2)', ' $0', 'end' }, { 'function ()', ' ', 'end' }) eq({ 1, 9 }, cursor()) feed('') eq({ 1, 10 }, cursor()) @@ -111,84 +112,84 @@ describe('vim.snippet', function() end) it('syncs text of tabstops with equal indexes', function() - test_success({ 'var double = ${1:x} + ${1:x}' }, { 'var double = x + x' }) + test_expand_success({ 'var double = ${1:x} + ${1:x}' }, { 'var double = x + x' }) feed('123') - eq({ 'var double = 123 + 123' }, helpers.buf_lines(0)) + eq({ 'var double = 123 + 123' }, buf_lines(0)) end) it('cancels session with changes outside the snippet', function() - test_success({ 'print($1)' }, { 'print()' }) + test_expand_success({ 'print($1)' }, { 'print()' }) feed('O-- A comment') eq(false, exec_lua('return vim.snippet.active()')) - eq({ '-- A comment', 'print()' }, helpers.buf_lines(0)) + eq({ '-- A comment', 'print()' }, buf_lines(0)) end) it('handles non-consecutive tabstops', function() - test_success({ 'class $1($3) {', ' $0', '}' }, { 'class () {', ' ', '}' }) + test_expand_success({ 'class $1($3) {', ' $0', '}' }, { 'class () {', ' ', '}' }) feed('Foo') -- First tabstop feed('') -- Jump to $0 feed('// Inside') -- Insert text - eq({ 'class Foo() {', ' // Inside', '}' }, helpers.buf_lines(0)) + eq({ 'class Foo() {', ' // Inside', '}' }, buf_lines(0)) end) it('handles multiline placeholders', function() - test_success( + test_expand_success( { 'public void foo() {', ' ${0:// TODO Auto-generated', ' throw;}', '}' }, { 'public void foo() {', ' // TODO Auto-generated', ' throw;', '}' } ) end) it('inserts placeholder in all tabstops when the first tabstop has the placeholder', function() - test_success( + test_expand_success( { 'for (${1:int} ${2:x} = ${3:0}; $2 < ${4:N}; $2++) {', ' $0', '}' }, { 'for (int x = 0; x < N; x++) {', ' ', '}' } ) end) it('inserts placeholder in all tabstops when a later tabstop has the placeholder', function() - test_success( + test_expand_success( { 'for (${1:int} $2 = ${3:0}; ${2:x} < ${4:N}; $2++) {', ' $0', '}' }, { 'for (int x = 0; x < N; x++) {', ' ', '}' } ) end) it('errors with multiple placeholders for the same index', function() - test_fail('class ${1:Foo} { void ${1:foo}() {} }', 'multiple placeholders for tabstop $1') + test_expand_fail('class ${1:Foo} { void ${1:foo}() {} }', 'multiple placeholders for tabstop $1') end) it('errors with multiple $0 tabstops', function() - test_fail('function $1() { $0 }$0', 'multiple $0 tabstops') + test_expand_fail('function $1() { $0 }$0', 'multiple $0 tabstops') end) it('cancels session when deleting the snippet', function() - test_success({ 'local function $1()', ' $0', 'end' }, { 'local function ()', ' ', 'end' }) + test_expand_success({ 'local function $1()', ' $0', 'end' }, { 'local function ()', ' ', 'end' }) feed('Vjjd') eq(false, exec_lua('return vim.snippet.active()')) end) it('cancels session when inserting outside snippet region', function() feed('i') - test_success({ 'local function $1()', ' $0', 'end' }, { '', 'local function ()', ' ', 'end' }) + test_expand_success({ 'local function $1()', ' $0', 'end' }, { '', 'local function ()', ' ', 'end' }) feed('O-- A comment') eq(false, exec_lua('return vim.snippet.active()')) end) it('inserts choice', function () - test_success({ 'console.${1|assert,log,error|}()' }, { 'console.()' }) + test_expand_success({ 'console.${1|assert,log,error|}()' }, { 'console.()' }) sleep(100) feed('') - eq({ 'console.log()' }, helpers.buf_lines(0)) + eq({ 'console.log()' }, buf_lines(0)) end) it('closes the choice completion menu when jumping', function () - test_success({ 'console.${1|assert,log,error|}($2)' }, { 'console.()' }) + test_expand_success({ 'console.${1|assert,log,error|}($2)' }, { 'console.()' }) sleep(100) exec_lua('vim.snippet.jump(1)') eq(0, exec_lua('return vim.fn.pumvisible()')) end) it('jumps to next tabstop after inserting choice', function() - test_success( + test_expand_success( { '${1|public,protected,private|} function ${2:name}() {', '\t$0', '}' }, { ' function name() {', '\t', '}' } ) @@ -196,6 +197,6 @@ describe('vim.snippet', function() feed('') sleep(10) feed('foo') - eq({ 'public function foo() {', '\t', '}' }, helpers.buf_lines(0)) + eq({ 'public function foo() {', '\t', '}' }, buf_lines(0)) end) end) -- cgit