diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index 742c1b4a08..af186e5643 100644 --- 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 ||. 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 - 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'. + 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 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 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 index 52c04333fc..c40d9d86fa 100644 --- 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 index dc8a87d6d4..68b426482f 100644 --- 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 index 06926c9052..b001361c41 100644 --- 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 index f107291ab5..9bae87e29b 100644 --- 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 index e5f0bdf014..490a414728 100644 --- 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 index 2edc49a9da..928d3cf46f 100644 --- 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 index a9476de67b..7875680ac6 100644 --- 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; @@ -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; diff --git a/src/nvim/cmdexpand_defs.h b/src/nvim/cmdexpand_defs.h index e6a7429ff1..454160e696 100644 --- 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 index 8e6b82e311..5b3606c416 100644 --- 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 index 7a537e1946..399e5afbb8 100644 --- 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 index 48f716a982..70281ac132 100644 --- 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 index a2ecc57388..3e8592a7d8 100644 --- 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 '], 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 index a4f95053c0..1c08044410 100644 --- 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 index 046e0b1874..d3f87ea924 100644 --- a/test/old/testdir/test_usercommands.vim +++ b/test/old/testdir/test_usercommands.vim @@ -406,6 +406,10 @@ func Test_CmdCompletion() " call feedkeys(":DoCmd \\\"\", 'tx') " call assert_equal('"DoCmd mswin xterm', @:) + com! -nargs=1 -complete=retab DoCmd : + call feedkeys(":DoCmd \\\"\", 'tx') + call assert_equal('"DoCmd -indentonly', @:) + " Test for file name completion com! -nargs=1 -complete=file DoCmd : call feedkeys(":DoCmd READM\\\"\", 'tx')