neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

comment_spec.lua (23899B)


      1 local t = require('test.testutil')
      2 local n = require('test.functional.testnvim')()
      3 
      4 local api = n.api
      5 local clear = n.clear
      6 local eq = t.eq
      7 local exec_capture = n.exec_capture
      8 local exec_lua = n.exec_lua
      9 local feed = n.feed
     10 
     11 -- Reference text
     12 -- aa
     13 --  aa
     14 --   aa
     15 --
     16 --   aa
     17 --  aa
     18 -- aa
     19 local example_lines = { 'aa', ' aa', '  aa', '', '  aa', ' aa', 'aa' }
     20 
     21 local set_commentstring = function(commentstring)
     22  api.nvim_set_option_value('commentstring', commentstring, { buf = 0 })
     23 end
     24 
     25 local get_lines = function(from, to)
     26  from, to = from or 0, to or -1
     27  return api.nvim_buf_get_lines(0, from, to, false)
     28 end
     29 
     30 local set_lines = function(lines, from, to)
     31  from, to = from or 0, to or -1
     32  api.nvim_buf_set_lines(0, from, to, false, lines)
     33 end
     34 
     35 local set_cursor = function(row, col)
     36  api.nvim_win_set_cursor(0, { row, col })
     37 end
     38 
     39 local get_cursor = function()
     40  return api.nvim_win_get_cursor(0)
     41 end
     42 
     43 local setup_treesitter = function()
     44  -- NOTE: This leverages bundled Vimscript and Lua tree-sitter parsers
     45  api.nvim_set_option_value('filetype', 'vim', { buf = 0 })
     46  exec_lua('vim.treesitter.start()')
     47 end
     48 
     49 before_each(function()
     50  -- avoid options, but we still need TS parsers
     51  clear({ args_rm = { '--cmd' }, args = { '--clean', '--cmd', n.runtime_set } })
     52 end)
     53 
     54 describe('commenting', function()
     55  before_each(function()
     56    set_lines(example_lines)
     57    set_commentstring('# %s')
     58  end)
     59 
     60  describe('toggle_lines()', function()
     61    local toggle_lines = function(...)
     62      exec_lua('require("vim._comment").toggle_lines(...)', ...)
     63    end
     64 
     65    it('works', function()
     66      toggle_lines(3, 5)
     67      eq(get_lines(2, 5), { '  # aa', '  #', '  # aa' })
     68 
     69      toggle_lines(3, 5)
     70      eq(get_lines(2, 5), { '  aa', '', '  aa' })
     71    end)
     72 
     73    it("works with different 'commentstring' options", function()
     74      local validate = function(lines_before, lines_after, lines_again)
     75        set_lines(lines_before)
     76        toggle_lines(1, #lines_before)
     77        eq(get_lines(), lines_after)
     78        toggle_lines(1, #lines_before)
     79        eq(get_lines(), lines_again or lines_before)
     80      end
     81 
     82      -- Single whitespace inside comment parts (main case)
     83      set_commentstring('# %s #')
     84      -- - General case
     85      validate(
     86        { 'aa', '  aa', 'aa  ', '  aa  ' },
     87        { '# aa #', '#   aa #', '# aa   #', '#   aa   #' }
     88      )
     89      -- - Tabs
     90      validate(
     91        { 'aa', '\taa', 'aa\t', '\taa\t' },
     92        { '# aa #', '# \taa #', '# aa\t #', '# \taa\t #' }
     93      )
     94      -- - With indent
     95      validate({ ' aa', '  aa' }, { ' # aa #', ' #  aa #' })
     96      -- - With blank/empty lines
     97      validate(
     98        { '  aa', '', '  ', '\t' },
     99        { '  # aa #', '  ##', '  ##', '  ##' },
    100        { '  aa', '', '', '' }
    101      )
    102 
    103      set_commentstring('# %s')
    104      validate({ 'aa', '  aa', 'aa  ', '  aa  ' }, { '# aa', '#   aa', '# aa  ', '#   aa  ' })
    105      validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { '# aa', '# \taa', '# aa\t', '# \taa\t' })
    106      validate({ ' aa', '  aa' }, { ' # aa', ' #  aa' })
    107      validate(
    108        { '  aa', '', '  ', '\t' },
    109        { '  # aa', '  #', '  #', '  #' },
    110        { '  aa', '', '', '' }
    111      )
    112 
    113      set_commentstring('%s #')
    114      validate({ 'aa', '  aa', 'aa  ', '  aa  ' }, { 'aa #', '  aa #', 'aa   #', '  aa   #' })
    115      validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { 'aa #', '\taa #', 'aa\t #', '\taa\t #' })
    116      validate({ ' aa', '  aa' }, { ' aa #', '  aa #' })
    117      validate(
    118        { '  aa', '', '  ', '\t' },
    119        { '  aa #', '  #', '  #', '  #' },
    120        { '  aa', '', '', '' }
    121      )
    122 
    123      -- No whitespace in parts
    124      set_commentstring('#%s#')
    125      validate({ 'aa', '  aa', 'aa  ', '  aa  ' }, { '#aa#', '#  aa#', '#aa  #', '#  aa  #' })
    126      validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { '#aa#', '#\taa#', '#aa\t#', '#\taa\t#' })
    127      validate({ ' aa', '  aa' }, { ' #aa#', ' # aa#' })
    128      validate(
    129        { '  aa', '', '  ', '\t' },
    130        { '  #aa#', '  ##', '  ##', '  ##' },
    131        { '  aa', '', '', '' }
    132      )
    133 
    134      set_commentstring('#%s')
    135      validate({ 'aa', '  aa', 'aa  ', '  aa  ' }, { '#aa', '#  aa', '#aa  ', '#  aa  ' })
    136      validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { '#aa', '#\taa', '#aa\t', '#\taa\t' })
    137      validate({ ' aa', '  aa' }, { ' #aa', ' # aa' })
    138      validate({ '  aa', '', '  ', '\t' }, { '  #aa', '  #', '  #', '  #' }, { '  aa', '', '', '' })
    139 
    140      set_commentstring('%s#')
    141      validate({ 'aa', '  aa', 'aa  ', '  aa  ' }, { 'aa#', '  aa#', 'aa  #', '  aa  #' })
    142      validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { 'aa#', '\taa#', 'aa\t#', '\taa\t#' })
    143      validate({ ' aa', '  aa' }, { ' aa#', '  aa#' })
    144      validate({ '  aa', '', '  ', '\t' }, { '  aa#', '  #', '  #', '  #' }, { '  aa', '', '', '' })
    145 
    146      -- Extra whitespace inside comment parts
    147      set_commentstring('#  %s  #')
    148      validate(
    149        { 'aa', '  aa', 'aa  ', '  aa  ' },
    150        { '#  aa  #', '#    aa  #', '#  aa    #', '#    aa    #' }
    151      )
    152      validate(
    153        { 'aa', '\taa', 'aa\t', '\taa\t' },
    154        { '#  aa  #', '#  \taa  #', '#  aa\t  #', '#  \taa\t  #' }
    155      )
    156      validate({ ' aa', '  aa' }, { ' #  aa  #', ' #   aa  #' })
    157      validate(
    158        { '  aa', '', '  ', '\t' },
    159        { '  #  aa  #', '  ##', '  ##', '  ##' },
    160        { '  aa', '', '', '' }
    161      )
    162 
    163      set_commentstring('#  %s')
    164      validate({ 'aa', '  aa', 'aa  ', '  aa  ' }, { '#  aa', '#    aa', '#  aa  ', '#    aa  ' })
    165      validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { '#  aa', '#  \taa', '#  aa\t', '#  \taa\t' })
    166      validate({ ' aa', '  aa' }, { ' #  aa', ' #   aa' })
    167      validate(
    168        { '  aa', '', '  ', '\t' },
    169        { '  #  aa', '  #', '  #', '  #' },
    170        { '  aa', '', '', '' }
    171      )
    172 
    173      set_commentstring('%s  #')
    174      validate({ 'aa', '  aa', 'aa  ', '  aa  ' }, { 'aa  #', '  aa  #', 'aa    #', '  aa    #' })
    175      validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { 'aa  #', '\taa  #', 'aa\t  #', '\taa\t  #' })
    176      validate({ ' aa', '  aa' }, { ' aa  #', '  aa  #' })
    177      validate(
    178        { '  aa', '', '  ', '\t' },
    179        { '  aa  #', '  #', '  #', '  #' },
    180        { '  aa', '', '', '' }
    181      )
    182 
    183      -- Whitespace outside of comment parts
    184      set_commentstring(' # %s # ')
    185      validate(
    186        { 'aa', '  aa', 'aa  ', '  aa  ' },
    187        { ' # aa # ', ' #   aa # ', ' # aa   # ', ' #   aa   # ' }
    188      )
    189      validate(
    190        { 'aa', '\taa', 'aa\t', '\taa\t' },
    191        { ' # aa # ', ' # \taa # ', ' # aa\t # ', ' # \taa\t # ' }
    192      )
    193      validate({ ' aa', '  aa' }, { '  # aa # ', '  #  aa # ' })
    194      validate(
    195        { '  aa', '', '  ', '\t' },
    196        { '   # aa # ', '  ##', '  ##', '  ##' },
    197        { '  aa', '', '', '' }
    198      )
    199 
    200      set_commentstring(' # %s ')
    201      validate(
    202        { 'aa', '  aa', 'aa  ', '  aa  ' },
    203        { ' # aa ', ' #   aa ', ' # aa   ', ' #   aa   ' }
    204      )
    205      validate(
    206        { 'aa', '\taa', 'aa\t', '\taa\t' },
    207        { ' # aa ', ' # \taa ', ' # aa\t ', ' # \taa\t ' }
    208      )
    209      validate({ ' aa', '  aa' }, { '  # aa ', '  #  aa ' })
    210      validate(
    211        { '  aa', '', '  ', '\t' },
    212        { '   # aa ', '  #', '  #', '  #' },
    213        { '  aa', '', '', '' }
    214      )
    215 
    216      set_commentstring(' %s # ')
    217      validate(
    218        { 'aa', '  aa', 'aa  ', '  aa  ' },
    219        { ' aa # ', '   aa # ', ' aa   # ', '   aa   # ' }
    220      )
    221      validate(
    222        { 'aa', '\taa', 'aa\t', '\taa\t' },
    223        { ' aa # ', ' \taa # ', ' aa\t # ', ' \taa\t # ' }
    224      )
    225      validate({ ' aa', '  aa' }, { '  aa # ', '   aa # ' })
    226      validate(
    227        { '  aa', '', '  ', '\t' },
    228        { '   aa # ', '  #', '  #', '  #' },
    229        { '  aa', '', '', '' }
    230      )
    231 
    232      -- LaTeX
    233      set_commentstring('% %s')
    234      validate({ 'aa', '  aa', 'aa  ', '  aa  ' }, { '% aa', '%   aa', '% aa  ', '%   aa  ' })
    235      validate({ 'aa', '\taa', 'aa\t', '\taa\t' }, { '% aa', '% \taa', '% aa\t', '% \taa\t' })
    236      validate({ ' aa', '  aa' }, { ' % aa', ' %  aa' })
    237      validate(
    238        { '  aa', '', '  ', '\t' },
    239        { '  % aa', '  %', '  %', '  %' },
    240        { '  aa', '', '', '' }
    241      )
    242    end)
    243 
    244    it('respects tree-sitter injections', function()
    245      setup_treesitter()
    246 
    247      local lines = {
    248        'set background=dark',
    249        'lua << EOF',
    250        'print(1)',
    251        'vim.api.nvim_exec2([[',
    252        '    set background=light',
    253        ']])',
    254        'EOF',
    255      }
    256 
    257      -- Single line comments
    258      local validate = function(line, ref_output)
    259        set_lines(lines)
    260        toggle_lines(line, line)
    261        eq(get_lines(line - 1, line)[1], ref_output)
    262      end
    263 
    264      validate(1, '"set background=dark')
    265      validate(2, '"lua << EOF')
    266      validate(3, '-- print(1)')
    267      validate(4, '-- vim.api.nvim_exec2([[')
    268      validate(5, '    "set background=light')
    269      validate(6, '-- ]])')
    270      validate(7, '"EOF')
    271 
    272      -- Multiline comments should be computed based on first line 'commentstring'
    273      set_lines(lines)
    274      toggle_lines(1, 3)
    275      local out_lines = get_lines()
    276      eq(out_lines[1], '"set background=dark')
    277      eq(out_lines[2], '"lua << EOF')
    278      eq(out_lines[3], '"print(1)')
    279    end)
    280 
    281    it('correctly computes indent', function()
    282      toggle_lines(2, 4)
    283      eq(get_lines(1, 4), { ' # aa', ' #  aa', ' #' })
    284    end)
    285 
    286    it('correctly detects comment/uncomment', function()
    287      local validate = function(from, to, ref_lines)
    288        set_lines({ '', 'aa', '# aa', '# aa', 'aa', '' })
    289        toggle_lines(from, to)
    290        eq(get_lines(), ref_lines)
    291      end
    292 
    293      -- It should uncomment only if all non-blank lines are comments
    294      validate(3, 4, { '', 'aa', 'aa', 'aa', 'aa', '' })
    295      validate(2, 4, { '', '# aa', '# # aa', '# # aa', 'aa', '' })
    296      validate(3, 5, { '', 'aa', '# # aa', '# # aa', '# aa', '' })
    297      validate(1, 6, { '#', '# aa', '# # aa', '# # aa', '# aa', '#' })
    298 
    299      -- Blank lines should be ignored when making a decision
    300      set_lines({ '# aa', '', '  ', '\t', '# aa' })
    301      toggle_lines(1, 5)
    302      eq(get_lines(), { 'aa', '', '  ', '\t', 'aa' })
    303    end)
    304 
    305    it('correctly matches comment parts during checking and uncommenting', function()
    306      local validate = function(from, to, ref_lines)
    307        set_lines({ '/*aa*/', '/* aa */', '/*  aa  */' })
    308        toggle_lines(from, to)
    309        eq(get_lines(), ref_lines)
    310      end
    311 
    312      -- Should first try to match 'commentstring' parts exactly with their
    313      -- whitespace, with fallback on trimmed parts
    314      set_commentstring('/*%s*/')
    315      validate(1, 3, { 'aa', ' aa ', '  aa  ' })
    316      validate(2, 3, { '/*aa*/', ' aa ', '  aa  ' })
    317      validate(3, 3, { '/*aa*/', '/* aa */', '  aa  ' })
    318 
    319      set_commentstring('/* %s */')
    320      validate(1, 3, { 'aa', 'aa', ' aa ' })
    321      validate(2, 3, { '/*aa*/', 'aa', ' aa ' })
    322      validate(3, 3, { '/*aa*/', '/* aa */', ' aa ' })
    323 
    324      set_commentstring('/*  %s  */')
    325      validate(1, 3, { 'aa', ' aa ', 'aa' })
    326      validate(2, 3, { '/*aa*/', ' aa ', 'aa' })
    327      validate(3, 3, { '/*aa*/', '/* aa */', 'aa' })
    328 
    329      set_commentstring(' /*%s*/ ')
    330      validate(1, 3, { 'aa', ' aa ', '  aa  ' })
    331      validate(2, 3, { '/*aa*/', ' aa ', '  aa  ' })
    332      validate(3, 3, { '/*aa*/', '/* aa */', '  aa  ' })
    333    end)
    334 
    335    it('uncomments on inconsistent indent levels', function()
    336      set_lines({ '# aa', ' # aa', '  # aa' })
    337      toggle_lines(1, 3)
    338      eq(get_lines(), { 'aa', ' aa', '  aa' })
    339    end)
    340 
    341    it('respects tabs', function()
    342      api.nvim_set_option_value('expandtab', false, { buf = 0 })
    343      set_lines({ '\t\taa', '\t\taa' })
    344 
    345      toggle_lines(1, 2)
    346      eq(get_lines(), { '\t\t# aa', '\t\t# aa' })
    347 
    348      toggle_lines(1, 2)
    349      eq(get_lines(), { '\t\taa', '\t\taa' })
    350    end)
    351 
    352    it('works with trailing whitespace', function()
    353      -- Without right-hand side
    354      set_commentstring('# %s')
    355      set_lines({ ' aa', ' aa  ', '  ' })
    356      toggle_lines(1, 3)
    357      eq(get_lines(), { ' # aa', ' # aa  ', ' #' })
    358      toggle_lines(1, 3)
    359      eq(get_lines(), { ' aa', ' aa  ', '' })
    360 
    361      -- With right-hand side
    362      set_commentstring('%s #')
    363      set_lines({ ' aa', ' aa  ', '  ' })
    364      toggle_lines(1, 3)
    365      eq(get_lines(), { ' aa #', ' aa   #', ' #' })
    366      toggle_lines(1, 3)
    367      eq(get_lines(), { ' aa', ' aa  ', '' })
    368 
    369      -- Trailing whitespace after right side should be preserved for non-blanks
    370      set_commentstring('%s #')
    371      set_lines({ ' aa #  ', ' aa #\t', ' #  ', ' #\t' })
    372      toggle_lines(1, 4)
    373      eq(get_lines(), { ' aa  ', ' aa\t', '', '' })
    374    end)
    375  end)
    376 
    377  describe('Operator', function()
    378    it('works in Normal mode', function()
    379      set_cursor(2, 2)
    380      feed('gc', 'ap')
    381      eq(get_lines(), { '# aa', '#  aa', '#   aa', '#', '  aa', ' aa', 'aa' })
    382      -- Cursor moves to start line
    383      eq(get_cursor(), { 1, 0 })
    384 
    385      -- Supports `v:count`
    386      set_lines(example_lines)
    387      set_cursor(2, 0)
    388      feed('2gc', 'ap')
    389      eq(get_lines(), { '# aa', '#  aa', '#   aa', '#', '#   aa', '#  aa', '# aa' })
    390    end)
    391 
    392    it('allows dot-repeat in Normal mode', function()
    393      local doubly_commented = { '# # aa', '# #  aa', '# #   aa', '# #', '#   aa', '#  aa', '# aa' }
    394 
    395      set_lines(example_lines)
    396      set_cursor(2, 2)
    397      feed('gc', 'ap')
    398      feed('.')
    399      eq(get_lines(), doubly_commented)
    400 
    401      -- Not immediate dot-repeat
    402      set_lines(example_lines)
    403      set_cursor(2, 2)
    404      feed('gc', 'ap')
    405      set_cursor(7, 0)
    406      feed('.')
    407      eq(get_lines(), doubly_commented)
    408    end)
    409 
    410    it('works in Visual mode', function()
    411      set_cursor(2, 2)
    412      feed('v', 'ap', 'gc')
    413      eq(get_lines(), { '# aa', '#  aa', '#   aa', '#', '  aa', ' aa', 'aa' })
    414 
    415      -- Cursor moves to start line
    416      eq(get_cursor(), { 1, 0 })
    417    end)
    418 
    419    it('allows dot-repeat after initial Visual mode', function()
    420      -- local example_lines = { 'aa', ' aa', '  aa', '', '  aa', ' aa', 'aa' }
    421 
    422      set_lines(example_lines)
    423      set_cursor(2, 2)
    424      feed('vip', 'gc')
    425      eq(get_lines(), { '# aa', '#  aa', '#   aa', '', '  aa', ' aa', 'aa' })
    426      eq(get_cursor(), { 1, 0 })
    427 
    428      -- Dot-repeat after first application in Visual mode should apply to the same
    429      -- relative region
    430      feed('.')
    431      eq(get_lines(), example_lines)
    432 
    433      set_cursor(3, 0)
    434      feed('.')
    435      eq(get_lines(), { 'aa', ' aa', '  # aa', '  #', '  # aa', ' aa', 'aa' })
    436    end)
    437 
    438    it("respects 'commentstring'", function()
    439      set_commentstring('/*%s*/')
    440      set_cursor(2, 2)
    441      feed('gc', 'ap')
    442      eq(get_lines(), { '/*aa*/', '/* aa*/', '/*  aa*/', '/**/', '  aa', ' aa', 'aa' })
    443    end)
    444 
    445    it("works with empty 'commentstring'", function()
    446      set_commentstring('')
    447      set_cursor(2, 2)
    448      feed('gc', 'ap')
    449      eq(get_lines(), example_lines)
    450      eq(exec_capture('1messages'), [[Option 'commentstring' is empty.]])
    451    end)
    452 
    453    it('respects tree-sitter injections', function()
    454      setup_treesitter()
    455 
    456      local lines = {
    457        'set background=dark',
    458        'lua << EOF',
    459        'print(1)',
    460        'vim.api.nvim_exec2([[',
    461        '    set background=light',
    462        ']])',
    463        'EOF',
    464      }
    465 
    466      -- Single line comments
    467      local validate = function(line, ref_output)
    468        set_lines(lines)
    469        set_cursor(line, 0)
    470        feed('gc_')
    471        eq(get_lines(line - 1, line)[1], ref_output)
    472      end
    473 
    474      validate(1, '"set background=dark')
    475      validate(2, '"lua << EOF')
    476      validate(3, '-- print(1)')
    477      validate(4, '-- vim.api.nvim_exec2([[')
    478      validate(5, '    "set background=light')
    479      validate(6, '-- ]])')
    480      validate(7, '"EOF')
    481 
    482      -- Has proper dot-repeat which recomputes 'commentstring'
    483      set_lines(lines)
    484 
    485      set_cursor(1, 0)
    486      feed('gc_')
    487      eq(get_lines()[1], '"set background=dark')
    488 
    489      set_cursor(3, 0)
    490      feed('.')
    491      eq(get_lines()[3], '-- print(1)')
    492 
    493      -- Multiline comments should be computed based on cursor position
    494      -- which in case of Visual selection means its left part
    495      set_lines(lines)
    496      set_cursor(1, 0)
    497      feed('v2j', 'gc')
    498      local out_lines = get_lines()
    499      eq(out_lines[1], '"set background=dark')
    500      eq(out_lines[2], '"lua << EOF')
    501      eq(out_lines[3], '"print(1)')
    502    end)
    503 
    504    it("recomputes local 'commentstring' based on cursor position", function()
    505      setup_treesitter()
    506      local lines = {
    507        '  print(1)',
    508        'lua << EOF',
    509        '  print(1)',
    510        'EOF',
    511      }
    512      set_lines(lines)
    513 
    514      set_cursor(1, 1)
    515      feed('gc_')
    516      eq(get_lines()[1], '  "print(1)')
    517 
    518      set_lines(lines)
    519      set_cursor(3, 2)
    520      feed('.')
    521      eq(get_lines()[3], '  -- print(1)')
    522    end)
    523 
    524    it('preserves marks', function()
    525      set_cursor(2, 0)
    526      -- Set '`<' and '`>' marks
    527      feed('VV')
    528      feed('gc', 'ip')
    529      eq(api.nvim_buf_get_mark(0, '<'), { 2, 0 })
    530      eq(api.nvim_buf_get_mark(0, '>'), { 2, 2147483647 })
    531    end)
    532  end)
    533 
    534  describe('Current line', function()
    535    it('works', function()
    536      set_lines(example_lines)
    537      set_cursor(1, 1)
    538      feed('gcc')
    539      eq(get_lines(0, 2), { '# aa', ' aa' })
    540 
    541      -- Does not comment empty line
    542      set_lines(example_lines)
    543      set_cursor(4, 0)
    544      feed('gcc')
    545      eq(get_lines(2, 5), { '  aa', '', '  aa' })
    546 
    547      -- Supports `v:count`
    548      set_lines(example_lines)
    549      set_cursor(2, 0)
    550      feed('2gcc')
    551      eq(get_lines(0, 3), { 'aa', ' # aa', ' #  aa' })
    552    end)
    553 
    554    it('allows dot-repeat', function()
    555      set_lines(example_lines)
    556      set_cursor(1, 1)
    557      feed('gcc')
    558      feed('.')
    559      eq(get_lines(), example_lines)
    560 
    561      -- Not immediate dot-repeat
    562      set_lines(example_lines)
    563      set_cursor(1, 1)
    564      feed('gcc')
    565      set_cursor(7, 0)
    566      feed('.')
    567      eq(get_lines(6, 7), { '# aa' })
    568    end)
    569 
    570    it('respects tree-sitter injections', function()
    571      setup_treesitter()
    572 
    573      local lines = {
    574        'set background=dark',
    575        'lua << EOF',
    576        'print(1)',
    577        'EOF',
    578      }
    579      set_lines(lines)
    580 
    581      set_cursor(1, 0)
    582      feed('gcc')
    583      eq(get_lines(), { '"set background=dark', 'lua << EOF', 'print(1)', 'EOF' })
    584 
    585      -- Should work with dot-repeat
    586      set_cursor(3, 0)
    587      feed('.')
    588      eq(get_lines(), { '"set background=dark', 'lua << EOF', '-- print(1)', 'EOF' })
    589    end)
    590 
    591    it('respects tree-sitter commentstring metadata', function()
    592      exec_lua [=[
    593        vim.treesitter.query.set('vim', 'highlights', [[
    594          ((list) @_list (#set! @_list bo.commentstring "!! %s"))
    595        ]])
    596      ]=]
    597      setup_treesitter()
    598 
    599      local lines = {
    600        'set background=dark',
    601        'let mylist = [',
    602        [[  \"a",]],
    603        [[  \"b",]],
    604        [[  \"c",]],
    605        '  \\]',
    606      }
    607      set_lines(lines)
    608 
    609      set_cursor(1, 0)
    610      feed('gcc')
    611      eq(
    612        { '"set background=dark', 'let mylist = [', [[  \"a",]], [[  \"b",]], [[  \"c",]], '  \\]' },
    613        get_lines()
    614      )
    615 
    616      -- Should work with dot-repeat
    617      set_cursor(4, 0)
    618      feed('.')
    619      eq({
    620        '"set background=dark',
    621        'let mylist = [',
    622        [[  \"a",]],
    623        [[  !! \"b",]],
    624        [[  \"c",]],
    625        '  \\]',
    626      }, get_lines())
    627    end)
    628 
    629    it('only applies the innermost tree-sitter commentstring metadata', function()
    630      exec_lua [=[
    631        vim.treesitter.query.set('vim', 'highlights', [[
    632          ((list) @_list (#gsub! @_list "(.*)" "%1") (#set! bo.commentstring "!! %s"))
    633          ((script_file) @_src (#set! @_src bo.commentstring "## %s"))
    634        ]])
    635      ]=]
    636      setup_treesitter()
    637 
    638      local lines = {
    639        'set background=dark',
    640        'let mylist = [',
    641        [[  \"a",]],
    642        [[  \"b",]],
    643        [[  \"c",]],
    644        '  \\]',
    645      }
    646      set_lines(lines)
    647 
    648      set_cursor(1, 0)
    649      feed('gcc')
    650      eq({
    651        '## set background=dark',
    652        'let mylist = [',
    653        [[  \"a",]],
    654        [[  \"b",]],
    655        [[  \"c",]],
    656        '  \\]',
    657      }, get_lines())
    658 
    659      -- Should work with dot-repeat
    660      set_cursor(4, 0)
    661      feed('.')
    662      eq({
    663        '## set background=dark',
    664        'let mylist = [',
    665        [[  \"a",]],
    666        [[  !! \"b",]],
    667        [[  \"c",]],
    668        '  \\]',
    669      }, get_lines())
    670    end)
    671 
    672    it('respects injected tree-sitter commentstring metadata', function()
    673      exec_lua [=[
    674        vim.treesitter.query.set('lua', 'highlights', [[
    675          ((string) @string (#set! @string bo.commentstring "; %s"))
    676        ]])
    677      ]=]
    678      setup_treesitter()
    679 
    680      local lines = {
    681        'set background=dark',
    682        'lua << EOF',
    683        'print[[',
    684        'Inside string',
    685        ']]',
    686        'EOF',
    687      }
    688      set_lines(lines)
    689 
    690      set_cursor(1, 0)
    691      feed('gcc')
    692      eq({
    693        '"set background=dark',
    694        'lua << EOF',
    695        'print[[',
    696        'Inside string',
    697        ']]',
    698        'EOF',
    699      }, get_lines())
    700 
    701      -- Should work with dot-repeat
    702      set_cursor(4, 0)
    703      feed('.')
    704      eq({
    705        '"set background=dark',
    706        'lua << EOF',
    707        'print[[',
    708        '; Inside string',
    709        ']]',
    710        'EOF',
    711      }, get_lines())
    712 
    713      set_cursor(3, 0)
    714      feed('.')
    715      eq({
    716        '"set background=dark',
    717        'lua << EOF',
    718        '-- print[[',
    719        '; Inside string',
    720        ']]',
    721        'EOF',
    722      }, get_lines())
    723    end)
    724 
    725    it('works across combined injections #30799', function()
    726      exec_lua [=[
    727        vim.treesitter.query.set('lua', 'injections', [[
    728          ((function_call
    729            name: (_) @_vimcmd_identifier
    730            arguments: (arguments
    731              (string
    732                content: _ @injection.content)))
    733            (#eq? @_vimcmd_identifier "vim.cmd")
    734            (#set! injection.language "vim")
    735            (#set! injection.combined))
    736        ]])
    737      ]=]
    738 
    739      api.nvim_set_option_value('filetype', 'lua', { buf = 0 })
    740      exec_lua('vim.treesitter.start()')
    741 
    742      local lines = {
    743        'vim.cmd([[" some text]])',
    744        'local a = 123',
    745        'vim.cmd([[" some more text]])',
    746      }
    747      set_lines(lines)
    748 
    749      set_cursor(2, 0)
    750      feed('gcc')
    751      eq({
    752        'vim.cmd([[" some text]])',
    753        '-- local a = 123',
    754        'vim.cmd([[" some more text]])',
    755      }, get_lines())
    756    end)
    757  end)
    758 
    759  describe('Textobject', function()
    760    it('works', function()
    761      set_lines({ 'aa', '# aa', '# aa', 'aa' })
    762      set_cursor(2, 0)
    763      feed('d', 'gc')
    764      eq(get_lines(), { 'aa', 'aa' })
    765    end)
    766 
    767    it('allows dot-repeat', function()
    768      set_lines({ 'aa', '# aa', '# aa', 'aa', '# aa' })
    769      set_cursor(2, 0)
    770      feed('d', 'gc')
    771      set_cursor(3, 0)
    772      feed('.')
    773      eq(get_lines(), { 'aa', 'aa' })
    774    end)
    775 
    776    it('does nothing when not inside textobject', function()
    777      -- Builtin operators
    778      feed('d', 'gc')
    779      eq(get_lines(), example_lines)
    780 
    781      -- Comment operator
    782      local validate_no_action = function(line, col)
    783        set_lines(example_lines)
    784        set_cursor(line, col)
    785        feed('gc', 'gc')
    786        eq(get_lines(), example_lines)
    787      end
    788 
    789      validate_no_action(1, 1)
    790      validate_no_action(2, 2)
    791 
    792      -- Doesn't work (but should) because both `[` and `]` are set to (1, 0)
    793      -- (instead of more reasonable (1, -1) or (0, 2147483647)).
    794      -- validate_no_action(1, 0)
    795    end)
    796 
    797    it('respects tree-sitter injections', function()
    798      setup_treesitter()
    799      local lines = {
    800        '"set background=dark',
    801        '"set termguicolors',
    802        'lua << EOF',
    803        '-- print(1)',
    804        '-- print(2)',
    805        'EOF',
    806      }
    807      set_lines(lines)
    808 
    809      set_cursor(1, 0)
    810      feed('dgc')
    811      eq(get_lines(), { 'lua << EOF', '-- print(1)', '-- print(2)', 'EOF' })
    812 
    813      -- Should work with dot-repeat
    814      set_cursor(2, 0)
    815      feed('.')
    816      eq(get_lines(), { 'lua << EOF', 'EOF' })
    817    end)
    818  end)
    819 end)