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

836e54f5de

Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
This commit is contained in:
zeertzjq
2025-07-18 09:30:32 +08:00
committed by GitHub
parent 4fe6fcc5b7
commit 7631302ad6
15 changed files with 139 additions and 19 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
@@ -2768,6 +2773,16 @@ static char *get_scriptnames_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
return NameBuff;
}
/// 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;

View File

@@ -114,6 +114,7 @@ enum {
EXPAND_FINDFUNC,
EXPAND_FILETYPECMD,
EXPAND_PATTERN_IN_BUF,
EXPAND_RETAB,
EXPAND_CHECKHEALTH,
EXPAND_LUA,
};

View File

@@ -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

View File

@@ -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;

View File

@@ -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",

View File

@@ -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]

View File

@@ -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()

View File

@@ -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')