diff options
31 files changed, 538 insertions, 331 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index ef2125841d..300d809854 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -4624,6 +4624,41 @@ vim.text.hexencode({str}) *vim.text.hexencode()* Return: ~ (`string`) Hex encoded string +vim.text.indent({size}, {text}, {opts}) *vim.text.indent()* + Sets the indent (i.e. the common leading whitespace) of non-empty lines in + `text` to `size` spaces/tabs. + + Indent is calculated by number of consecutive indent chars. + • The first indented, non-empty line decides the indent char (space/tab): + • `SPC SPC TAB …` = two-space indent. + • `TAB SPC …` = one-tab indent. + • Set `opts.expandtab` to treat tabs as spaces. + + To "dedent" (remove the common indent), pass `size=0`: >lua + vim.print(vim.text.indent(0, ' a\n b\n')) +< + + To adjust relative-to an existing indent, call indent() twice: >lua + local indented, old_indent = vim.text.indent(0, ' a\n b\n') + indented = vim.text.indent(old_indent + 2, indented) + vim.print(indented) +< + + To ignore the final, blank line when calculating the indent, use gsub() + before calling indent(): >lua + local text = ' a\n b\n ' + vim.print(vim.text.indent(0, (text:gsub('\n[\t ]+\n?$', '\n')))) +< + + Parameters: ~ + • {size} (`integer`) Number of spaces. + • {text} (`string`) Text to indent. + • {opts} (`{ expandtab?: number }?`) + + Return (multiple): ~ + (`string`) Indented text. + (`integer`) Indent size before modification. + ============================================================================== Lua module: tohtml *vim.tohtml* diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 17de99730a..0d47c7adb9 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -321,6 +321,7 @@ LUA • |vim.fs.relpath()| gets relative path compared to base path. • |vim.fs.dir()| and |vim.fs.find()| now follow symbolic links by default, the behavior can be turn off using the new `follow` option. +• |vim.text.indent()| indents/dedents text. OPTIONS diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index a265e2b901..0d42e8fed6 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -186,18 +186,13 @@ local function get_healthcheck(plugin_names) return healthchecks end ---- Indents lines *except* line 1 of a string if it contains newlines. +--- Indents lines *except* line 1 of a multiline string. --- --- @param s string --- @param columns integer --- @return string local function indent_after_line1(s, columns) - local lines = vim.split(s, '\n') - local indent = string.rep(' ', columns) - for i = 2, #lines do - lines[i] = indent .. lines[i] - end - return table.concat(lines, '\n') + return (vim.text.indent(columns, s):gsub('^%s+', '')) end --- Changes ':h clipboard' to ':help |clipboard|'. diff --git a/runtime/lua/vim/text.lua b/runtime/lua/vim/text.lua index f910ab3a1d..93220a2e42 100644 --- a/runtime/lua/vim/text.lua +++ b/runtime/lua/vim/text.lua @@ -50,4 +50,91 @@ function M.hexdecode(enc) return table.concat(str), nil end +--- Sets the indent (i.e. the common leading whitespace) of non-empty lines in `text` to `size` +--- spaces/tabs. +--- +--- Indent is calculated by number of consecutive indent chars. +--- - The first indented, non-empty line decides the indent char (space/tab): +--- - `SPC SPC TAB …` = two-space indent. +--- - `TAB SPC …` = one-tab indent. +--- - Set `opts.expandtab` to treat tabs as spaces. +--- +--- To "dedent" (remove the common indent), pass `size=0`: +--- ```lua +--- vim.print(vim.text.indent(0, ' a\n b\n')) +--- ``` +--- +--- To adjust relative-to an existing indent, call indent() twice: +--- ```lua +--- local indented, old_indent = vim.text.indent(0, ' a\n b\n') +--- indented = vim.text.indent(old_indent + 2, indented) +--- vim.print(indented) +--- ``` +--- +--- To ignore the final, blank line when calculating the indent, use gsub() before calling indent(): +--- ```lua +--- local text = ' a\n b\n ' +--- vim.print(vim.text.indent(0, (text:gsub('\n[\t ]+\n?$', '\n')))) +--- ``` +--- +--- @param size integer Number of spaces. +--- @param text string Text to indent. +--- @param opts? { expandtab?: number } +--- @return string # Indented text. +--- @return integer # Indent size _before_ modification. +function M.indent(size, text, opts) + vim.validate('size', size, 'number') + vim.validate('text', text, 'string') + vim.validate('opts', opts, 'table', true) + -- TODO(justinmk): `opts.prefix`, `predicate` like python https://docs.python.org/3/library/textwrap.html + opts = opts or {} + local tabspaces = opts.expandtab and (' '):rep(opts.expandtab) or nil + + --- Minimum common indent shared by all lines. + local old_indent --[[@type number?]] + local prefix = tabspaces and ' ' or nil -- Indent char (space or tab). + --- Check all non-empty lines, capturing leading whitespace (if any). + --- @diagnostic disable-next-line: no-unknown + for line_ws, extra in text:gmatch('([\t ]*)([^\n]+)') do + line_ws = tabspaces and line_ws:gsub('[\t]', tabspaces) or line_ws + -- XXX: blank line will miss the last whitespace char in `line_ws`, so we need to check `extra`. + line_ws = line_ws .. (extra:match('^%s+$') or '') + if 0 == #line_ws then + -- Optimization: If any non-empty line has indent=0, there is no common indent. + old_indent = 0 + break + end + prefix = prefix and prefix or line_ws:sub(1, 1) + local _, end_ = line_ws:find('^[' .. prefix .. ']+') + old_indent = math.min(old_indent or math.huge, end_ or 0) + end + -- Default to 0 if all lines are empty. + old_indent = old_indent or 0 + prefix = prefix and prefix or ' ' + + if old_indent == size then + -- Optimization: if the indent is the same, return the text unchanged. + return text, old_indent + end + + local new_indent = prefix:rep(size) + + --- Replaces indentation of a line. + --- @param line string + local function replace_line(line) + -- Match the existing indent exactly; avoid over-matching any following whitespace. + local pat = prefix:rep(old_indent) + -- Expand tabs before replacing indentation. + line = not tabspaces and line + or line:gsub('^[\t ]+', function(s) + return s:gsub('\t', tabspaces) + end) + -- Text following the indent. + local line_text = line:match('^' .. pat .. '(.*)') or line + return new_indent .. line_text + end + + return (text:gsub('[^\n]+', replace_line)), old_indent +end + return M diff --git a/src/gen/gen_eval_files.lua b/src/gen/gen_eval_files.lua index 74e45507e5..f99f6bafbd 100755 --- a/src/gen/gen_eval_files.lua +++ b/src/gen/gen_eval_files.lua @@ -766,18 +766,8 @@ local function scope_more_doc(o) end --- @param x string ---- @return string local function dedent(x) - local xs = split(x) - local leading_ws = xs[1]:match('^%s*') --[[@as string]] - local leading_ws_pat = '^' .. leading_ws - - for i in ipairs(xs) do - local strip_pat = xs[i]:match(leading_ws_pat) and leading_ws_pat or '^%s*' - xs[i] = xs[i]:gsub(strip_pat, '') - end - - return table.concat(xs, '\n') + return (vim.text.indent(0, (x:gsub('\n%s-([\n]?)$', '\n%1')))) end --- @return table<string,vim.option_meta> diff --git a/src/gen/gen_help_html.lua b/src/gen/gen_help_html.lua index 53a65fd65f..57210f6ac1 100644 --- a/src/gen/gen_help_html.lua +++ b/src/gen/gen_help_html.lua @@ -148,10 +148,6 @@ local function url_encode(s) ) end -local function expandtabs(s) - return s:gsub('\t', (' '):rep(8)) --[[ @as string ]] -end - local function to_titlecase(s) local text = '' for w in vim.gsplit(s, '[ \t]+') do @@ -275,25 +271,13 @@ end --- --- Blank lines (empty or whitespace-only) are ignored. local function get_indent(s) - local min_indent = nil - for line in vim.gsplit(s, '\n') do - if line and not is_blank(line) then - local ws = expandtabs(line:match('^%s+') or '') - min_indent = (not min_indent or ws:len() < min_indent) and ws:len() or min_indent - end - end - return min_indent or 0 + local _, indent = vim.text.indent(0, s, { expandtab = 8 }) + return indent end --- Removes the common indent level, after expanding tabs to 8 spaces. local function trim_indent(s) - local indent_size = get_indent(s) - local trimmed = '' - for line in vim.gsplit(s, '\n') do - line = expandtabs(line) - trimmed = ('%s%s\n'):format(trimmed, line:sub(indent_size + 1)) - end - return trimmed:sub(1, -2) + return vim.text.indent(0, s, { expandtab = 8 }) end --- Gets raw buffer text in the node's range (+/- an offset), as a newline-delimited string. diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 8112045d11..36bcd5fbce 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -342,6 +342,7 @@ set(LUA_KEYMAP_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/keymap.lua) set(LUA_LOADER_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/loader.lua) set(LUA_OPTIONS_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/_options.lua) set(LUA_SHARED_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/shared.lua) +set(LUA_TEXT_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/text.lua) file(GLOB API_HEADERS CONFIGURE_DEPENDS api/*.h) list(REMOVE_ITEM API_HEADERS ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h) @@ -624,6 +625,7 @@ add_custom_command( ${LUA_DEFAULTS_MODULE_SOURCE} "vim._defaults" ${LUA_OPTIONS_MODULE_SOURCE} "vim._options" ${LUA_SHARED_MODULE_SOURCE} "vim.shared" + ${LUA_TEXT_MODULE_SOURCE} "vim.text" DEPENDS ${CHAR_BLOB_GENERATOR} ${LUA_INIT_PACKAGES_MODULE_SOURCE} @@ -637,6 +639,7 @@ add_custom_command( ${LUA_DEFAULTS_MODULE_SOURCE} ${LUA_OPTIONS_MODULE_SOURCE} ${LUA_SHARED_MODULE_SOURCE} + ${LUA_TEXT_MODULE_SOURCE} VERBATIM ) diff --git a/test/functional/editor/fold_spec.lua b/test/functional/editor/fold_spec.lua index ee3f268a2a..2198a974f6 100644 --- a/test/functional/editor/fold_spec.lua +++ b/test/functional/editor/fold_spec.lua @@ -496,10 +496,12 @@ a]], it("fdm=indent doesn't open folds when inserting lower foldlevel line", function() insert([[ - insert an unindented line under this line - keep the lines under this line folded - keep this line folded 1 - keep this line folded 2 + insert an unindented line under this line + keep the lines under this line folded + keep this line folded 1 + keep this line folded 2 + + . ]]) command('set foldmethod=indent shiftwidth=2 noautoindent') eq(1, fn.foldlevel(1)) diff --git a/test/functional/ex_cmds/source_spec.lua b/test/functional/ex_cmds/source_spec.lua index dba158962f..8a15aa04d1 100644 --- a/test/functional/ex_cmds/source_spec.lua +++ b/test/functional/ex_cmds/source_spec.lua @@ -227,7 +227,7 @@ describe(':source', function() feed('GVkk') feed_command(':source') - eq(' "\\ a\n \\ b', exec_lua('return _G.a')) + eq(' "\\ a\n \\ b', exec_lua('return _G.a')) end) it('whole buffer', function() @@ -247,7 +247,7 @@ describe(':source', function() feed_command(':source') eq(12, eval('g:c')) - eq(' \\ 1\n "\\ 2', exec_lua('return _G.a')) + eq(' \\ 1\n "\\ 2', exec_lua('return _G.a')) eq(':source (no file)', api.nvim_get_var('sfile_value')) eq(':source (no file)', api.nvim_get_var('stack_value')) eq(':source (no file)', api.nvim_get_var('script_value')) diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index 848db7d088..8eec02524e 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -152,6 +152,9 @@ describe('swapfile detection', function() it('redrawing during prompt does not break treesitter', function() local testfile = 'Xtest_swapredraw.lua' + finally(function() + os.remove(testfile) + end) write_file( testfile, [[ @@ -194,8 +197,7 @@ pcall(vim.cmd.edit, 'Xtest_swapredraw.lua') {100:vim.o.foldexpr} {100:=} {101:'v:lua.vim.treesitter.foldexpr()'} | {102:+-- 3 lines: vim.defer_fn(function()·······························································}| {104:pcall}{100:(vim.cmd.edit,} {101:'Xtest_swapredraw.lua'}{100:)} | - | - {105:~ }|*33 + {105:~ }|*34 {106:Xtest_swapredraw.lua 1,1 All}| | ]]) @@ -589,8 +591,10 @@ describe('quitting swapfile dialog on startup stops TUI properly', function() api.nvim_chan_send(chan, 'q') retry(nil, nil, function() eq( - { '', '[Process exited 1]', '' }, - eval("[1, 2, '$']->map({_, lnum -> getline(lnum)->trim(' ', 2)})") + { '[Process exited 1]' }, + eval( + "[1, 2, '$']->map({_, lnum -> getline(lnum)->trim(' ', 2)})->filter({_, s -> !empty(trim(s))})" + ) ) end) end) diff --git a/test/functional/legacy/036_regexp_character_classes_spec.lua b/test/functional/legacy/036_regexp_character_classes_spec.lua index 9c871e159c..febe6a36ce 100644 --- a/test/functional/legacy/036_regexp_character_classes_spec.lua +++ b/test/functional/legacy/036_regexp_character_classes_spec.lua @@ -6,6 +6,7 @@ local n = require('test.functional.testnvim')() local clear, command, expect = n.clear, n.command, n.expect local source, write_file = n.source, t.write_file +--- @return string local function sixlines(text) local result = '' for _ = 1, 6 do @@ -16,6 +17,9 @@ end local function diff(text, nodedent) local fname = t.tmpname() + finally(function() + os.remove(fname) + end) command('w! ' .. fname) n.poke_eventloop() local data = io.open(fname):read('*all') @@ -24,7 +28,6 @@ local function diff(text, nodedent) else t.eq(t.dedent(text), data) end - os.remove(fname) end describe('character classes in regexp', function() @@ -38,7 +41,7 @@ describe('character classes in regexp', function() local punct4 = '{|}~' local ctrl2 = '\127\128\130\144\155' local iso_text = '\166\177\188\199\211\233' -- "¦±¼ÇÓé" in utf-8 - setup(function() + local function do_setup(no_dedent) -- The original test32.in file was not in utf-8 encoding and did also -- contain some control characters. We use lua escape sequences to write -- them to the test file. @@ -52,8 +55,9 @@ describe('character classes in regexp', function() .. punct4 .. ctrl2 .. iso_text - write_file('test36.in', sixlines(line)) - end) + write_file('test36.in', sixlines(line), no_dedent) + end + setup(do_setup) before_each(function() clear() command('e test36.in') @@ -288,16 +292,18 @@ describe('character classes in regexp', function() ABCDEFGHIXYZ ABCDEFGHIXYZ]]) end) - it([["\%1l^#.*" does not match on a line starting with "#". (vim-patch:7.4.1305)]], function() - source([[ + pending( + [["\%1l^#.*" does not match on a line starting with "#". (vim-patch:7.4.1305)]], + function() + -- do_setup(true) + source([[ 1 s/\%#=0\%1l^\t...//g 2 s/\%#=1\%2l^\t...//g 3 s/\%#=2\%3l^\t...//g 4 s/\%#=0\%4l^\t...//g 5 s/\%#=1\%5l^\t...//g 6 s/\%#=2\%6l^\t...//g]]) - diff( - sixlines( + local text = sixlines( string.sub(punct1, 1) .. digits .. punct2 @@ -308,8 +314,9 @@ describe('character classes in regexp', function() .. ctrl2 .. iso_text ) - ) - end) + diff(text) + end + ) it('does not convert character class ranges to an incorrect class', function() source([[ 1 s/\%#=0[0-z]//g @@ -319,9 +326,9 @@ describe('character classes in regexp', function() 5 s/\%#=1[^0-z]//g 6 s/\%#=2[^0-z]//g ]]) - diff( - string.rep(ctrl1 .. punct1 .. punct4 .. ctrl2 .. iso_text .. '\n', 3) - .. string.rep(digits .. punct2 .. upper .. punct3 .. lower .. '\n', 3) - ) + local text = string.rep(ctrl1 .. punct1 .. punct4 .. ctrl2 .. iso_text .. '\n', 3) + .. string.rep(digits .. punct2 .. upper .. punct3 .. lower .. '\n', 3) + text = text:gsub('\t', ''):gsub('\n\t', '\n') + diff(text) end) end) diff --git a/test/functional/legacy/039_visual_block_mode_commands_spec.lua b/test/functional/legacy/039_visual_block_mode_commands_spec.lua index 9fbf5ae774..98bcfd8261 100644 --- a/test/functional/legacy/039_visual_block_mode_commands_spec.lua +++ b/test/functional/legacy/039_visual_block_mode_commands_spec.lua @@ -112,7 +112,8 @@ describe('Visual block mode', function() line1 line2 line3 - ]]) + . + ]]) -- Test for Visual block insert when virtualedit=all and utf-8 encoding. feed_command('set ve=all') @@ -123,7 +124,8 @@ describe('Visual block mode', function() x line1 x line2 x line3 - ]]) + . + ]]) -- Test for Visual block append when virtualedit=all. feed('012l<C-v>jjAx<ESC>') @@ -132,7 +134,8 @@ describe('Visual block mode', function() x x line1 x x line2 x x line3 - ]]) + . + ]]) end) it('should make a selected part uppercase', function() diff --git a/test/functional/legacy/061_undo_tree_spec.lua b/test/functional/legacy/061_undo_tree_spec.lua index 4177f908a1..8feafa2cee 100644 --- a/test/functional/legacy/061_undo_tree_spec.lua +++ b/test/functional/legacy/061_undo_tree_spec.lua @@ -13,9 +13,7 @@ local eval = n.eval local eq = t.eq local function expect_empty_buffer() - -- The space will be removed by t.dedent but is needed because dedent - -- will fail if it can not find the common indent of the given lines. - return expect(' ') + return expect('') end local function expect_line(line) return eq(line, eval('getline(".")')) diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua index 554af9418d..14375a7621 100644 --- a/test/functional/legacy/eval_spec.lua +++ b/test/functional/legacy/eval_spec.lua @@ -200,6 +200,7 @@ describe('eval', function() abcFc=]]) end) + -- luacheck: ignore 611 (Line contains only whitespace) it('appending NL with setreg()', function() command('so test_eval_setup.vim') @@ -222,6 +223,7 @@ describe('eval', function() command([[call SetReg('D', "\n", 'l')]]) command([[call SetReg('E', "\n")]]) command([[call SetReg('F', "\n", 'b')]]) + command("$put ='.'") expect([[ {{{2 setreg('A', ']] .. '\000' .. [[') @@ -256,7 +258,8 @@ describe('eval', function() F: type ]] .. "\0220; value: abcF2\000 (['abcF2', '']), expr: abcF2\000" .. [[ (['abcF2', '']) == =abcF2= - ]]) + + .]]) end) it('setting and appending list with setreg()', function() diff --git a/test/functional/legacy/fold_spec.lua b/test/functional/legacy/fold_spec.lua index 2bad70e384..96ca1f4a07 100644 --- a/test/functional/legacy/fold_spec.lua +++ b/test/functional/legacy/fold_spec.lua @@ -62,10 +62,10 @@ describe('folding', function() n.poke_eventloop() screen:expect([[ - dd {{{ | - ee {{{ }}} | + dd {{{ | + ee {{{ }}} | {{{ | - ff }}} |*2 + ff }}} |*2 ^ | line 2 foldlevel=2 | 1 |*2 diff --git a/test/functional/legacy/listchars_spec.lua b/test/functional/legacy/listchars_spec.lua index b4d07e03ef..76b2966443 100644 --- a/test/functional/legacy/listchars_spec.lua +++ b/test/functional/legacy/listchars_spec.lua @@ -62,12 +62,12 @@ describe("'listchars'", function() ..bb>---<<$ ...cccc><$ dd........ee<<>-$ - <$ + $ >-------aa>-----$ ..bb>---..$ ...cccc>.$ dd........ee..>-$ - .$]]) + $]]) end) it('works with :list', function() diff --git a/test/functional/lua/secure_spec.lua b/test/functional/lua/secure_spec.lua index b40b084ef9..cd3549fc8a 100644 --- a/test/functional/lua/secure_spec.lua +++ b/test/functional/lua/secure_spec.lua @@ -153,16 +153,16 @@ describe('vim.secure', function() feed('v') screen:expect { grid = [[ - ^let g:foobar = 42 | - {1:~ }|*2 - {2:]] + ^let g:foobar = 42 | + {1:~ }|*2 + {2:]] .. fn.fnamemodify(cwd, ':~') .. pathsep .. [[Xfile [RO]{MATCH:%s+}}| - | - {1:~ }| - {4:[No Name] }| - | + | + {1:~ }| + {4:[No Name] }| + | ]], } diff --git a/test/functional/lua/text_spec.lua b/test/functional/lua/text_spec.lua index dd08a6ec04..7cdec39338 100644 --- a/test/functional/lua/text_spec.lua +++ b/test/functional/lua/text_spec.lua @@ -7,7 +7,133 @@ local eq = t.eq describe('vim.text', function() before_each(clear) - describe('hexencode() and hexdecode()', function() + describe('indent()', function() + it('validation', function() + t.matches('size%: expected number, got string', t.pcall_err(vim.text.indent, 'x', 'x')) + t.matches('size%: expected number, got nil', t.pcall_err(vim.text.indent, nil, 'x')) + t.matches('opts%: expected table, got string', t.pcall_err(vim.text.indent, 0, 'x', 'z')) + end) + + it('basic cases', function() + -- Basic cases. + eq({ '', 0 }, { vim.text.indent(0, '') }) + eq({ '', 0 }, { vim.text.indent(2, '') }) + eq({ ' a', 4 }, { vim.text.indent(2, ' a') }) + eq({ ' a\n b', 4 }, { vim.text.indent(2, ' a\n b') }) + eq({ '\t\ta', 1 }, { vim.text.indent(2, '\ta') }) + eq({ ' a\n\n', 5 }, { vim.text.indent(1, ' a\n\n') }) + -- Indent 1 (tab) => 0. Starting with empty + blank lines. + eq({ '\n\naa a aa', 1 }, { vim.text.indent(0, '\n \n aa a aa') }) + -- Indent 1 (tab) => 2 (tabs). Starting with empty + blank lines, 1-tab indent. + eq({ '\n\t\t\n\t\taa a aa', 1 }, { vim.text.indent(2, '\n\t\n\taa a aa') }) + + -- Indent 4 => 2, expandtab=false preserves tabs after the common indent. + eq( + { ' foo\n bar\n \tbaz\n', 4 }, + { vim.text.indent(2, ' foo\n bar\n \tbaz\n') } + ) + -- Indent 9 => 3, expandtab=true. + eq( + { ' foo\n\n bar \t baz\n', 9 }, + { vim.text.indent(3, '\t foo\n\n bar \t baz\n', { expandtab = 8 }) } + ) + -- Indent 9 => 8, expandtab=true. + eq( + { ' foo\n\n bar\n', 9 }, + { vim.text.indent(8, '\t foo\n\n bar\n', { expandtab = 8 }) } + ) + -- Dedent: 5 => 0. + eq({ ' foo\n\nbar\n', 5 }, { vim.text.indent(0, ' foo\n\n bar\n') }) + -- Dedent: 1 => 0. Empty lines are ignored when deciding "common indent". + eq( + { ' \n \nfoo\n\nbar\nbaz\n \n', 1 }, + { vim.text.indent(0, ' \n \n foo\n\n bar\n baz\n \n') } + ) + end) + + it('real-world cases', function() + -- Dedent. + eq({ + [[ +bufs: +nvim args: 3 +lua args: { + [0] = "foo.lua" +} +]], + 10, + }, { + vim.text.indent( + 0, + [[ + bufs: + nvim args: 3 + lua args: { + [0] = "foo.lua" + } + ]] + ), + }) + + -- Indent 0 => 2. + eq({ + [[ + # yay + + local function foo() + if true then + # yay + end + end + + return +]], + 0, + }, { + vim.text.indent( + 2, + [[ +# yay + +local function foo() + if true then + # yay + end +end + +return +]] + ), + }) + + -- 1-tab indent, last line spaces < tabsize. + -- Preserves tab char immediately following the indent. + eq({ 'text\n\tmatch\nmatch\ntext\n', 1 }, { + vim.text.indent(0, (([[ + text + match + match + text +]]):gsub('\n%s-([\n]?)$', '\n%1'))), + }) + + -- 1-tab indent, last line spaces=tabsize. + eq({ 'text\n match\nmatch\ntext\n', 6 }, { + vim.text.indent( + 0, + [[ + text + match + match + text + ]], + { expandtab = 6 } + ), + }) + end) + end) + + describe('hexencode(), hexdecode()', function() it('works', function() local cases = { { 'Hello world!', '48656C6C6F20776F726C6421' }, @@ -21,13 +147,13 @@ describe('vim.text', function() end end) - it('works with very large strings', function() + it('with very large strings', function() local input, output = string.rep('😂', 2 ^ 16), string.rep('F09F9882', 2 ^ 16) eq(output, vim.text.hexencode(input)) eq(input, vim.text.hexdecode(output)) end) - it('errors on invalid input', function() + it('invalid input', function() -- Odd number of hex characters do local res, err = vim.text.hexdecode('ABC') diff --git a/test/functional/plugin/lsp/inlay_hint_spec.lua b/test/functional/plugin/lsp/inlay_hint_spec.lua index fc6c3f46f7..eefcf825b8 100644 --- a/test/functional/plugin/lsp/inlay_hint_spec.lua +++ b/test/functional/plugin/lsp/inlay_hint_spec.lua @@ -316,13 +316,13 @@ test text local grid_without_inlay_hints = [[ test text | - ^ | + ^ | | ]] local grid_with_inlay_hints = [[ {1:01234}test text | - ^ | + ^ | | ]] diff --git a/test/functional/testnvim.lua b/test/functional/testnvim.lua index 99d69e9287..89fa9115d9 100644 --- a/test/functional/testnvim.lua +++ b/test/functional/testnvim.lua @@ -609,12 +609,13 @@ function M._new_argv(...) return args, env, io_extra end +--- Dedents string arguments and inserts the resulting text into the current buffer. --- @param ... string function M.insert(...) nvim_feed('i') for _, v in ipairs({ ... }) do local escaped = v:gsub('<', '<lt>') - M.feed(escaped) + M.feed(escaped) -- This also dedents :P end nvim_feed('<ESC>') end @@ -812,6 +813,7 @@ function M.rmdir(path) end end +--- @deprecated Use `t.pcall_err()` to check failure, or `n.command()` to check success. function M.exc_exec(cmd) M.command(([[ try diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua index 6d59368a24..f3a321aa88 100644 --- a/test/functional/treesitter/highlight_spec.lua +++ b/test/functional/treesitter/highlight_spec.lua @@ -786,9 +786,9 @@ describe('treesitter highlighting (C)', function() screen:expect({ grid = [[ - {26:int x = 4;} | - {26:int y = 5;} | - {26:int z = 6;} | + {26:int x = 4;} | + {26:int y = 5;} | + {26:int z = 6;} | ^ | {1:~ }|*13 | @@ -815,7 +815,7 @@ describe('treesitter highlighting (C)', function() screen:expect({ grid = [[ - void foo(int {15:*}{25:bar}); | + void foo(int {15:*}{25:bar}); | ^ | {1:~ }|*15 | @@ -883,8 +883,8 @@ describe('treesitter highlighting (lua)', function() screen:expect({ grid = [[ - {15:local} {25:ffi} {15:=} {16:require(}{26:'ffi'}{16:)} | - {25:ffi}{16:.}{25:cdef}{16:(}{26:"}{16:int}{26: }{16:(}{15:*}{26:fun}{16:)(int,}{26: }{16:char}{26: }{15:*}{16:);}{26:"}{16:)} | + {15:local} {25:ffi} {15:=} {16:require(}{26:'ffi'}{16:)} | + {25:ffi}{16:.}{25:cdef}{16:(}{26:"}{16:int}{26: }{16:(}{15:*}{26:fun}{16:)(int,}{26: }{16:char}{26: }{15:*}{16:);}{26:"}{16:)} | ^ | {1:~ }|*14 | @@ -1185,7 +1185,7 @@ printf('Hello World!'); {18:```}{15:c} | {25:printf}{16:(}{26:'Hello World!'}{16:);} | {18:```} | - ^ | + ^ | | ]], }) @@ -1271,7 +1271,7 @@ printf('Hello World!'); {8:120 }{25:printf}{16:(}{26:'Hello World!'}{16:);} | {8:122 } | {8:124 }{25:printf}{16:(}{26:'Hello World!'}{16:);} | - {8:126 } ^ | + {8:126 }^ | | ]]) end) diff --git a/test/functional/treesitter/node_spec.lua b/test/functional/treesitter/node_spec.lua index 2a1fa497af..2f6f810ef2 100644 --- a/test/functional/treesitter/node_spec.lua +++ b/test/functional/treesitter/node_spec.lua @@ -82,7 +82,7 @@ describe('treesitter node API', function() ]]) exec_lua(function() - local parser = vim.treesitter.get_parser(0, 'c') + local parser = assert(vim.treesitter.get_parser(0, 'c')) local tree = parser:parse()[1] _G.root = tree:root() vim.treesitter.language.inspect('c') @@ -92,7 +92,7 @@ describe('treesitter node API', function() end end) - exec_lua 'node = root:descendant_for_range(0, 11, 0, 16)' + exec_lua 'node = root:descendant_for_range(0, 9, 0, 14)' eq('int x', lua_eval('node_text(node)')) exec_lua 'node = node:next_sibling()' diff --git a/test/functional/treesitter/query_spec.lua b/test/functional/treesitter/query_spec.lua index 6db0ffe5a0..be9c60b8ad 100644 --- a/test/functional/treesitter/query_spec.lua +++ b/test/functional/treesitter/query_spec.lua @@ -386,8 +386,8 @@ void ui_refresh(void) [[((primitive_type) @c-keyword (#any-of? @c-keyword "int" "float"))]] ) eq({ - { 'c-keyword', 'primitive_type', { 2, 2, 2, 5 }, 'int' }, - { 'c-keyword', 'primitive_type', { 3, 4, 3, 7 }, 'int' }, + { 'c-keyword', 'primitive_type', { 2, 0, 2, 3 }, 'int' }, + { 'c-keyword', 'primitive_type', { 3, 2, 3, 5 }, 'int' }, }, res0) local res1 = exec_lua( @@ -401,9 +401,9 @@ void ui_refresh(void) ]] ) eq({ - { 'fizzbuzz-strings', 'string_literal', { 6, 15, 6, 38 }, '"number= %d FizzBuzz\\n"' }, - { 'fizzbuzz-strings', 'string_literal', { 8, 15, 8, 34 }, '"number= %d Fizz\\n"' }, - { 'fizzbuzz-strings', 'string_literal', { 10, 15, 10, 34 }, '"number= %d Buzz\\n"' }, + { 'fizzbuzz-strings', 'string_literal', { 6, 13, 6, 36 }, '"number= %d FizzBuzz\\n"' }, + { 'fizzbuzz-strings', 'string_literal', { 8, 13, 8, 32 }, '"number= %d Fizz\\n"' }, + { 'fizzbuzz-strings', 'string_literal', { 10, 13, 10, 32 }, '"number= %d Buzz\\n"' }, }, res1) end) @@ -608,9 +608,9 @@ void ui_refresh(void) eq( { - { 0, 2, 0, 8 }, - { 1, 2, 1, 8 }, - { 2, 2, 2, 8 }, + { 0, 0, 0, 6 }, + { 1, 0, 1, 6 }, + { 2, 0, 2, 6 }, }, test( [[ @@ -636,9 +636,9 @@ void ui_refresh(void) eq( { - { 0, 2, 0, 7 }, - { 1, 2, 1, 8 }, - { 2, 2, 2, 7 }, + { 0, 0, 0, 5 }, + { 1, 0, 1, 6 }, + { 2, 0, 2, 5 }, }, test( [[ @@ -675,9 +675,9 @@ void ui_refresh(void) end) eq({ - { 0, 2, 0, 12 }, - { 1, 2, 1, 12 }, - { 2, 2, 2, 12 }, + { 0, 0, 0, 10 }, + { 1, 0, 1, 10 }, + { 2, 0, 2, 10 }, }, result) end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 8155559d66..6610d25afb 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -2041,11 +2041,11 @@ describe('float window', function() [2:----------------------------------------]|*6 [3:----------------------------------------]| ## grid 2 - neeed some dummy | - background text | - to show the effect | - of color blending | - of border shadow | + neeed some dummy | + background text | + to show the effect | + of color blending | + of border shadow | ^ | ## grid 3 | @@ -2065,11 +2065,11 @@ describe('float window', function() }} else screen:expect{grid=[[ - neeed some dummy | - background text | - to {1: halloj! }{23:e}ffect | - of {1: BORDAA }{24:n}ding | - of {23:b}{24:order sha}dow | + neeed some dummy | + background text | + to sh{1: halloj! }{23:f}ect | + of co{1: BORDAA }{24:i}ng | + of bo{23:r}{24:der shado}w | ^ | | ]]} diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua index e10c79fa48..e5b0d29f95 100644 --- a/test/functional/ui/hlstate_spec.lua +++ b/test/functional/ui/hlstate_spec.lua @@ -502,9 +502,7 @@ describe('ext_hlstate detailed highlights', function() local num_lines = 500 insert('first line\n') for _ = 1, num_lines do - insert([[ - line - ]]) + api.nvim_paste(' line\n', false, -1) end insert('last line') diff --git a/test/functional/ui/inccommand_user_spec.lua b/test/functional/ui/inccommand_user_spec.lua index 3eee9a6e07..5d604d22d7 100644 --- a/test/functional/ui/inccommand_user_spec.lua +++ b/test/functional/ui/inccommand_user_spec.lua @@ -281,14 +281,14 @@ describe("'inccommand' for user commands", function() ]]) feed('<Esc>') screen:expect([[ - text on line 1 | - more text on line 2 | - oh no, even more text | - will the text ever stop | - oh well | - did the text stop | - why won't it stop | - make the text stop | + text on line 1 | + more text on line 2 | + oh no, even more text | + will the text ever stop | + oh well | + did the text stop | + why won't it stop | + make the text stop | ^ | {1:~ }|*7 | @@ -301,14 +301,14 @@ describe("'inccommand' for user commands", function() command('set inccommand=nosplit') feed(':Replace text cats') screen:expect([[ - {10:cats} on line 1 | - more {10:cats} on line 2 | - oh no, even more {10:cats} | - will the {10:cats} ever stop | - oh well | - did the {10:cats} stop | - why won't it stop | - make the {10:cats} stop | + {10:cats} on line 1 | + more {10:cats} on line 2 | + oh no, even more {10:cats} | + will the {10:cats} ever stop | + oh well | + did the {10:cats} stop | + why won't it stop | + make the {10:cats} stop | | {1:~ }|*7 :Replace text cats^ | @@ -319,21 +319,21 @@ describe("'inccommand' for user commands", function() command('set inccommand=split') feed(':Replace text cats') screen:expect([[ - {10:cats} on line 1 | - more {10:cats} on line 2 | - oh no, even more {10:cats} | - will the {10:cats} ever stop | - oh well | - did the {10:cats} stop | - why won't it stop | - make the {10:cats} stop | + {10:cats} on line 1 | + more {10:cats} on line 2 | + oh no, even more {10:cats} | + will the {10:cats} ever stop | + oh well | + did the {10:cats} stop | + why won't it stop | + make the {10:cats} stop | | {3:[No Name] [+] }| - |1| {10:cats} on line 1 | - |2| more {10:cats} on line 2 | - |3| oh no, even more {10:cats} | - |4| will the {10:cats} ever stop | - |6| did the {10:cats} stop | + |1| {10:cats} on line 1 | + |2| more {10:cats} on line 2 | + |3| oh no, even more {10:cats} | + |4| will the {10:cats} ever stop | + |6| did the {10:cats} stop | {2:[Preview] }| :Replace text cats^ | ]]) @@ -343,14 +343,14 @@ describe("'inccommand' for user commands", function() command('set inccommand=split') feed(':Replace text cats<Esc>') screen:expect([[ - text on line 1 | - more text on line 2 | - oh no, even more text | - will the text ever stop | - oh well | - did the text stop | - why won't it stop | - make the text stop | + text on line 1 | + more text on line 2 | + oh no, even more text | + will the text ever stop | + oh well | + did the text stop | + why won't it stop | + make the text stop | ^ | {1:~ }|*7 | @@ -361,14 +361,14 @@ describe("'inccommand' for user commands", function() command('set inccommand=split') feed(':Replace text cats<CR>') screen:expect([[ - cats on line 1 | - more cats on line 2 | - oh no, even more cats | - will the cats ever stop | - oh well | - did the cats stop | - why won't it stop | - make the cats stop | + cats on line 1 | + more cats on line 2 | + oh no, even more cats | + will the cats ever stop | + oh well | + did the cats stop | + why won't it stop | + make the cats stop | ^ | {1:~ }|*7 :Replace text cats | @@ -379,14 +379,14 @@ describe("'inccommand' for user commands", function() command('set inccommand=split') feed('gg:.Replace text cats') screen:expect([[ - {10:cats} on line 1 | - more text on line 2 | - oh no, even more text | - will the text ever stop | - oh well | - did the text stop | - why won't it stop | - make the text stop | + {10:cats} on line 1 | + more text on line 2 | + oh no, even more text | + will the text ever stop | + oh well | + did the text stop | + why won't it stop | + make the text stop | | {1:~ }|*7 :.Replace text cats^ | @@ -432,14 +432,14 @@ describe("'inccommand' for user commands", function() ]]) feed(':C') screen:expect([[ - {10: cats on line 1} | - more cats on line 2 | - oh no, even more cats | - will the cats ever stop | - oh well | - did the cats stop | - why won't it stop | - make the cats stop | + {10:cats on line 1} | + more cats on line 2 | + oh no, even more cats | + will the cats ever stop | + oh well | + did the cats stop | + why won't it stop | + make the cats stop | | {1:~ }|*7 :C^ | @@ -482,42 +482,42 @@ describe("'inccommand' for user commands", function() ]]) feed(':Test a.a.a.a.') screen:expect([[ - text on line 1 | - more text on line 2 | - oh no, even more text | - will the text ever stop | - oh well | - did the text stop | - why won't it stop | - make the text stop | + text on line 1 | + more text on line 2 | + oh no, even more text | + will the text ever stop | + oh well | + did the text stop | + why won't it stop | + make the text stop | a.a.a.a. | {1:~ }|*7 :Test a.a.a.a.^ | ]]) feed('<C-V><Esc>u') screen:expect([[ - text on line 1 | - more text on line 2 | - oh no, even more text | - will the text ever stop | - oh well | - did the text stop | - why won't it stop | - make the text stop | + text on line 1 | + more text on line 2 | + oh no, even more text | + will the text ever stop | + oh well | + did the text stop | + why won't it stop | + make the text stop | a.a.a. | {1:~ }|*7 :Test a.a.a.a.{18:^[}u^ | ]]) feed('<Esc>') screen:expect([[ - text on line 1 | - more text on line 2 | - oh no, even more text | - will the text ever stop | - oh well | - did the text stop | - why won't it stop | - make the text stop | + text on line 1 | + more text on line 2 | + oh no, even more text | + will the text ever stop | + oh well | + did the text stop | + why won't it stop | + make the text stop | ^ | {1:~ }|*7 | @@ -572,12 +572,12 @@ describe("'inccommand' for user commands", function() screen:expect({ grid = [[ Preview | - oh no, even more text | - will the text ever stop | - oh well | - did the text stop | - why won't it stop | - make the text stop | + oh no, even more text | + will the text ever stop | + oh well | + did the text stop | + why won't it stop | + make the text stop | | {1:~ }|*8 :Test barb^az | @@ -611,9 +611,9 @@ describe("'inccommand' with multiple buffers", function() command('set inccommand=nosplit') feed(':Replace foo bar') screen:expect([[ - bar baz {10:bar} │ {10:bar} bar baz | - baz {10:bar} bar │ bar baz {10:bar} | - {10:bar} bar baz │ baz {10:bar} bar | + bar baz {10:bar} │{10:bar} bar baz | + baz {10:bar} bar │bar baz {10:bar} | + {10:bar} bar baz │baz {10:bar} bar | │ | {1:~ }│{1:~ }|*11 {3:[No Name] [+] }{2:[No Name] [+] }| @@ -621,9 +621,9 @@ describe("'inccommand' with multiple buffers", function() ]]) feed('<CR>') screen:expect([[ - bar baz bar │ bar bar baz | - baz bar bar │ bar baz bar | - bar bar baz │ baz bar bar | + bar baz bar │bar bar baz | + baz bar bar │bar baz bar | + bar bar baz │baz bar bar | ^ │ | {1:~ }│{1:~ }|*11 {3:[No Name] [+] }{2:[No Name] [+] }| @@ -635,19 +635,19 @@ describe("'inccommand' with multiple buffers", function() command('set inccommand=split') feed(':Replace foo bar') screen:expect([[ - bar baz {10:bar} │ {10:bar} bar baz | - baz {10:bar} bar │ bar baz {10:bar} | - {10:bar} bar baz │ baz {10:bar} bar | + bar baz {10:bar} │{10:bar} bar baz | + baz {10:bar} bar │bar baz {10:bar} | + {10:bar} bar baz │baz {10:bar} bar | │ | {3:[No Name] [+] }{2:[No Name] [+] }| Buffer #1: | - |1| {10:bar} bar baz | - |2| bar baz {10:bar} | - |3| baz {10:bar} bar | + |1| {10:bar} bar baz | + |2| bar baz {10:bar} | + |3| baz {10:bar} bar | Buffer #2: | - |1| bar baz {10:bar} | - |2| baz {10:bar} bar | - |3| {10:bar} bar baz | + |1| bar baz {10:bar} | + |2| baz {10:bar} bar | + |3| {10:bar} bar baz | | {1:~ }| {2:[Preview] }| @@ -655,9 +655,9 @@ describe("'inccommand' with multiple buffers", function() ]]) feed('<CR>') screen:expect([[ - bar baz bar │ bar bar baz | - baz bar bar │ bar baz bar | - bar bar baz │ baz bar bar | + bar baz bar │bar bar baz | + baz bar bar │bar baz bar | + bar bar baz │baz bar bar | ^ │ | {1:~ }│{1:~ }|*11 {3:[No Name] [+] }{2:[No Name] [+] }| diff --git a/test/functional/ui/linematch_spec.lua b/test/functional/ui/linematch_spec.lua index 3593604c49..dc16036f21 100644 --- a/test/functional/ui/linematch_spec.lua +++ b/test/functional/ui/linematch_spec.lua @@ -95,8 +95,7 @@ describe('Diff mode screen with 3 diffs open', function() {7: }{8: 9 }{4: BBB }│{7: }{8: 9 }{4: BBB }│{7: }{8: }{23:---------------------------}| {7: }{8: 10 }{4: BBB }│{7: }{8: 10 }{4: BBB }│{7: }{8: }{23:---------------------------}| {7: }{8: 11 }{4:>>>>>>> branch1 }│{7: }{8: 11 }{4:>>>>>>> branch1 }│{7: }{8: }{23:---------------------------}| - {7: }{8: 12 } │{7: }{8: 12 } │{7: }{8: 6 } | - {1:~ }│{1:~ }│{1:~ }|*2 + {1:~ }│{1:~ }│{1:~ }|*3 {3:<-functional-diff-screen-1.3 [+] }{2:<est-functional-diff-screen-1.2 Xtest-functional-diff-screen-1 }| :2,6diffget screen-1.2 | ]]) @@ -114,8 +113,7 @@ describe('Diff mode screen with 3 diffs open', function() {7: }{8: 4 }{4: }{27:BBB}{4: }│{7: }{8: 6 }{4: }{27:BBB}{4: }│{7: }{8: 4 }{4: }{27:AAA}{4: }| {7: }{8: 5 }{4: }{27:BBB}{4: }│{7: }{8: 7 }{4: }{27:BBB}{4: }│{7: }{8: 5 }{4: }{27:AAA}{4: }| {7: }{8: }{23:---------------------------}│{7: }{8: 8 }{22:>>>>>>> branch1 }│{7: }{8: }{23:---------------------------}| - {7: }{8: 6 } │{7: }{8: 9 } │{7: }{8: 6 } | - {1:~ }│{1:~ }│{1:~ }|*5 + {1:~ }│{1:~ }│{1:~ }|*6 {2:<test-functional-diff-screen-1.3 }{3:<functional-diff-screen-1.2 [+] }{2:Xtest-functional-diff-screen-1 }| :5,7diffget screen-1.3 | ]]) @@ -136,8 +134,7 @@ describe('Diff mode screen with 3 diffs open', function() {7: }{8: 4 } BBB │{7: }{8: 9 } BBB │{7: }{8: 8 } BBB | {7: }{8: 5 } BBB │{7: }{8: 10 } BBB │{7: }{8: 9 } BBB | {7: }{8: }{23:---------------------------}│{7: }{8: 11 }{4:>>>>>>> branch1 }│{7: }{8: 10 }{4:>>>>>>> branch1 }| - {7: }{8: 6 } │{7: }{8: 12 } │{7: }{8: 11 } | - {1:~ }│{1:~ }│{1:~ }|*2 + {1:~ }│{1:~ }│{1:~ }|*3 {2:<test-functional-diff-screen-1.3 <est-functional-diff-screen-1.2 }{3:<st-functional-diff-screen-1 [+] }| :5,6diffget screen-1.2 | ]]) @@ -158,8 +155,7 @@ describe('Diff mode screen with 3 diffs open', function() {7: }{8: 4 }{4: BBB }│{7: }{8: 9 }{4: BBB }│{7: }{8: }{23:---------------------------}| {7: }{8: 5 } BBB │{7: }{8: 10 } BBB │{7: }{8: 7 } BBB | {7: }{8: }{23:---------------------------}│{7: }{8: 11 }{22:>>>>>>> branch1 }│{7: }{8: }{23:---------------------------}| - {7: }{8: 6 } │{7: }{8: 12 } │{7: }{8: 8 } | - {1:~ }│{1:~ }│{1:~ }|*2 + {1:~ }│{1:~ }│{1:~ }|*3 {2:<test-functional-diff-screen-1.3 }{3:<est-functional-diff-screen-1.2 }{2:<st-functional-diff-screen-1 [+] }| :6,8diffput screen-1 | ]]) @@ -179,8 +175,7 @@ describe('Diff mode screen with 3 diffs open', function() {7: }{8: 4 } BBB │{7: }{8: 9 } BBB │{7: }{8: 8 } BBB | {7: }{8: 5 } BBB │{7: }{8: 10 } BBB │{7: }{8: 9 } BBB | {7: }{8: }{23:---------------------------}│{7: }{8: 11 }{4:>>>>>>> branch1 }│{7: }{8: 10 }{4:>>>>>>> branch1 }| - {7: }{8: 6 } │{7: }{8: 12 } │{7: }{8: 11 } | - {1:~ }│{1:~ }│{1:~ }|*2 + {1:~ }│{1:~ }│{1:~ }|*3 {2:<test-functional-diff-screen-1.3 }{3:<est-functional-diff-screen-1.2 }{2:<st-functional-diff-screen-1 [+] }| :6,11diffput screen-1 | ]]) @@ -276,8 +271,7 @@ something {7: }{8: 14 }common line │{7: }{8: 15 }common line | {7: }{8: }{23:-------------------------------------------}│{7: }{8: 16 }{22:DEF }| {7: }{8: 15 }something │{7: }{8: 17 }something | - {7: }{8: 16 } │{7: }{8: 18 } | - {1:~ }│{1:~ }|*6 + {1:~ }│{1:~ }|*7 {3:Xtest-functional-diff-screen-1.2 [+] }{2:Xtest-functional-diff-screen-1 }| :5,9diffget | ]]) @@ -300,8 +294,7 @@ something {7: }{8: 11 }common line │{7: }{8: 12 }common line | {7: }{8: }{23:-------------------------------------------}│{7: }{8: 13 }{22:DEF }| {7: }{8: 12 }something │{7: }{8: 14 }something | - {7: }{8: 13 } │{7: }{8: 15 } | - {1:~ }│{1:~ }|*3 + {1:~ }│{1:~ }|*4 {2:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| :5,10diffget | ]]) @@ -322,8 +315,7 @@ something {7: }{8: 10 }common line │{7: }{8: 10 }common line | {7: }{8: 11 }common line │{7: }{8: 11 }common line | {7: }{8: 12 }something │{7: }{8: 12 }something | - {7: }{8: 13 } │{7: }{8: 13 } | - {1:~ }│{1:~ }|*5 + {1:~ }│{1:~ }|*6 {2:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| :4,17diffget | ]]) @@ -349,7 +341,7 @@ something {7: }{8: 15 }common line │{7: }{8: 15 }common line | {7: }{8: 16 }DEF │{7: }{8: 16 }DEF | {7: }{8: 17 }something │{7: }{8: 17 }something | - {7: }{8: 18 } │{7: }{8: 18 } | + {1:~ }│{1:~ }| {3:Xtest-functional-diff-screen-1.2 [+] }{2:Xtest-functional-diff-screen-1 }| :4,12diffget | ]]) @@ -376,7 +368,7 @@ something {7: }{8: 11 }common line │{7: }{8: 15 }common line | {7: }{8: }{23:-------------------------------------------}│{7: }{8: 16 }{22:DEF }| {7: }{8: 12 }something │{7: }{8: 17 }something | - {7: }{8: 13 } │{7: }{8: 18 } | + {1:~ }│{1:~ }| {3:Xtest-functional-diff-screen-1.2 [+] }{2:Xtest-functional-diff-screen-1 }| :e | ]]) @@ -403,7 +395,7 @@ something {7: }{8: 11 }common line │{7: }{8: 15 }common line | {7: }{8: }{23:-------------------------------------------}│{7: }{8: 16 }{22:DEF }| {7: }{8: 12 }something │{7: }{8: 17 }something | - {7: }{8: 13 } │{7: }{8: 18 } | + {1:~ }│{1:~ }| {3:Xtest-functional-diff-screen-1.2 [+] }{2:Xtest-functional-diff-screen-1 }| :e | ]]) @@ -430,7 +422,7 @@ something {7: }{8: 11 }common line │{7: }{8: 15 }common line | {7: }{8: }{23:-------------------------------------------}│{7: }{8: 16 }{22:DEF }| {7: }{8: 12 }something │{7: }{8: 17 }something | - {7: }{8: 13 } │{7: }{8: 18 } | + {1:~ }│{1:~ }| {3:Xtest-functional-diff-screen-1.2 [+] }{2:Xtest-functional-diff-screen-1 }| :e | ]]) @@ -457,7 +449,7 @@ something {7: }{8: 12 }^common line │{7: }{8: 15 }common line | {7: }{8: }{23:-------------------------------------------}│{7: }{8: 16 }{22:DEF }| {7: }{8: 13 }something │{7: }{8: 17 }something | - {7: }{8: 14 } │{7: }{8: 18 } | + {1:~ }│{1:~ }| {3:Xtest-functional-diff-screen-1.2 [+] }{2:Xtest-functional-diff-screen-1 }| :e | ]]) @@ -484,7 +476,7 @@ something {7: }{8: 11 }common line │{7: }{8: 15 }common line | {7: }{8: 12 }DEF │{7: }{8: 16 }DEF | {7: }{8: 13 }^something │{7: }{8: 17 }something | - {7: }{8: 14 } │{7: }{8: 18 } | + {1:~ }│{1:~ }| {3:Xtest-functional-diff-screen-1.2 [+] }{2:Xtest-functional-diff-screen-1 }| :e | ]]) @@ -511,7 +503,7 @@ something {7: }{8: 11 }common line │{7: }{8: 15 }common line | {7: }{8: }{23:-------------------------------------------}│{7: }{8: 16 }{22:DEF }| {7: }{8: 12 }something │{7: }{8: 17 }something | - {7: }{8: 13 } │{7: }{8: 18 } | + {1:~ }│{1:~ }| {3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 [+] }| :e | ]]) @@ -538,7 +530,7 @@ something {7: }{8: 11 }common line │{7: }{8: 15 }common line | {7: }{8: }{23:-------------------------------------------}│{7: }{8: 16 }{22:DEF }| {7: }{8: 12 }something │{7: }{8: 17 }something | - {7: }{8: 13 } │{7: }{8: 18 } | + {1:~ }│{1:~ }| {3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 [+] }| :e | ]]) @@ -565,7 +557,7 @@ something {7: }{8: 11 }common line │{7: }{8: 15 }common line | {7: }{8: }{23:-------------------------------------------}│{7: }{8: 16 }{22:DEF }| {7: }{8: 12 }something │{7: }{8: 17 }something | - {7: }{8: 13 } │{7: }{8: 18 } | + {1:~ }│{1:~ }| {3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 [+] }| :e | ]]) @@ -591,7 +583,7 @@ something {7: }{8: 11 }^common line │{7: }{8: 14 }common line | {7: }{8: }{23:-------------------------------------------}│{7: }{8: 15 }{22:DEF }| {7: }{8: 12 }something │{7: }{8: 16 }something | - {7: }{8: 13 } │{7: }{8: 17 } | + {1:~ }│{1:~ }| {1:~ }│{1:~ }| {3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 [+] }| :e | @@ -618,7 +610,7 @@ something {7: }{8: }{23:-------------------------------------------}│{7: }{8: 14 }{22:DEF }| {7: }{8: 11 }common line │{7: }{8: 15 }common line | {7: }{8: 12 }^something │{7: }{8: 16 }something | - {7: }{8: 13 } │{7: }{8: 17 } | + {1:~ }│{1:~ }| {1:~ }│{1:~ }| {3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 [+] }| :e | @@ -646,7 +638,7 @@ something {7: }{8: 14 }common line │{7: }{8: 15 }common line | {7: }{8: }{23:-------------------------------------------}│{7: }{8: 16 }{22:DEF }| {7: }{8: 15 }something │{7: }{8: 17 }something | - {7: }{8: 16 } │{7: }{8: 18 } | + {1:~ }│{1:~ }| {2:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| :e | ]]) @@ -673,7 +665,7 @@ something {7: }{8: 14 }common line │{7: }{8: 15 }common line | {7: }{8: }{23:-------------------------------------------}│{7: }{8: 16 }{22:DEF }| {7: }{8: 15 }something │{7: }{8: 17 }something | - {7: }{8: 16 } │{7: }{8: 18 } | + {1:~ }│{1:~ }| {2:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| :e | ]]) @@ -700,7 +692,7 @@ something {7: }{8: 11 }common line │{7: }{8: 15 }common line | {7: }{8: }{23:-------------------------------------------}│{7: }{8: 16 }{22:DEF }| {7: }{8: 12 }something │{7: }{8: 17 }something | - {7: }{8: 13 } │{7: }{8: 18 } | + {1:~ }│{1:~ }| {2:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| :e | ]]) @@ -727,7 +719,7 @@ something {7: }{8: 11 }common line │{7: }{8: 15 }common line | {7: }{8: 12 }DEF │{7: }{8: 16 }DEF | {7: }{8: 13 }something │{7: }{8: 17 }^something | - {7: }{8: 14 } │{7: }{8: 18 } | + {1:~ }│{1:~ }| {2:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| :e | ]]) @@ -757,8 +749,7 @@ d {7: }{8: 2 }{4:abc d }│{7: }{8: 1 }{27:// }{4:abc d }| {7: }{8: 3 }{4:d }│{7: }{8: 2 }{27:// }{4:d }| {7: }{8: }{23:-------------------------------------------}│{7: }{8: 3 }{22:// d }| - {7: }{8: 4 } │{7: }{8: 4 } | - {1:~ }│{1:~ }|*13 + {1:~ }│{1:~ }|*14 {3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }| :e | ]]) @@ -794,8 +785,7 @@ void testFunction () { {7: }{8: 3 }{4: }{27:// }{4:} }│{7: }{8: 4 }{4: } }| {7: }{8: }{23:-------------------------------------------}│{7: }{8: 5 }{22: } }| {7: }{8: 4 }} │{7: }{8: 6 }} | - {7: }{8: 5 } │{7: }{8: 7 } | - {1:~ }│{1:~ }|*11 + {1:~ }│{1:~ }|*12 {3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }| :e | ]]) @@ -834,8 +824,7 @@ void testFunction () { {7: }{8: 6 }{22:?B }│{7: }{8: }{23:--------------------------------------------}| {7: }{8: 7 }{22:?B }│{7: }{8: }{23:--------------------------------------------}| {7: }{8: 8 }{22:?C }│{7: }{8: }{23:--------------------------------------------}| - {7: }{8: 9 } │{7: }{8: 4 } | - {1:~ }│{1:~ }|*9 + {1:~ }│{1:~ }|*10 {3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }| :e | ]]) @@ -874,8 +863,7 @@ void testFunction () { {7: }{8: 6 }{27:?}{4:B }│{7: }{8: 2 }{27:!}{4:B }| {7: }{8: 7 }{27:?}{4:C }│{7: }{8: 3 }{27:!}{4:C }| {7: }{8: 8 }{22:?C }│{7: }{8: }{23:--------------------------------------------}| - {7: }{8: 9 } │{7: }{8: 4 } | - {1:~ }│{1:~ }|*9 + {1:~ }│{1:~ }|*10 {3:Xtest-functional-diff-screen-1.2 }{2:Xtest-functional-diff-screen-1 }| :e | ]]) @@ -1017,8 +1005,7 @@ something {7: }{8: 9 }HIL │{7: }{8: 9 }HIL | {7: }{8: 10 }common line │{7: }{8: 10 }common line | {7: }{8: 11 }something │{7: }{8: 11 }something | - {7: }{8: 12 } │{7: }{8: 12 } | - {1:~ }│{1:~ }|*6 + {1:~ }│{1:~ }|*7 {3:Xtest-functional-diff-screen-1.2 [+] }{2:Xtest-functional-diff-screen-1 }| :1,19diffget | ]]) diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index fe093b67d5..375bc560dc 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -2318,25 +2318,26 @@ describe('builtin popupmenu', function() command('set completeopt+=noinsert') command('set mouse=a') insert([[ - Lorem ipsum dolor sit amet, consectetur - adipisicing elit, sed do eiusmod tempor - incididunt ut labore et dolore magna aliqua. - Ut enim ad minim veniam, quis nostrud - exercitation ullamco laboris nisi ut aliquip ex - ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum - dolore eu fugiat nulla pariatur. Excepteur sint - occaecat cupidatat non proident, sunt in culpa - qui officia deserunt mollit anim id est - laborum. + Lorem ipsum dolor sit amet, consectetur + adipisicing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua. + Ut enim ad minim veniam, quis nostrud + exercitation ullamco laboris nisi ut aliquip ex + ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum + dolore eu fugiat nulla pariatur. Excepteur sint + occaecat cupidatat non proident, sunt in culpa + qui officia deserunt mollit anim id est + laborum. + . ]]) screen:expect([[ - reprehenderit in voluptate velit esse cillum | dolore eu fugiat nulla pariatur. Excepteur sint | occaecat cupidatat non proident, sunt in culpa | qui officia deserunt mollit anim id est | laborum. | + . | ^ | {4:[No Name] [+] }| Lorem ipsum dolor sit amet, consectetur | diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 934b4e9032..adc3080586 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -494,9 +494,8 @@ function Screen:expect(expected, attr_ids, ...) local expected_rows = {} --- @type string[] if grid then - -- Remove the last line and dedent. Note that gsub returns more then one - -- value. - grid = dedent(grid:gsub('\n[ ]+$', ''), 0) + -- Dedent (ignores last line if it is blank). + grid = dedent(grid, 0) for row in grid:gmatch('[^\n]+') do table.insert(expected_rows, row) end diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua index 86490b4527..5479cf6ee6 100644 --- a/test/functional/ui/searchhl_spec.lua +++ b/test/functional/ui/searchhl_spec.lua @@ -73,10 +73,10 @@ describe('search highlighting', function() -- 'hlsearch' is enabled by default. #2859 feed('gg/text<cr>') screen:expect([[ - some {2:^text} | - more {2:text}stuff | - stupid{2:texttext}stuff | - a {2:text} word | + some {2:^text} | + more {2:text}stuff | + stupid{2:texttext}stuff | + a {2:text} word | | {1:~ }| /text | @@ -85,10 +85,10 @@ describe('search highlighting', function() -- overlapping matches not allowed feed('3nx') screen:expect([[ - some {2:text} | - more {2:text}stuff | - stupid{2:text}^extstuff | - a {2:text} word | + some {2:text} | + more {2:text}stuff | + stupid{2:text}^extstuff | + a {2:text} word | | {1:~ }| /text | @@ -96,10 +96,10 @@ describe('search highlighting', function() feed('ggn*') -- search for entire word screen:expect([[ - some {2:text} | - more textstuff | - stupidtextextstuff | - a {2:^text} word | + some {2:text} | + more textstuff | + stupidtextextstuff | + a {2:^text} word | | {1:~ }| /\<text\> | @@ -107,10 +107,10 @@ describe('search highlighting', function() feed_command('nohlsearch') screen:expect([[ - some text | - more textstuff | - stupidtextextstuff | - a ^text word | + some text | + more textstuff | + stupidtextextstuff | + a ^text word | | {1:~ }| :nohlsearch | @@ -641,7 +641,7 @@ describe('search highlighting', function() feed_command('/ial te') screen:expect { grid = [[ - very {5:spec^ial}{2: te}{6:xt} | + very {5:spec^ial}{2: te}{6:xt} | | {1:~ }|*4 {4:search hit BOTTOM, continuing at TOP} | @@ -652,7 +652,7 @@ describe('search highlighting', function() topline = 0, botline = 3, curline = 0, - curcol = 11, + curcol = 9, linecount = 2, sum_scroll_delta = 0, }, @@ -670,7 +670,7 @@ describe('search highlighting', function() } command('%foldopen') screen:expect([[ - very {5:spec^ial}{2: te}{6:xt} | + very {5:spec^ial}{2: te}{6:xt} | | {1:~ }|*4 {4:search hit BOTTOM, continuing at TOP} | @@ -678,7 +678,7 @@ describe('search highlighting', function() feed_command('call clearmatches()') screen:expect([[ - very spec{2:^ial te}xt | + very spec{2:^ial te}xt | | {1:~ }|*4 :call clearmatches() | @@ -688,7 +688,7 @@ describe('search highlighting', function() -- nonconflicting attributes are combined feed_command('syntax keyword MyGroup special') screen:expect([[ - very {5:spec}{7:^ial}{2: te}xt | + very {5:spec}{7:^ial}{2: te}xt | | {1:~ }|*4 :syntax keyword MyGroup special | diff --git a/test/testutil.lua b/test/testutil.lua index e69dcae120..3655a87d93 100644 --- a/test/testutil.lua +++ b/test/testutil.lua @@ -148,6 +148,7 @@ end --- @param actual string --- @return boolean function M.matches(pat, actual) + assert(pat and pat ~= '', 'pat must be a non-empty string') if nil ~= string.match(actual, pat) then return true end @@ -641,28 +642,9 @@ end --- @param leave_indent? integer --- @return string function M.dedent(str, leave_indent) - -- find minimum common indent across lines - local indent --- @type string? - for line in str:gmatch('[^\n]+') do - local line_indent = line:match('^%s+') or '' - if indent == nil or #line_indent < #indent then - indent = line_indent - end - end - - if not indent or #indent == 0 then - -- no minimum common indent - return str - end - - local left_indent = (' '):rep(leave_indent or 0) - -- create a pattern for the indent - indent = indent:gsub('%s', '[ \t]') - -- strip it from the first line - str = str:gsub('^' .. indent, left_indent) - -- strip it from the remaining lines - str = str:gsub('[\n]' .. indent, '\n' .. left_indent) - return str + -- Last blank line often has non-matching indent, so remove it. + str = str:gsub('\n[ ]+$', '\n') + return (vim.text.indent(leave_indent or 0, str)) end function M.intchar2lua(ch) |