neovim

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

commit 7631302ad620ee89368002cc882a90af1e4ddd68
parent 4fe6fcc5b728a1e88fb5da6f1d3c8ed855348d3c
Author: zeertzjq <zeertzjq@outlook.com>
Date:   Fri, 18 Jul 2025 09:30:32 +0800

vim-patch:9.1.1544: :retab cannot be limited to indentation only (#34939)

Problem:  :retab cannot be limited to indentation only
Solution: add the optional -indentonly parameter
          (Hirohito Higashi)

closes: vim/vim#17730

https://github.com/vim/vim/commit/836e54f5de8479a39f6d1dd4a13202af0e447311

Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Diffstat:
Mruntime/doc/change.txt | 22+++++++++++++---------
Mruntime/doc/map.txt | 1+
Mruntime/doc/news.txt | 2++
Mruntime/doc/vim_diff.txt | 2+-
Mruntime/doc/vimfn.txt | 1+
Mruntime/lua/vim/_meta/vimfn.lua | 1+
Mruntime/syntax/vim.vim | 2+-
Msrc/nvim/cmdexpand.c | 16++++++++++++++++
Msrc/nvim/cmdexpand_defs.h | 1+
Msrc/nvim/eval.lua | 1+
Msrc/nvim/indent.c | 23+++++++++++++++++------
Msrc/nvim/usercmd.c | 1+
Mtest/old/testdir/test_cmdline.vim | 7+++++++
Mtest/old/testdir/test_retab.vim | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mtest/old/testdir/test_usercommands.vim | 4++++
15 files changed, 139 insertions(+), 19 deletions(-)

diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt @@ -949,22 +949,26 @@ This replaces each 'E' character with a euro sign. Read more in |<Char->|. 4.3 Changing tabs *change-tabs* *:ret* *:retab* *:retab!* -:[range]ret[ab][!] [new_tabstop] +:[range]ret[ab][!] [-indentonly] [{new-tabstop}] Replace all sequences of white-space containing a - <Tab> with new strings of white-space using the new - tabstop value given. If you do not specify a new - tabstop size or it is zero, Vim uses the current value - of 'tabstop'. + <Tab> with new strings of white-space using + {new-tabstop}. If you do not specify {new-tabstop} or + it is zero, Vim uses the current value of 'tabstop'. The current value of 'tabstop' is always used to compute the width of existing tabs. With !, Vim also replaces strings of only normal spaces with tabs where appropriate. With 'expandtab' on, Vim replaces all tabs with the appropriate number of spaces. - This command sets 'tabstop' to the new value given, - and if performed on the whole file, which is default, - should not make any visible change. - Careful: This command modifies any <Tab> characters + This command sets 'tabstop' to {new-tabstop} and if + performed on the whole file, which is default, should + not make any visible change. + + When [-indentonly] is specified, only the leading + white-space will be targeted. Any other consecutive + white-space will not be changed. + + Warning: This command modifies any <Tab> characters inside of strings in a C program. Use "\t" to avoid this (that's a good habit anyway). `:retab!` may also change a sequence of spaces by diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt @@ -1410,6 +1410,7 @@ completion can be enabled: -complete=messages |:messages| suboptions -complete=option options -complete=packadd optional package |pack-add| names + -complete=retab |:retab| suboptions -complete=runtime file and directory names in |'runtimepath'| -complete=scriptnames sourced script names -complete=shellcmd Shell command diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt @@ -161,6 +161,8 @@ DIAGNOSTICS EDITOR • |:iput| works like |:put| but adjusts indent. +• |:retab| accepts new optional parameter -indentonly to only change leading + whitespace in indented lines. • |:uniq| deduplicates text in the current buffer. • |omnicompletion| in `help` buffer. |ft-help-omni| • Setting "'0" in 'shada' prevents storing the jumplist in the shada file. diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt @@ -69,7 +69,7 @@ Defaults *defaults* *nvim-defaults* - 'langremap' is disabled - 'laststatus' defaults to 2 (statusline is always shown) - 'listchars' defaults to "tab:> ,trail:-,nbsp:+" -- 'maxsearchcount' defaults t0 999 +- 'maxsearchcount' defaults to 999 - 'mouse' defaults to "nvi", see |default-mouse| for details - 'mousemodel' defaults to "popup_setpos" - 'nrformats' defaults to "bin,hex" diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt @@ -3469,6 +3469,7 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()* messages |:messages| suboptions option options packadd optional package |pack-add| names + retab |:retab| suboptions runtime |:runtime| completion scriptnames sourced script names |:scriptnames| shellcmd Shell command diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua @@ -3113,6 +3113,7 @@ function vim.fn.getcmdwintype() end --- messages |:messages| suboptions --- option options --- packadd optional package |pack-add| names +--- retab |:retab| suboptions --- runtime |:runtime| completion --- scriptnames sourced script names |:scriptnames| --- shellcmd Shell command diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim @@ -778,7 +778,7 @@ syn case ignore syn keyword vimUserCmdAttrKey contained a[ddr] ban[g] bar bu[ffer] com[plete] cou[nt] k[eepscript] n[args] ra[nge] re[gister] " GEN_SYN_VIM: vimUserCmdAttrComplete, START_STR='syn keyword vimUserCmdAttrComplete contained', END_STR='' -syn keyword vimUserCmdAttrComplete contained arglist augroup behave breakpoint buffer color command compiler cscope diff_buffer dir dir_in_path environment event expression file file_in_path filetype filetypecmd function help highlight history keymap locale mapclear mapping menu messages option packadd runtime scriptnames shellcmd shellcmdline sign syntax syntime tag tag_listfiles user var +syn keyword vimUserCmdAttrComplete contained arglist augroup behave breakpoint buffer color command compiler cscope diff_buffer dir dir_in_path environment event expression file file_in_path filetype filetypecmd function help highlight history keymap locale mapclear mapping menu messages option packadd retab runtime scriptnames shellcmd shellcmdline sign syntax syntime tag tag_listfiles user var syn keyword vimUserCmdAttrComplete contained arglist augroup behave breakpoint buffer color command compiler cscope diff_buffer dir dir_in_path environment event expression file file_in_path filetype function help highlight history keymap locale mapclear mapping menu messages option packadd runtime scriptnames shellcmd shellcmdline sign syntax syntime tag tag_listfiles user var syn keyword vimUserCmdAttrComplete contained custom customlist nextgroup=vimUserCmdAttrCompleteFunc,vimUserCmdError syn match vimUserCmdAttrCompleteFunc contained ",\%([bwglstav]:\|<[sS][iI][dD]>\)\=\h\w*\%([.#]\h\w*\)*"hs=s+1 nextgroup=vimUserCmdError contains=vimVarScope,vimFunctionSID diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c @@ -2290,6 +2290,11 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa xp->xp_pattern = (char *)arg; break; + case CMD_retab: + xp->xp_context = EXPAND_RETAB; + xp->xp_pattern = (char *)arg; + break; + case CMD_messages: xp->xp_context = EXPAND_MESSAGES; xp->xp_pattern = (char *)arg; @@ -2769,6 +2774,16 @@ static char *get_scriptnames_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) } /// Function given to ExpandGeneric() to obtain the possible arguments of the +/// ":retab {-indentonly}" option. +static char *get_retab_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx == 0) { + return "-indentonly"; + } + return NULL; +} + +/// Function given to ExpandGeneric() to obtain the possible arguments of the /// ":messages {clear}" command. static char *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) { @@ -2853,6 +2868,7 @@ static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches { EXPAND_ARGLIST, get_arglist_name, true, false }, { EXPAND_BREAKPOINT, get_breakadd_arg, true, true }, { EXPAND_SCRIPTNAMES, get_scriptnames_arg, true, false }, + { EXPAND_RETAB, get_retab_arg, true, true }, { EXPAND_CHECKHEALTH, get_healthcheck_names, true, false }, }; int ret = FAIL; diff --git a/src/nvim/cmdexpand_defs.h b/src/nvim/cmdexpand_defs.h @@ -114,6 +114,7 @@ enum { EXPAND_FINDFUNC, EXPAND_FILETYPECMD, EXPAND_PATTERN_IN_BUF, + EXPAND_RETAB, EXPAND_CHECKHEALTH, EXPAND_LUA, }; diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua @@ -3901,6 +3901,7 @@ M.funcs = { messages |:messages| suboptions option options packadd optional package |pack-add| names + retab |:retab| suboptions runtime |:runtime| completion scriptnames sourced script names |:scriptnames| shellcmd Shell command diff --git a/src/nvim/indent.c b/src/nvim/indent.c @@ -1006,16 +1006,23 @@ void ex_retab(exarg_T *eap) linenr_T first_line = 0; // first changed line linenr_T last_line = 0; // last changed line + bool is_indent_only = false; int save_list = curwin->w_p_list; curwin->w_p_list = 0; // don't want list mode here - new_ts_str = eap->arg; - if (!tabstop_set(eap->arg, &new_vts_array)) { + char *ptr = eap->arg; + if (strncmp(ptr, "-indentonly", 11) == 0 && ascii_iswhite_or_nul(ptr[11])) { + is_indent_only = true; + ptr = skipwhite(ptr + 11); + } + + new_ts_str = ptr; + if (!tabstop_set(ptr, &new_vts_array)) { return; } - while (ascii_isdigit(*(eap->arg)) || *(eap->arg) == ',') { - eap->arg++; + while (ascii_isdigit(*ptr) || *ptr == ',') { + ptr++; } // This ensures that either new_vts_array and new_ts_str are freshly @@ -1025,10 +1032,10 @@ void ex_retab(exarg_T *eap) new_vts_array = curbuf->b_p_vts_array; new_ts_str = NULL; } else { - new_ts_str = xmemdupz(new_ts_str, (size_t)(eap->arg - new_ts_str)); + new_ts_str = xmemdupz(new_ts_str, (size_t)(ptr - new_ts_str)); } for (linenr_T lnum = eap->line1; !got_int && lnum <= eap->line2; lnum++) { - char *ptr = ml_get(lnum); + ptr = ml_get(lnum); int old_len = ml_get_len(lnum); int col = 0; int64_t vcol = 0; @@ -1106,6 +1113,10 @@ void ex_retab(exarg_T *eap) } got_tab = false; num_spaces = 0; + + if (is_indent_only) { + break; + } } if (ptr[col] == NUL) { break; diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c @@ -90,6 +90,7 @@ static const char *command_complete[] = { [EXPAND_SYNTIME] = "syntime", [EXPAND_SETTINGS] = "option", [EXPAND_PACKADD] = "packadd", + [EXPAND_RETAB] = "retab", [EXPAND_RUNTIME] = "runtime", [EXPAND_SHELLCMD] = "shellcmd", [EXPAND_SHELLCMDLINE] = "shellcmdline", diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim @@ -626,6 +626,11 @@ func Test_getcompletion() let l = getcompletion('not', 'mapclear') call assert_equal([], l) + let l = getcompletion('', 'retab') + call assert_true(index(l, '-indentonly') >= 0) + let l = getcompletion('not', 'retab') + call assert_equal([], l) + let l = getcompletion('.', 'shellcmd') call assert_equal(['./', '../'], filter(l, 'v:val =~ "\\./"')) call assert_equal(-1, match(l[2:], '^\.\.\?/$')) @@ -682,6 +687,8 @@ func Test_getcompletion() call assert_equal([], l) let l = getcompletion('autocmd BufEnter * map <bu', 'cmdline') call assert_equal(['<buffer>'], l) + let l = getcompletion('retab! ', 'cmdline') + call assert_true(index(l, '-indentonly') >= 0) func T(a, c, p) let g:cmdline_compl_params = [a:a, a:c, a:p] diff --git a/test/old/testdir/test_retab.vim b/test/old/testdir/test_retab.vim @@ -11,10 +11,13 @@ func TearDown() bwipe! endfunc -func Retab(bang, n) +func Retab(bang, n, subopt='', test_line='') let l:old_tabstop = &tabstop let l:old_line = getline(1) - exe "retab" . a:bang . a:n + if a:test_line != '' + call setline(1, a:test_line) + endif + exe "retab" . a:bang . ' ' . a:subopt . ' ' . a:n let l:line = getline(1) call setline(1, l:old_line) if a:n > 0 @@ -73,6 +76,70 @@ func Test_retab() call assert_equal(" a b c ", Retab('', 5)) call assert_equal(" a b c ", Retab('!', 5)) + " Test with '-indentonly' + let so='-indentonly' + set tabstop=8 noexpandtab + call assert_equal("\ta \t b c ", Retab('', '', so)) + call assert_equal("\ta \t b c ", Retab('', 0, so)) + call assert_equal("\ta \t b c ", Retab('', 8, so)) + call assert_equal("\ta \t b c ", Retab('!', '', so)) + call assert_equal("\ta \t b c ", Retab('!', 0, so)) + call assert_equal("\ta \t b c ", Retab('!', 8, so)) + + call assert_equal("\t\ta \t b c ", Retab('', 4, so)) + call assert_equal("\t\ta \t b c ", Retab('!', 4, so)) + + call assert_equal(" a \t b c ", Retab('', 10, so)) + call assert_equal(" a \t b c ", Retab('!', 10, so)) + + set tabstop=8 expandtab + call assert_equal(" a \t b c ", Retab('', '', so)) + call assert_equal(" a \t b c ", Retab('', 0, so)) + call assert_equal(" a \t b c ", Retab('', 8, so)) + call assert_equal(" a \t b c ", Retab('!', '', so)) + call assert_equal(" a \t b c ", Retab('!', 0, so)) + call assert_equal(" a \t b c ", Retab('!', 8, so)) + + call assert_equal(" a \t b c ", Retab(' ', 4, so)) + call assert_equal(" a \t b c ", Retab('!', 4, so)) + + call assert_equal(" a \t b c ", Retab(' ', 10, so)) + call assert_equal(" a \t b c ", Retab('!', 10, so)) + + set tabstop=4 noexpandtab + call assert_equal("\ta \t b c ", Retab('', '', so)) + call assert_equal("\ta \t b c ", Retab('!', '', so)) + call assert_equal("\t a \t b c ", Retab('', 3, so)) + call assert_equal("\t a \t b c ", Retab('!', 3, so)) + call assert_equal(" a \t b c ", Retab('', 5, so)) + call assert_equal(" a \t b c ", Retab('!', 5, so)) + + set tabstop=4 expandtab + call assert_equal(" a \t b c ", Retab('', '', so)) + call assert_equal(" a \t b c ", Retab('!', '', so)) + call assert_equal(" a \t b c ", Retab('', 3, so)) + call assert_equal(" a \t b c ", Retab('!', 3, so)) + call assert_equal(" a \t b c ", Retab('', 5, so)) + call assert_equal(" a \t b c ", Retab('!', 5, so)) + + " Test for variations in leading whitespace + let so='-indentonly' + let test_line=" \t a\t " + set tabstop=8 noexpandtab + call assert_equal("\t a\t ", Retab('', '', so, test_line)) + call assert_equal("\t a\t ", Retab('!', '', so, test_line)) + set tabstop=8 expandtab + call assert_equal(" a\t ", Retab('', '', so, test_line)) + call assert_equal(" a\t ", Retab('!', '', so, test_line)) + + let test_line=" a\t " + set tabstop=8 noexpandtab + call assert_equal(test_line, Retab('', '', so, test_line)) + call assert_equal("\t a\t ", Retab('!', '', so, test_line)) + set tabstop=8 expandtab + call assert_equal(test_line, Retab('', '', so, test_line)) + call assert_equal(test_line, Retab('!', '', so, test_line)) + set tabstop& expandtab& endfunc @@ -82,6 +149,9 @@ func Test_retab_error() call assert_fails('ret -1000', 'E487:') call assert_fails('ret 10000', 'E475:') call assert_fails('ret 80000000000000000000', 'E475:') + call assert_fails('retab! -in', 'E475:') + call assert_fails('retab! -indentonly2', 'E475:') + call assert_fails('retab! -indentonlyx 0', 'E475:') endfunc func RetabLoop() diff --git a/test/old/testdir/test_usercommands.vim b/test/old/testdir/test_usercommands.vim @@ -406,6 +406,10 @@ func Test_CmdCompletion() " call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx') " call assert_equal('"DoCmd mswin xterm', @:) + com! -nargs=1 -complete=retab DoCmd : + call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"DoCmd -indentonly', @:) + " Test for file name completion com! -nargs=1 -complete=file DoCmd : call feedkeys(":DoCmd READM\<Tab>\<C-B>\"\<CR>", 'tx')