diff options
Diffstat (limited to 'test/functional/lua/comment_spec.lua')
-rw-r--r-- | test/functional/lua/comment_spec.lua | 651 |
1 files changed, 651 insertions, 0 deletions
diff --git a/test/functional/lua/comment_spec.lua b/test/functional/lua/comment_spec.lua new file mode 100644 index 0000000000..bbf061a2ab --- /dev/null +++ b/test/functional/lua/comment_spec.lua @@ -0,0 +1,651 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +local api = n.api +local clear = n.clear +local eq = t.eq +local exec_capture = n.exec_capture +local exec_lua = n.exec_lua +local feed = n.feed + +-- Reference text +-- aa +-- aa +-- aa +-- +-- aa +-- aa +-- aa +local example_lines = { 'aa', ' aa', ' aa', '', ' aa', ' aa', 'aa' } + +local set_commentstring = function(commentstring) + api.nvim_set_option_value('commentstring', commentstring, { buf = 0 }) +end + +local get_lines = function(from, to) + from, to = from or 0, to or -1 + return api.nvim_buf_get_lines(0, from, to, false) +end + +local set_lines = function(lines, from, to) + from, to = from or 0, to or -1 + api.nvim_buf_set_lines(0, from, to, false, lines) +end + +local set_cursor = function(row, col) + api.nvim_win_set_cursor(0, { row, col }) +end + +local get_cursor = function() + return api.nvim_win_get_cursor(0) +end + +local setup_treesitter = function() + -- NOTE: This leverages bundled Vimscript and Lua tree-sitter parsers + api.nvim_set_option_value('filetype', 'vim', { buf = 0 }) + exec_lua('vim.treesitter.start()') +end + +before_each(function() + clear({ args_rm = { '--cmd' }, args = { '--clean' } }) +end) + +describe('commenting', function() + before_each(function() + set_lines(example_lines) + set_commentstring('# %s') + end) + + describe('toggle_lines()', function() + local toggle_lines = function(...) + exec_lua('require("vim._comment").toggle_lines(...)', ...) + end + + it('works', function() + toggle_lines(3, 5) + eq(get_lines(2, 5), { ' # aa', ' #', ' # aa' }) + + toggle_lines(3, 5) + eq(get_lines(2, 5), { ' aa', '', ' aa' }) + end) + + it("works with different 'commentstring' options", function() + local validate = function(lines_before, lines_after, lines_again) + set_lines(lines_before) + toggle_lines(1, #lines_before) + eq(get_lines(), lines_after) + toggle_lines(1, #lines_before) + eq(get_lines(), lines_again or lines_before) + end + + -- Single whitespace inside comment parts (main case) + set_commentstring('# %s #') + -- - General case + validate( + { 'aa', ' aa', 'aa ', ' aa ' }, + { '# aa #', '# aa #', '# aa #', '# aa #' } + ) + -- - Tabs + validate( + { 'aa', '\taa', 'aa\t', '\taa\t' }, + { '# aa #', '# \taa #', '# aa\t #', '# \taa\t #' } + ) + -- - With indent + validate({ ' aa', ' aa' }, { ' # aa #', ' # aa #' }) + -- - With blank/empty lines + validate( + { ' aa', '', ' ', '\t' }, + { ' # aa #', ' ##', ' ##', ' ##' }, + { ' aa', '', '', '' } + ) + + set_commentstring('# %s') + validate({ 'aa', ' aa', 'aa ', ' aa ' }, { '# aa', '# aa', '# aa ', '# aa ' }) + validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { '# aa', '# \taa', '# aa\t', '# \taa\t' }) + validate({ ' aa', ' aa' }, { ' # aa', ' # aa' }) + validate( + { ' aa', '', ' ', '\t' }, + { ' # aa', ' #', ' #', ' #' }, + { ' aa', '', '', '' } + ) + + set_commentstring('%s #') + validate({ 'aa', ' aa', 'aa ', ' aa ' }, { 'aa #', ' aa #', 'aa #', ' aa #' }) + validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { 'aa #', '\taa #', 'aa\t #', '\taa\t #' }) + validate({ ' aa', ' aa' }, { ' aa #', ' aa #' }) + validate( + { ' aa', '', ' ', '\t' }, + { ' aa #', ' #', ' #', ' #' }, + { ' aa', '', '', '' } + ) + + -- No whitespace in parts + set_commentstring('#%s#') + validate({ 'aa', ' aa', 'aa ', ' aa ' }, { '#aa#', '# aa#', '#aa #', '# aa #' }) + validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { '#aa#', '#\taa#', '#aa\t#', '#\taa\t#' }) + validate({ ' aa', ' aa' }, { ' #aa#', ' # aa#' }) + validate( + { ' aa', '', ' ', '\t' }, + { ' #aa#', ' ##', ' ##', ' ##' }, + { ' aa', '', '', '' } + ) + + set_commentstring('#%s') + validate({ 'aa', ' aa', 'aa ', ' aa ' }, { '#aa', '# aa', '#aa ', '# aa ' }) + validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { '#aa', '#\taa', '#aa\t', '#\taa\t' }) + validate({ ' aa', ' aa' }, { ' #aa', ' # aa' }) + validate({ ' aa', '', ' ', '\t' }, { ' #aa', ' #', ' #', ' #' }, { ' aa', '', '', '' }) + + set_commentstring('%s#') + validate({ 'aa', ' aa', 'aa ', ' aa ' }, { 'aa#', ' aa#', 'aa #', ' aa #' }) + validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { 'aa#', '\taa#', 'aa\t#', '\taa\t#' }) + validate({ ' aa', ' aa' }, { ' aa#', ' aa#' }) + validate({ ' aa', '', ' ', '\t' }, { ' aa#', ' #', ' #', ' #' }, { ' aa', '', '', '' }) + + -- Extra whitespace inside comment parts + set_commentstring('# %s #') + validate( + { 'aa', ' aa', 'aa ', ' aa ' }, + { '# aa #', '# aa #', '# aa #', '# aa #' } + ) + validate( + { 'aa', '\taa', 'aa\t', '\taa\t' }, + { '# aa #', '# \taa #', '# aa\t #', '# \taa\t #' } + ) + validate({ ' aa', ' aa' }, { ' # aa #', ' # aa #' }) + validate( + { ' aa', '', ' ', '\t' }, + { ' # aa #', ' ##', ' ##', ' ##' }, + { ' aa', '', '', '' } + ) + + set_commentstring('# %s') + validate({ 'aa', ' aa', 'aa ', ' aa ' }, { '# aa', '# aa', '# aa ', '# aa ' }) + validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { '# aa', '# \taa', '# aa\t', '# \taa\t' }) + validate({ ' aa', ' aa' }, { ' # aa', ' # aa' }) + validate( + { ' aa', '', ' ', '\t' }, + { ' # aa', ' #', ' #', ' #' }, + { ' aa', '', '', '' } + ) + + set_commentstring('%s #') + validate({ 'aa', ' aa', 'aa ', ' aa ' }, { 'aa #', ' aa #', 'aa #', ' aa #' }) + validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { 'aa #', '\taa #', 'aa\t #', '\taa\t #' }) + validate({ ' aa', ' aa' }, { ' aa #', ' aa #' }) + validate( + { ' aa', '', ' ', '\t' }, + { ' aa #', ' #', ' #', ' #' }, + { ' aa', '', '', '' } + ) + + -- Whitespace outside of comment parts + set_commentstring(' # %s # ') + validate( + { 'aa', ' aa', 'aa ', ' aa ' }, + { ' # aa # ', ' # aa # ', ' # aa # ', ' # aa # ' } + ) + validate( + { 'aa', '\taa', 'aa\t', '\taa\t' }, + { ' # aa # ', ' # \taa # ', ' # aa\t # ', ' # \taa\t # ' } + ) + validate({ ' aa', ' aa' }, { ' # aa # ', ' # aa # ' }) + validate( + { ' aa', '', ' ', '\t' }, + { ' # aa # ', ' ##', ' ##', ' ##' }, + { ' aa', '', '', '' } + ) + + set_commentstring(' # %s ') + validate( + { 'aa', ' aa', 'aa ', ' aa ' }, + { ' # aa ', ' # aa ', ' # aa ', ' # aa ' } + ) + validate( + { 'aa', '\taa', 'aa\t', '\taa\t' }, + { ' # aa ', ' # \taa ', ' # aa\t ', ' # \taa\t ' } + ) + validate({ ' aa', ' aa' }, { ' # aa ', ' # aa ' }) + validate( + { ' aa', '', ' ', '\t' }, + { ' # aa ', ' #', ' #', ' #' }, + { ' aa', '', '', '' } + ) + + set_commentstring(' %s # ') + validate( + { 'aa', ' aa', 'aa ', ' aa ' }, + { ' aa # ', ' aa # ', ' aa # ', ' aa # ' } + ) + validate( + { 'aa', '\taa', 'aa\t', '\taa\t' }, + { ' aa # ', ' \taa # ', ' aa\t # ', ' \taa\t # ' } + ) + validate({ ' aa', ' aa' }, { ' aa # ', ' aa # ' }) + validate( + { ' aa', '', ' ', '\t' }, + { ' aa # ', ' #', ' #', ' #' }, + { ' aa', '', '', '' } + ) + + -- LaTeX + set_commentstring('% %s') + validate({ 'aa', ' aa', 'aa ', ' aa ' }, { '% aa', '% aa', '% aa ', '% aa ' }) + validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { '% aa', '% \taa', '% aa\t', '% \taa\t' }) + validate({ ' aa', ' aa' }, { ' % aa', ' % aa' }) + validate( + { ' aa', '', ' ', '\t' }, + { ' % aa', ' %', ' %', ' %' }, + { ' aa', '', '', '' } + ) + end) + + it('respects tree-sitter injections', function() + setup_treesitter() + + local lines = { + 'set background=dark', + 'lua << EOF', + 'print(1)', + 'vim.api.nvim_exec2([[', + ' set background=light', + ']])', + 'EOF', + } + + -- Single line comments + local validate = function(line, ref_output) + set_lines(lines) + toggle_lines(line, line) + eq(get_lines(line - 1, line)[1], ref_output) + end + + validate(1, '"set background=dark') + validate(2, '"lua << EOF') + validate(3, '-- print(1)') + validate(4, '-- vim.api.nvim_exec2([[') + validate(5, ' "set background=light') + validate(6, '-- ]])') + validate(7, '"EOF') + + -- Multiline comments should be computed based on first line 'commentstring' + set_lines(lines) + toggle_lines(1, 3) + local out_lines = get_lines() + eq(out_lines[1], '"set background=dark') + eq(out_lines[2], '"lua << EOF') + eq(out_lines[3], '"print(1)') + end) + + it('correctly computes indent', function() + toggle_lines(2, 4) + eq(get_lines(1, 4), { ' # aa', ' # aa', ' #' }) + end) + + it('correctly detects comment/uncomment', function() + local validate = function(from, to, ref_lines) + set_lines({ '', 'aa', '# aa', '# aa', 'aa', '' }) + toggle_lines(from, to) + eq(get_lines(), ref_lines) + end + + -- It should uncomment only if all non-blank lines are comments + validate(3, 4, { '', 'aa', 'aa', 'aa', 'aa', '' }) + validate(2, 4, { '', '# aa', '# # aa', '# # aa', 'aa', '' }) + validate(3, 5, { '', 'aa', '# # aa', '# # aa', '# aa', '' }) + validate(1, 6, { '#', '# aa', '# # aa', '# # aa', '# aa', '#' }) + + -- Blank lines should be ignored when making a decision + set_lines({ '# aa', '', ' ', '\t', '# aa' }) + toggle_lines(1, 5) + eq(get_lines(), { 'aa', '', ' ', '\t', 'aa' }) + end) + + it('correctly matches comment parts during checking and uncommenting', function() + local validate = function(from, to, ref_lines) + set_lines({ '/*aa*/', '/* aa */', '/* aa */' }) + toggle_lines(from, to) + eq(get_lines(), ref_lines) + end + + -- Should first try to match 'commentstring' parts exactly with their + -- whitespace, with fallback on trimmed parts + set_commentstring('/*%s*/') + validate(1, 3, { 'aa', ' aa ', ' aa ' }) + validate(2, 3, { '/*aa*/', ' aa ', ' aa ' }) + validate(3, 3, { '/*aa*/', '/* aa */', ' aa ' }) + + set_commentstring('/* %s */') + validate(1, 3, { 'aa', 'aa', ' aa ' }) + validate(2, 3, { '/*aa*/', 'aa', ' aa ' }) + validate(3, 3, { '/*aa*/', '/* aa */', ' aa ' }) + + set_commentstring('/* %s */') + validate(1, 3, { 'aa', ' aa ', 'aa' }) + validate(2, 3, { '/*aa*/', ' aa ', 'aa' }) + validate(3, 3, { '/*aa*/', '/* aa */', 'aa' }) + + set_commentstring(' /*%s*/ ') + validate(1, 3, { 'aa', ' aa ', ' aa ' }) + validate(2, 3, { '/*aa*/', ' aa ', ' aa ' }) + validate(3, 3, { '/*aa*/', '/* aa */', ' aa ' }) + end) + + it('uncomments on inconsistent indent levels', function() + set_lines({ '# aa', ' # aa', ' # aa' }) + toggle_lines(1, 3) + eq(get_lines(), { 'aa', ' aa', ' aa' }) + end) + + it('respects tabs', function() + api.nvim_set_option_value('expandtab', false, { buf = 0 }) + set_lines({ '\t\taa', '\t\taa' }) + + toggle_lines(1, 2) + eq(get_lines(), { '\t\t# aa', '\t\t# aa' }) + + toggle_lines(1, 2) + eq(get_lines(), { '\t\taa', '\t\taa' }) + end) + + it('works with trailing whitespace', function() + -- Without right-hand side + set_commentstring('# %s') + set_lines({ ' aa', ' aa ', ' ' }) + toggle_lines(1, 3) + eq(get_lines(), { ' # aa', ' # aa ', ' #' }) + toggle_lines(1, 3) + eq(get_lines(), { ' aa', ' aa ', '' }) + + -- With right-hand side + set_commentstring('%s #') + set_lines({ ' aa', ' aa ', ' ' }) + toggle_lines(1, 3) + eq(get_lines(), { ' aa #', ' aa #', ' #' }) + toggle_lines(1, 3) + eq(get_lines(), { ' aa', ' aa ', '' }) + + -- Trailing whitespace after right side should be preserved for non-blanks + set_commentstring('%s #') + set_lines({ ' aa # ', ' aa #\t', ' # ', ' #\t' }) + toggle_lines(1, 4) + eq(get_lines(), { ' aa ', ' aa\t', '', '' }) + end) + end) + + describe('Operator', function() + it('works in Normal mode', function() + set_cursor(2, 2) + feed('gc', 'ap') + eq(get_lines(), { '# aa', '# aa', '# aa', '#', ' aa', ' aa', 'aa' }) + -- Cursor moves to start line + eq(get_cursor(), { 1, 0 }) + + -- Supports `v:count` + set_lines(example_lines) + set_cursor(2, 0) + feed('2gc', 'ap') + eq(get_lines(), { '# aa', '# aa', '# aa', '#', '# aa', '# aa', '# aa' }) + end) + + it('allows dot-repeat in Normal mode', function() + local doubly_commented = { '# # aa', '# # aa', '# # aa', '# #', '# aa', '# aa', '# aa' } + + set_lines(example_lines) + set_cursor(2, 2) + feed('gc', 'ap') + feed('.') + eq(get_lines(), doubly_commented) + + -- Not immediate dot-repeat + set_lines(example_lines) + set_cursor(2, 2) + feed('gc', 'ap') + set_cursor(7, 0) + feed('.') + eq(get_lines(), doubly_commented) + end) + + it('works in Visual mode', function() + set_cursor(2, 2) + feed('v', 'ap', 'gc') + eq(get_lines(), { '# aa', '# aa', '# aa', '#', ' aa', ' aa', 'aa' }) + + -- Cursor moves to start line + eq(get_cursor(), { 1, 0 }) + end) + + it('allows dot-repeat after initial Visual mode', function() + -- local example_lines = { 'aa', ' aa', ' aa', '', ' aa', ' aa', 'aa' } + + set_lines(example_lines) + set_cursor(2, 2) + feed('vip', 'gc') + eq(get_lines(), { '# aa', '# aa', '# aa', '', ' aa', ' aa', 'aa' }) + eq(get_cursor(), { 1, 0 }) + + -- Dot-repeat after first application in Visual mode should apply to the same + -- relative region + feed('.') + eq(get_lines(), example_lines) + + set_cursor(3, 0) + feed('.') + eq(get_lines(), { 'aa', ' aa', ' # aa', ' #', ' # aa', ' aa', 'aa' }) + end) + + it("respects 'commentstring'", function() + set_commentstring('/*%s*/') + set_cursor(2, 2) + feed('gc', 'ap') + eq(get_lines(), { '/*aa*/', '/* aa*/', '/* aa*/', '/**/', ' aa', ' aa', 'aa' }) + end) + + it("works with empty 'commentstring'", function() + set_commentstring('') + set_cursor(2, 2) + feed('gc', 'ap') + eq(get_lines(), example_lines) + eq(exec_capture('1messages'), [[Option 'commentstring' is empty.]]) + end) + + it('respects tree-sitter injections', function() + setup_treesitter() + + local lines = { + 'set background=dark', + 'lua << EOF', + 'print(1)', + 'vim.api.nvim_exec2([[', + ' set background=light', + ']])', + 'EOF', + } + + -- Single line comments + local validate = function(line, ref_output) + set_lines(lines) + set_cursor(line, 0) + feed('gc_') + eq(get_lines(line - 1, line)[1], ref_output) + end + + validate(1, '"set background=dark') + validate(2, '"lua << EOF') + validate(3, '-- print(1)') + validate(4, '-- vim.api.nvim_exec2([[') + validate(5, ' "set background=light') + validate(6, '-- ]])') + validate(7, '"EOF') + + -- Has proper dot-repeat which recomputes 'commentstring' + set_lines(lines) + + set_cursor(1, 0) + feed('gc_') + eq(get_lines()[1], '"set background=dark') + + set_cursor(3, 0) + feed('.') + eq(get_lines()[3], '-- print(1)') + + -- Multiline comments should be computed based on cursor position + -- which in case of Visual selection means its left part + set_lines(lines) + set_cursor(1, 0) + feed('v2j', 'gc') + local out_lines = get_lines() + eq(out_lines[1], '"set background=dark') + eq(out_lines[2], '"lua << EOF') + eq(out_lines[3], '"print(1)') + end) + + it("recomputes local 'commentstring' based on cursor position", function() + setup_treesitter() + local lines = { + ' print(1)', + 'lua << EOF', + ' print(1)', + 'EOF', + } + set_lines(lines) + + set_cursor(1, 1) + feed('gc_') + eq(get_lines()[1], ' "print(1)') + + set_lines(lines) + set_cursor(3, 2) + feed('.') + eq(get_lines()[3], ' -- print(1)') + end) + + it('preserves marks', function() + set_cursor(2, 0) + -- Set '`<' and '`>' marks + feed('VV') + feed('gc', 'ip') + eq(api.nvim_buf_get_mark(0, '<'), { 2, 0 }) + eq(api.nvim_buf_get_mark(0, '>'), { 2, 2147483647 }) + end) + end) + + describe('Current line', function() + it('works', function() + set_lines(example_lines) + set_cursor(1, 1) + feed('gcc') + eq(get_lines(0, 2), { '# aa', ' aa' }) + + -- Does not comment empty line + set_lines(example_lines) + set_cursor(4, 0) + feed('gcc') + eq(get_lines(2, 5), { ' aa', '', ' aa' }) + + -- Supports `v:count` + set_lines(example_lines) + set_cursor(2, 0) + feed('2gcc') + eq(get_lines(0, 3), { 'aa', ' # aa', ' # aa' }) + end) + + it('allows dot-repeat', function() + set_lines(example_lines) + set_cursor(1, 1) + feed('gcc') + feed('.') + eq(get_lines(), example_lines) + + -- Not immediate dot-repeat + set_lines(example_lines) + set_cursor(1, 1) + feed('gcc') + set_cursor(7, 0) + feed('.') + eq(get_lines(6, 7), { '# aa' }) + end) + + it('respects tree-sitter injections', function() + setup_treesitter() + + local lines = { + 'set background=dark', + 'lua << EOF', + 'print(1)', + 'EOF', + } + set_lines(lines) + + set_cursor(1, 0) + feed('gcc') + eq(get_lines(), { '"set background=dark', 'lua << EOF', 'print(1)', 'EOF' }) + + -- Should work with dot-repeat + set_cursor(3, 0) + feed('.') + eq(get_lines(), { '"set background=dark', 'lua << EOF', '-- print(1)', 'EOF' }) + end) + end) + + describe('Textobject', function() + it('works', function() + set_lines({ 'aa', '# aa', '# aa', 'aa' }) + set_cursor(2, 0) + feed('d', 'gc') + eq(get_lines(), { 'aa', 'aa' }) + end) + + it('allows dot-repeat', function() + set_lines({ 'aa', '# aa', '# aa', 'aa', '# aa' }) + set_cursor(2, 0) + feed('d', 'gc') + set_cursor(3, 0) + feed('.') + eq(get_lines(), { 'aa', 'aa' }) + end) + + it('does nothing when not inside textobject', function() + -- Builtin operators + feed('d', 'gc') + eq(get_lines(), example_lines) + + -- Comment operator + local validate_no_action = function(line, col) + set_lines(example_lines) + set_cursor(line, col) + feed('gc', 'gc') + eq(get_lines(), example_lines) + end + + validate_no_action(1, 1) + validate_no_action(2, 2) + + -- Doesn't work (but should) because both `[` and `]` are set to (1, 0) + -- (instead of more reasonable (1, -1) or (0, 2147483647)). + -- validate_no_action(1, 0) + end) + + it('respects tree-sitter injections', function() + setup_treesitter() + local lines = { + '"set background=dark', + '"set termguicolors', + 'lua << EOF', + '-- print(1)', + '-- print(2)', + 'EOF', + } + set_lines(lines) + + set_cursor(1, 0) + feed('dgc') + eq(get_lines(), { 'lua << EOF', '-- print(1)', '-- print(2)', 'EOF' }) + + -- Should work with dot-repeat + set_cursor(2, 0) + feed('.') + eq(get_lines(), { 'lua << EOF', 'EOF' }) + end) + end) +end) |