mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	feat(comment): add built-in commenting
Design
- Enable commenting support only through `gc` mappings for simplicity.
  No ability to configure, no Lua module, no user commands. Yet.
- Overall implementation is a simplified version of 'mini.comment'
  module of 'echasnovski/mini.nvim' adapted to be a better suit for
  core. It basically means reducing code paths which use only specific
  fixed set of plugin config.
  All used options are default except `pad_comment_parts = false`. This
  means that 'commentstring' option is used as is without forcing single
  space inner padding.
As 'tpope/vim-commentary' was considered for inclusion earlier, here is
a quick summary of how this commit differs from it:
- **User-facing features**. Both implement similar user-facing mappings.
  This commit does not include `gcu` which is essentially a `gcgc`.
  There are no commands, events, or configuration in this commit.
- **Size**. Both have reasonably comparable number of lines of code,
  while this commit has more comments in tricky areas.
- **Maintainability**. This commit has (purely subjectively) better
  readability, tests, and Lua types.
- **Configurability**. This commit has no user configuration, while
  'vim-commentary' has some (partially as a counter-measure to possibly
  modifying 'commentstring' option).
- **Extra features**:
    - This commit supports tree-sitter by computing `'commentstring'`
      option under cursor, which can matter in presence of tree-sitter
      injected languages.
    - This commit comments blank lines while 'tpope/vim-commentary' does
      not. At the same time, blank lines are not taken into account when
      deciding the toggle action.
    - This commit has much better speed on larger chunks of lines (like
      above 1000). This is thanks to using `nvim_buf_set_lines()` to set
      all new lines at once, and not with `vim.fn.setline()`.
			
			
This commit is contained in:
		 Evgeni Chasnovski
					Evgeni Chasnovski
				
			
				
					committed by
					
						 Christian Clason
						Christian Clason
					
				
			
			
				
	
			
			
			 Christian Clason
						Christian Clason
					
				
			
						parent
						
							2b9d8dc87e
						
					
				
				
					commit
					73de98256c
				
			| @@ -346,6 +346,8 @@ The following new APIs and features were added. | ||||
|  | ||||
| • |extmarks| option `scoped`: only show the extmarks in its namespace's scope. | ||||
|  | ||||
| • Added built-in |commenting| support. | ||||
|  | ||||
| ============================================================================== | ||||
| CHANGED FEATURES                                                 *news-changed* | ||||
|  | ||||
|   | ||||
| @@ -557,5 +557,43 @@ LessInitFunc in your vimrc, for example: > | ||||
| 	  set nocursorcolumn nocursorline | ||||
| 	endfunc | ||||
| < | ||||
| ============================================================================== | ||||
| 3. Commenting							*commenting* | ||||
|  | ||||
| Nvim supports commenting and uncommenting of lines based on 'commentstring'. | ||||
|  | ||||
| Acting on a single line behaves as follows: | ||||
| - If the line matches 'commentstring', the comment markers are removed (e.g. | ||||
|   `/*foo*/` is transformed to `foo`). | ||||
| - Otherwise the comment markers are added to the current line (e.g. `foo` is | ||||
|   transformed to `/*foo*/`). Blank lines are ignored. | ||||
|  | ||||
| Acting on multiple lines behaves as follows: | ||||
| - If each affected non-blank line matches 'commentstring', then all comment | ||||
|   markers are removed. | ||||
| - Otherwise all affected lines are converted to comments; blank lines are | ||||
|   transformed to empty comments (e.g. `/**/`). Comment markers are aligned to | ||||
|   the least indented line. | ||||
|  | ||||
| If the filetype of the buffer is associated with a language for which a | ||||
| |treesitter| parser is installed, then |vim.filetype.get_option()| is called | ||||
| to look up the value of 'commentstring' corresponding to the cursor position. | ||||
| (This can be different from the buffer's 'commentstring' in case of | ||||
| |treesitter-language-injections|.) | ||||
|  | ||||
| 							*gc-default* | ||||
| gc{motion}		Comment or uncomment lines covered by {motion}. | ||||
|  | ||||
| 							*gcc-default* | ||||
| gcc			Comment or uncomment [count] lines starting at cursor. | ||||
|  | ||||
| 							*v_gc-default* | ||||
| {Visual}gc		Comment or uncomment the selected line(s). | ||||
|  | ||||
| 							*o_gc-default* | ||||
| gc			Text object for the largest contiguous block of | ||||
| 			non-blank commented lines around the cursor (e.g. | ||||
| 			`gcgc` uncomments a comment block; `dgc` deletes it). | ||||
| 			Works only in Operator-pending mode. | ||||
|  | ||||
|  vim:noet:tw=78:ts=8:ft=help:norl: | ||||
|   | ||||
| @@ -134,6 +134,8 @@ of these in your config by simply removing the mapping, e.g. ":unmap Y". | ||||
| - @ |v_@-default| | ||||
| - # |v_#-default| | ||||
| - * |v_star-default| | ||||
| - gc |gc-default| |v_gc-default| |o_gc-default| | ||||
| - gcc |gcc-default| | ||||
| - Nvim LSP client defaults |lsp-defaults| | ||||
|   - K |K-lsp-default| | ||||
|  | ||||
|   | ||||
							
								
								
									
										266
									
								
								runtime/lua/vim/_comment.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								runtime/lua/vim/_comment.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,266 @@ | ||||
| ---@nodoc | ||||
| ---@class vim._comment.Parts | ||||
| ---@field left string Left part of comment | ||||
| ---@field right string Right part of comment | ||||
|  | ||||
| --- Get 'commentstring' at cursor | ||||
| ---@param ref_position integer[] | ||||
| ---@return string | ||||
| local function get_commentstring(ref_position) | ||||
|   local buf_cs = vim.bo.commentstring | ||||
|  | ||||
|   local has_ts_parser, ts_parser = pcall(vim.treesitter.get_parser) | ||||
|   if not has_ts_parser then | ||||
|     return buf_cs | ||||
|   end | ||||
|  | ||||
|   -- Try to get 'commentstring' associated with local tree-sitter language. | ||||
|   -- This is useful for injected languages (like markdown with code blocks). | ||||
|   local row, col = ref_position[1] - 1, ref_position[2] | ||||
|   local ref_range = { row, col, row, col + 1 } | ||||
|  | ||||
|   -- - Get 'commentstring' from the deepest LanguageTree which both contains | ||||
|   --   reference range and has valid 'commentstring' (meaning it has at least | ||||
|   --   one associated 'filetype' with valid 'commentstring'). | ||||
|   --   In simple cases using `parser:language_for_range()` would be enough, but | ||||
|   --   it fails for languages without valid 'commentstring' (like 'comment'). | ||||
|   local ts_cs, res_level = nil, 0 | ||||
|  | ||||
|   ---@param lang_tree vim.treesitter.LanguageTree | ||||
|   local function traverse(lang_tree, level) | ||||
|     if not lang_tree:contains(ref_range) then | ||||
|       return | ||||
|     end | ||||
|  | ||||
|     local lang = lang_tree:lang() | ||||
|     local filetypes = vim.treesitter.language.get_filetypes(lang) | ||||
|     for _, ft in ipairs(filetypes) do | ||||
|       local cur_cs = vim.filetype.get_option(ft, 'commentstring') | ||||
|       if cur_cs ~= '' and level > res_level then | ||||
|         ts_cs = cur_cs | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     for _, child_lang_tree in pairs(lang_tree:children()) do | ||||
|       traverse(child_lang_tree, level + 1) | ||||
|     end | ||||
|   end | ||||
|   traverse(ts_parser, 1) | ||||
|  | ||||
|   return ts_cs or buf_cs | ||||
| end | ||||
|  | ||||
| --- Compute comment parts from 'commentstring' | ||||
| ---@param ref_position integer[] | ||||
| ---@return vim._comment.Parts | ||||
| local function get_comment_parts(ref_position) | ||||
|   local cs = get_commentstring(ref_position) | ||||
|  | ||||
|   if cs == nil or cs == '' then | ||||
|     vim.api.nvim_echo({ { "Option 'commentstring' is empty.", 'WarningMsg' } }, true, {}) | ||||
|     return { left = '', right = '' } | ||||
|   end | ||||
|  | ||||
|   if not (type(cs) == 'string' and cs:find('%%s') ~= nil) then | ||||
|     error(vim.inspect(cs) .. " is not a valid 'commentstring'.") | ||||
|   end | ||||
|  | ||||
|   -- Structure of 'commentstring': <left part> <%s> <right part> | ||||
|   local left, right = cs:match('^(.-)%%s(.-)$') | ||||
|   return { left = left, right = right } | ||||
| end | ||||
|  | ||||
| --- Make a function that checks if a line is commented | ||||
| ---@param parts vim._comment.Parts | ||||
| ---@return fun(line: string): boolean | ||||
| local function make_comment_check(parts) | ||||
|   local l_esc, r_esc = vim.pesc(parts.left), vim.pesc(parts.right) | ||||
|  | ||||
|   -- Commented line has the following structure: | ||||
|   -- <possible whitespace> <left> <anything> <right> <possible whitespace> | ||||
|   local nonblank_regex = '^%s-' .. l_esc .. '.*' .. r_esc .. '%s-$' | ||||
|  | ||||
|   -- Commented blank line can have any amoung of whitespace around parts | ||||
|   local blank_regex = '^%s-' .. vim.trim(l_esc) .. '%s*' .. vim.trim(r_esc) .. '%s-$' | ||||
|  | ||||
|   return function(line) | ||||
|     return line:find(nonblank_regex) ~= nil or line:find(blank_regex) ~= nil | ||||
|   end | ||||
| end | ||||
|  | ||||
| --- Compute comment-related information about lines | ||||
| ---@param lines string[] | ||||
| ---@param parts vim._comment.Parts | ||||
| ---@return string indent | ||||
| ---@return boolean is_commented | ||||
| local function get_lines_info(lines, parts) | ||||
|   local comment_check = make_comment_check(parts) | ||||
|  | ||||
|   local is_commented = true | ||||
|   local indent_width = math.huge | ||||
|   ---@type string | ||||
|   local indent | ||||
|  | ||||
|   for _, l in ipairs(lines) do | ||||
|     -- Update lines indent: minimum of all indents except blank lines | ||||
|     local _, indent_width_cur, indent_cur = l:find('^(%s*)') | ||||
|  | ||||
|     -- Ignore blank lines completely when making a decision | ||||
|     if indent_width_cur < l:len() then | ||||
|       -- NOTE: Copying actual indent instead of recreating it with `indent_width` | ||||
|       -- allows to handle both tabs and spaces | ||||
|       if indent_width_cur < indent_width then | ||||
|         ---@diagnostic disable-next-line:cast-local-type | ||||
|         indent_width, indent = indent_width_cur, indent_cur | ||||
|       end | ||||
|  | ||||
|       -- Update comment info: commented if every non-blank line is commented | ||||
|       if is_commented then | ||||
|         is_commented = comment_check(l) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   -- `indent` can still be `nil` in case all `lines` are empty | ||||
|   return indent or '', is_commented | ||||
| end | ||||
|  | ||||
| --- Compute whether a string is blank | ||||
| ---@param x string | ||||
| ---@return boolean is_blank | ||||
| local function is_blank(x) | ||||
|   return x:find('^%s*$') ~= nil | ||||
| end | ||||
|  | ||||
| --- Make a function which comments a line | ||||
| ---@param parts vim._comment.Parts | ||||
| ---@param indent string | ||||
| ---@return fun(line: string): string | ||||
| local function make_comment_function(parts, indent) | ||||
|   local prefix, nonindent_start, suffix = indent .. parts.left, indent:len() + 1, parts.right | ||||
|   local blank_comment = indent .. vim.trim(parts.left) .. vim.trim(parts.right) | ||||
|  | ||||
|   return function(line) | ||||
|     if is_blank(line) then | ||||
|       return blank_comment | ||||
|     end | ||||
|     return prefix .. line:sub(nonindent_start) .. suffix | ||||
|   end | ||||
| end | ||||
|  | ||||
| --- Make a function which uncomments a line | ||||
| ---@param parts vim._comment.Parts | ||||
| ---@return fun(line: string): string | ||||
| local function make_uncomment_function(parts) | ||||
|   local l_esc, r_esc = vim.pesc(parts.left), vim.pesc(parts.right) | ||||
|   local nonblank_regex = '^(%s*)' .. l_esc .. '(.*)' .. r_esc .. '(%s-)$' | ||||
|   local blank_regex = '^(%s*)' .. vim.trim(l_esc) .. '(%s*)' .. vim.trim(r_esc) .. '(%s-)$' | ||||
|  | ||||
|   return function(line) | ||||
|     -- Try both non-blank and blank regexes | ||||
|     local indent, new_line, trail = line:match(nonblank_regex) | ||||
|     if new_line == nil then | ||||
|       indent, new_line, trail = line:match(blank_regex) | ||||
|     end | ||||
|  | ||||
|     -- Return original if line is not commented | ||||
|     if new_line == nil then | ||||
|       return line | ||||
|     end | ||||
|  | ||||
|     -- Prevent trailing whitespace | ||||
|     if is_blank(new_line) then | ||||
|       indent, trail = '', '' | ||||
|     end | ||||
|  | ||||
|     return indent .. new_line .. trail | ||||
|   end | ||||
| end | ||||
|  | ||||
| --- Comment/uncomment buffer range | ||||
| ---@param line_start integer | ||||
| ---@param line_end integer | ||||
| ---@param ref_position? integer[] | ||||
| local function toggle_lines(line_start, line_end, ref_position) | ||||
|   ref_position = ref_position or { line_start, 0 } | ||||
|   local parts = get_comment_parts(ref_position) | ||||
|   local lines = vim.api.nvim_buf_get_lines(0, line_start - 1, line_end, false) | ||||
|   local indent, is_comment = get_lines_info(lines, parts) | ||||
|  | ||||
|   local f = is_comment and make_uncomment_function(parts) or make_comment_function(parts, indent) | ||||
|  | ||||
|   -- Direct `nvim_buf_set_lines()` essentially removes both regular and | ||||
|   -- extended marks  (squashes to empty range at either side of the region) | ||||
|   -- inside region. Use 'lockmarks' to preserve regular marks. | ||||
|   -- Preserving extmarks is not a universally good thing to do: | ||||
|   -- - Good for non-highlighting in text area extmarks (like showing signs). | ||||
|   -- - Debatable for highlighting in text area (like LSP semantic tokens). | ||||
|   --   Mostly because it causes flicker as highlighting is preserved during | ||||
|   --   comment toggling. | ||||
|   package.loaded['vim._comment']._lines = vim.tbl_map(f, lines) | ||||
|   local lua_cmd = string.format( | ||||
|     'vim.api.nvim_buf_set_lines(0, %d, %d, false, package.loaded["vim._comment"]._lines)', | ||||
|     line_start - 1, | ||||
|     line_end | ||||
|   ) | ||||
|   vim.cmd.lua({ lua_cmd, mods = { lockmarks = true } }) | ||||
|   package.loaded['vim._comment']._lines = nil | ||||
| end | ||||
|  | ||||
| --- Operator which toggles user-supplied range of lines | ||||
| ---@param mode string? | ||||
| ---|"'line'" | ||||
| ---|"'char'" | ||||
| ---|"'block'" | ||||
| local function operator(mode) | ||||
|   -- Used without arguments as part of expression mapping. Otherwise it is | ||||
|   -- called as 'operatorfunc'. | ||||
|   if mode == nil then | ||||
|     vim.o.operatorfunc = "v:lua.require'vim._comment'.operator" | ||||
|     return 'g@' | ||||
|   end | ||||
|  | ||||
|   -- Compute target range | ||||
|   local mark_from, mark_to = "'[", "']" | ||||
|   local lnum_from, col_from = vim.fn.line(mark_from), vim.fn.col(mark_from) | ||||
|   local lnum_to, col_to = vim.fn.line(mark_to), vim.fn.col(mark_to) | ||||
|  | ||||
|   -- Do nothing if "from" mark is after "to" (like in empty textobject) | ||||
|   if (lnum_from > lnum_to) or (lnum_from == lnum_to and col_from > col_to) then | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   -- NOTE: use cursor position as reference for possibly computing local | ||||
|   -- tree-sitter-based 'commentstring'. Recompute every time for a proper | ||||
|   -- dot-repeat. In Visual and sometimes Normal mode it uses start position. | ||||
|   toggle_lines(lnum_from, lnum_to, vim.api.nvim_win_get_cursor(0)) | ||||
|   return '' | ||||
| end | ||||
|  | ||||
| --- Select contiguous commented lines at cursor | ||||
| local function textobject() | ||||
|   local lnum_cur = vim.fn.line('.') | ||||
|   local parts = get_comment_parts({ lnum_cur, vim.fn.col('.') }) | ||||
|   local comment_check = make_comment_check(parts) | ||||
|  | ||||
|   if not comment_check(vim.fn.getline(lnum_cur)) then | ||||
|     return | ||||
|   end | ||||
|  | ||||
|   -- Compute commented range | ||||
|   local lnum_from = lnum_cur | ||||
|   while (lnum_from >= 2) and comment_check(vim.fn.getline(lnum_from - 1)) do | ||||
|     lnum_from = lnum_from - 1 | ||||
|   end | ||||
|  | ||||
|   local lnum_to = lnum_cur | ||||
|   local n_lines = vim.api.nvim_buf_line_count(0) | ||||
|   while (lnum_to <= n_lines - 1) and comment_check(vim.fn.getline(lnum_to + 1)) do | ||||
|     lnum_to = lnum_to + 1 | ||||
|   end | ||||
|  | ||||
|   -- Select range linewise for operator to act upon | ||||
|   vim.cmd('normal! ' .. lnum_from .. 'GV' .. lnum_to .. 'G') | ||||
| end | ||||
|  | ||||
| return { operator = operator, textobject = textobject, toggle_lines = toggle_lines } | ||||
| @@ -114,6 +114,24 @@ do | ||||
|       do_open(table.concat(vim.iter(lines):map(vim.trim):totable())) | ||||
|     end, { desc = gx_desc }) | ||||
|   end | ||||
|  | ||||
|   --- Default maps for built-in commenting | ||||
|   do | ||||
|     local operator_rhs = function() | ||||
|       return require('vim._comment').operator() | ||||
|     end | ||||
|     vim.keymap.set({ 'n', 'x' }, 'gc', operator_rhs, { expr = true, desc = 'Toggle comment' }) | ||||
|  | ||||
|     local line_rhs = function() | ||||
|       return require('vim._comment').operator() .. '_' | ||||
|     end | ||||
|     vim.keymap.set('n', 'gcc', line_rhs, { expr = true, desc = 'Toggle comment line' }) | ||||
|  | ||||
|     local textobject_rhs = function() | ||||
|       require('vim._comment').textobject() | ||||
|     end | ||||
|     vim.keymap.set({ 'o' }, 'gc', textobject_rhs, { desc = 'Comment textobject' }) | ||||
|   end | ||||
| end | ||||
|  | ||||
| --- Default menus | ||||
|   | ||||
							
								
								
									
										644
									
								
								test/functional/lua/comment_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										644
									
								
								test/functional/lua/comment_spec.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,644 @@ | ||||
| local helpers = require('test.functional.helpers')(after_each) | ||||
|  | ||||
| local api = helpers.api | ||||
| local clear = helpers.clear | ||||
| local eq = helpers.eq | ||||
| local exec_capture = helpers.exec_capture | ||||
| local exec_lua = helpers.exec_lua | ||||
| local feed = helpers.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('matches comment parts strictly when detecting comment/uncomment', function() | ||||
|       local validate = function(from, to, ref_lines) | ||||
|         set_lines({ '#aa', '# aa', '#  aa' }) | ||||
|         toggle_lines(from, to) | ||||
|         eq(get_lines(), ref_lines) | ||||
|       end | ||||
|  | ||||
|       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 = { | ||||
|         'lua << EOF', | ||||
|         '  print(1)', | ||||
|         'EOF', | ||||
|       } | ||||
|       set_lines(lines) | ||||
|  | ||||
|       -- Vimscript's tree-sitter grammar is (currently) written in a way that Lua's | ||||
|       -- injection really starts at the first non-blank character | ||||
|       set_cursor(2, 1) | ||||
|       feed('gc_') | ||||
|       eq(get_lines()[2], '  "print(1)') | ||||
|  | ||||
|       set_lines(lines) | ||||
|       set_cursor(2, 2) | ||||
|       feed('.') | ||||
|       eq(get_lines()[2], '  -- 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) | ||||
		Reference in New Issue
	
	Block a user