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* 4.3 Changing tabs *change-tabs*
*:ret* *:retab* *:retab!* *:ret* *:retab* *:retab!*
:[range]ret[ab][!] [new_tabstop] :[range]ret[ab][!] [-indentonly] [{new-tabstop}]
Replace all sequences of white-space containing a Replace all sequences of white-space containing a
<Tab> with new strings of white-space using the new <Tab> with new strings of white-space using
tabstop value given. If you do not specify a new {new-tabstop}. If you do not specify {new-tabstop} or
tabstop size or it is zero, Vim uses the current value it is zero, Vim uses the current value of 'tabstop'.
of 'tabstop'.
The current value of 'tabstop' is always used to The current value of 'tabstop' is always used to
compute the width of existing tabs. compute the width of existing tabs.
With !, Vim also replaces strings of only normal With !, Vim also replaces strings of only normal
spaces with tabs where appropriate. spaces with tabs where appropriate.
With 'expandtab' on, Vim replaces all tabs with the With 'expandtab' on, Vim replaces all tabs with the
appropriate number of spaces. appropriate number of spaces.
This command sets 'tabstop' to the new value given, This command sets 'tabstop' to {new-tabstop} and if
and if performed on the whole file, which is default, performed on the whole file, which is default, should
should not make any visible change. not make any visible change.
Careful: This command modifies any <Tab> characters
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 inside of strings in a C program. Use "\t" to avoid
this (that's a good habit anyway). this (that's a good habit anyway).
`:retab!` may also change a sequence of spaces by `:retab!` may also change a sequence of spaces by

View File

@@ -1410,6 +1410,7 @@ completion can be enabled:
-complete=messages |:messages| suboptions -complete=messages |:messages| suboptions
-complete=option options -complete=option options
-complete=packadd optional package |pack-add| names -complete=packadd optional package |pack-add| names
-complete=retab |:retab| suboptions
-complete=runtime file and directory names in |'runtimepath'| -complete=runtime file and directory names in |'runtimepath'|
-complete=scriptnames sourced script names -complete=scriptnames sourced script names
-complete=shellcmd Shell command -complete=shellcmd Shell command

View File

@@ -161,6 +161,8 @@ DIAGNOSTICS
EDITOR EDITOR
• |:iput| works like |:put| but adjusts indent. • |: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. • |:uniq| deduplicates text in the current buffer.
• |omnicompletion| in `help` buffer. |ft-help-omni| • |omnicompletion| in `help` buffer. |ft-help-omni|
• Setting "'0" in 'shada' prevents storing the jumplist in the shada file. • 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 - 'langremap' is disabled
- 'laststatus' defaults to 2 (statusline is always shown) - 'laststatus' defaults to 2 (statusline is always shown)
- 'listchars' defaults to "tab:> ,trail:-,nbsp:+" - 'listchars' defaults to "tab:> ,trail:-,nbsp:+"
- 'maxsearchcount' defaults t0 999 - 'maxsearchcount' defaults to 999
- 'mouse' defaults to "nvi", see |default-mouse| for details - 'mouse' defaults to "nvi", see |default-mouse| for details
- 'mousemodel' defaults to "popup_setpos" - 'mousemodel' defaults to "popup_setpos"
- 'nrformats' defaults to "bin,hex" - 'nrformats' defaults to "bin,hex"

View File

@@ -3469,6 +3469,7 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()*
messages |:messages| suboptions messages |:messages| suboptions
option options option options
packadd optional package |pack-add| names packadd optional package |pack-add| names
retab |:retab| suboptions
runtime |:runtime| completion runtime |:runtime| completion
scriptnames sourced script names |:scriptnames| scriptnames sourced script names |:scriptnames|
shellcmd Shell command shellcmd Shell command

View File

@@ -3113,6 +3113,7 @@ function vim.fn.getcmdwintype() end
--- messages |:messages| suboptions --- messages |:messages| suboptions
--- option options --- option options
--- packadd optional package |pack-add| names --- packadd optional package |pack-add| names
--- retab |:retab| suboptions
--- runtime |:runtime| completion --- runtime |:runtime| completion
--- scriptnames sourced script names |:scriptnames| --- scriptnames sourced script names |:scriptnames|
--- shellcmd Shell command --- 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] 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='' " 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 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 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 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; xp->xp_pattern = (char *)arg;
break; break;
case CMD_retab:
xp->xp_context = EXPAND_RETAB;
xp->xp_pattern = (char *)arg;
break;
case CMD_messages: case CMD_messages:
xp->xp_context = EXPAND_MESSAGES; xp->xp_context = EXPAND_MESSAGES;
xp->xp_pattern = (char *)arg; xp->xp_pattern = (char *)arg;
@@ -2768,6 +2773,16 @@ static char *get_scriptnames_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
return NameBuff; 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 /// Function given to ExpandGeneric() to obtain the possible arguments of the
/// ":messages {clear}" command. /// ":messages {clear}" command.
static char *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) 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_ARGLIST, get_arglist_name, true, false },
{ EXPAND_BREAKPOINT, get_breakadd_arg, true, true }, { EXPAND_BREAKPOINT, get_breakadd_arg, true, true },
{ EXPAND_SCRIPTNAMES, get_scriptnames_arg, true, false }, { EXPAND_SCRIPTNAMES, get_scriptnames_arg, true, false },
{ EXPAND_RETAB, get_retab_arg, true, true },
{ EXPAND_CHECKHEALTH, get_healthcheck_names, true, false }, { EXPAND_CHECKHEALTH, get_healthcheck_names, true, false },
}; };
int ret = FAIL; int ret = FAIL;

View File

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

View File

@@ -3901,6 +3901,7 @@ M.funcs = {
messages |:messages| suboptions messages |:messages| suboptions
option options option options
packadd optional package |pack-add| names packadd optional package |pack-add| names
retab |:retab| suboptions
runtime |:runtime| completion runtime |:runtime| completion
scriptnames sourced script names |:scriptnames| scriptnames sourced script names |:scriptnames|
shellcmd Shell command 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 first_line = 0; // first changed line
linenr_T last_line = 0; // last changed line linenr_T last_line = 0; // last changed line
bool is_indent_only = false;
int save_list = curwin->w_p_list; int save_list = curwin->w_p_list;
curwin->w_p_list = 0; // don't want list mode here curwin->w_p_list = 0; // don't want list mode here
new_ts_str = eap->arg; char *ptr = eap->arg;
if (!tabstop_set(eap->arg, &new_vts_array)) { 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; return;
} }
while (ascii_isdigit(*(eap->arg)) || *(eap->arg) == ',') { while (ascii_isdigit(*ptr) || *ptr == ',') {
eap->arg++; ptr++;
} }
// This ensures that either new_vts_array and new_ts_str are freshly // 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_vts_array = curbuf->b_p_vts_array;
new_ts_str = NULL; new_ts_str = NULL;
} else { } 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++) { 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 old_len = ml_get_len(lnum);
int col = 0; int col = 0;
int64_t vcol = 0; int64_t vcol = 0;
@@ -1106,6 +1113,10 @@ void ex_retab(exarg_T *eap)
} }
got_tab = false; got_tab = false;
num_spaces = 0; num_spaces = 0;
if (is_indent_only) {
break;
}
} }
if (ptr[col] == NUL) { if (ptr[col] == NUL) {
break; break;

View File

@@ -90,6 +90,7 @@ static const char *command_complete[] = {
[EXPAND_SYNTIME] = "syntime", [EXPAND_SYNTIME] = "syntime",
[EXPAND_SETTINGS] = "option", [EXPAND_SETTINGS] = "option",
[EXPAND_PACKADD] = "packadd", [EXPAND_PACKADD] = "packadd",
[EXPAND_RETAB] = "retab",
[EXPAND_RUNTIME] = "runtime", [EXPAND_RUNTIME] = "runtime",
[EXPAND_SHELLCMD] = "shellcmd", [EXPAND_SHELLCMD] = "shellcmd",
[EXPAND_SHELLCMDLINE] = "shellcmdline", [EXPAND_SHELLCMDLINE] = "shellcmdline",

View File

@@ -626,6 +626,11 @@ func Test_getcompletion()
let l = getcompletion('not', 'mapclear') let l = getcompletion('not', 'mapclear')
call assert_equal([], l) 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') let l = getcompletion('.', 'shellcmd')
call assert_equal(['./', '../'], filter(l, 'v:val =~ "\\./"')) call assert_equal(['./', '../'], filter(l, 'v:val =~ "\\./"'))
call assert_equal(-1, match(l[2:], '^\.\.\?/$')) call assert_equal(-1, match(l[2:], '^\.\.\?/$'))
@@ -682,6 +687,8 @@ func Test_getcompletion()
call assert_equal([], l) call assert_equal([], l)
let l = getcompletion('autocmd BufEnter * map <bu', 'cmdline') let l = getcompletion('autocmd BufEnter * map <bu', 'cmdline')
call assert_equal(['<buffer>'], l) call assert_equal(['<buffer>'], l)
let l = getcompletion('retab! ', 'cmdline')
call assert_true(index(l, '-indentonly') >= 0)
func T(a, c, p) func T(a, c, p)
let g:cmdline_compl_params = [a:a, a:c, a:p] let g:cmdline_compl_params = [a:a, a:c, a:p]

View File

@@ -11,10 +11,13 @@ func TearDown()
bwipe! bwipe!
endfunc endfunc
func Retab(bang, n) func Retab(bang, n, subopt='', test_line='')
let l:old_tabstop = &tabstop let l:old_tabstop = &tabstop
let l:old_line = getline(1) 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) let l:line = getline(1)
call setline(1, l:old_line) call setline(1, l:old_line)
if a:n > 0 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))
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& set tabstop& expandtab&
endfunc endfunc
@@ -82,6 +149,9 @@ func Test_retab_error()
call assert_fails('ret -1000', 'E487:') call assert_fails('ret -1000', 'E487:')
call assert_fails('ret 10000', 'E475:') call assert_fails('ret 10000', 'E475:')
call assert_fails('ret 80000000000000000000', '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 endfunc
func RetabLoop() func RetabLoop()

View File

@@ -406,6 +406,10 @@ func Test_CmdCompletion()
" call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx') " call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx')
" call assert_equal('"DoCmd mswin xterm', @:) " 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 " Test for file name completion
com! -nargs=1 -complete=file DoCmd : com! -nargs=1 -complete=file DoCmd :
call feedkeys(":DoCmd READM\<Tab>\<C-B>\"\<CR>", 'tx') call feedkeys(":DoCmd READM\<Tab>\<C-B>\"\<CR>", 'tx')