diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index f55ec62b77..f0ce15ac0f 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1038,7 +1038,12 @@ A jump table for the options with a short description can be found at |Q_op|. continuation (positive). sbr Display the 'showbreak' value before applying the additional indent. - The default value for min is 20 and shift is 0. + list:{n} Adds an additional indent for lines that match a + numbered or bulleted list (using the + 'formatlistpat' setting). + list:-1 Uses the length of a match with 'formatlistpat' + for indentation. + The default value for min is 20, shift and list is 0. *'browsedir'* *'bsdir'* 'browsedir' 'bsdir' string (default: "last") diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 3db6892b71..1247e48c5f 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1395,6 +1395,7 @@ struct window_S { int w_briopt_min; // minimum width for breakindent int w_briopt_shift; // additional shift for breakindent bool w_briopt_sbr; // sbr in 'briopt' + int w_briopt_list; // additional indent for lists // transform a pointer to a "onebuf" option into a "allbuf" option #define GLOBAL_WO(p) ((char *)p + sizeof(winopt_T)) diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index d10ecf5c7f..3afcd9ec5a 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5514,6 +5514,9 @@ invalid_count: return OK; } +static char e_complete_used_without_nargs[] = N_( + "E1208: -complete used without -nargs"); + /* * ":command ..." */ @@ -5565,10 +5568,10 @@ static void ex_command(exarg_T *eap) uc_list(name, end - name); } else if (!ASCII_ISUPPER(*name)) { EMSG(_("E183: User defined commands must start with an uppercase letter")); - return; } else if (name_len <= 4 && STRNCMP(name, "Next", name_len) == 0) { EMSG(_("E841: Reserved name, cannot be used for user defined command")); - return; + } else if (compl > 0 && (argt & EX_EXTRA) == 0) { + EMSG(_(e_complete_used_without_nargs)); } else { uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg, addr_type_arg, eap->forceit); diff --git a/src/nvim/indent.c b/src/nvim/indent.c index c1f9c1e172..bfb77688b0 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -432,7 +432,7 @@ int get_number_indent(linenr_T lnum) // Return appropriate space number for breakindent, taking influencing // parameters into account. Window must be specified, since it is not // necessarily always the current one. -int get_breakindent_win(win_T *wp, const char_u *line) +int get_breakindent_win(win_T *wp, char_u *line) FUNC_ATTR_NONNULL_ALL { static int prev_indent = 0; // Cached indent value. @@ -462,13 +462,33 @@ int get_breakindent_win(win_T *wp, const char_u *line) } bri = prev_indent + wp->w_briopt_shift; - // indent minus the length of the showbreak string - if (wp->w_briopt_sbr) { - bri -= vim_strsize(p_sbr); - } // Add offset for number column, if 'n' is in 'cpoptions' bri += win_col_off2(wp); + // add additional indent for numbered lists + if (wp->w_briopt_list != 0) { + regmatch_T regmatch = { + .regprog = vim_regcomp(curbuf->b_p_flp, + RE_MAGIC + RE_STRING + RE_AUTO + RE_STRICT), + }; + + if (regmatch.regprog != NULL) { + if (vim_regexec(®match, line, 0)) { + if (wp->w_briopt_list > 0) { + bri += wp->w_briopt_list; + } else { + bri = (int)(*regmatch.endp - *regmatch.startp); + } + } + vim_regfree(regmatch.regprog); + } + } + + // indent minus the length of the showbreak string + if (wp->w_briopt_sbr) { + bri -= vim_strsize(get_showbreak_value(wp)); + } + // never indent past left window margin if (bri < 0) { bri = 0; diff --git a/src/nvim/option.c b/src/nvim/option.c index c3f9909b9d..0595776f79 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -7426,6 +7426,7 @@ static bool briopt_check(win_T *wp) int bri_shift = 0; int bri_min = 20; bool bri_sbr = false; + int bri_list = 0; char_u *p = wp->w_p_briopt; while (*p != NUL) @@ -7445,6 +7446,9 @@ static bool briopt_check(win_T *wp) { p += 3; bri_sbr = true; + } else if (STRNCMP(p, "list:", 5) == 0) { + p += 5; + bri_list = (int)getdigits(&p, false, 0); } if (*p != ',' && *p != NUL) { return false; @@ -7457,6 +7461,7 @@ static bool briopt_check(win_T *wp) wp->w_briopt_shift = bri_shift; wp->w_briopt_min = bri_min; wp->w_briopt_sbr = bri_sbr; + wp->w_briopt_list = bri_list; return true; } @@ -7669,6 +7674,12 @@ int win_signcol_configured(win_T *wp, int *is_fixed) return ret; } +// Get the local or global value of 'showbreak'. +char_u *get_showbreak_value(win_T *win FUNC_ATTR_UNUSED) +{ + return p_sbr; +} + /// Get window or buffer local options dict_T *get_winbuf_options(const int bufopt) FUNC_ATTR_WARN_UNUSED_RESULT diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim index ff5029b889..5542746a04 100644 --- a/src/nvim/testdir/test_breakindent.vim +++ b/src/nvim/testdir/test_breakindent.vim @@ -16,6 +16,10 @@ func s:screen_lines(lnum, width) abort return ScreenLines([a:lnum, a:lnum + 2], a:width) endfunc +func s:screen_lines2(lnums, lnume, width) abort + return ScreenLines([a:lnums, a:lnume], a:width) +endfunc + func! s:compare_lines(expect, actual) call assert_equal(join(a:expect, "\n"), join(a:actual, "\n")) endfunc @@ -745,4 +749,123 @@ func Test_breakindent20_cpo_n_nextpage() call s:close_windows('set breakindent& briopt& cpo& number&') endfunc +func Test_breakindent20_list() + call s:test_windows('setl breakindent breakindentopt= linebreak') + " default: + call setline(1, [' 1. Congress shall make no law', + \ ' 2.) Congress shall make no law', + \ ' 3.] Congress shall make no law']) + norm! 1gg + redraw! + let lines = s:screen_lines2(1, 6, 20) + let expect = [ + \ " 1. Congress ", + \ "shall make no law ", + \ " 2.) Congress ", + \ "shall make no law ", + \ " 3.] Congress ", + \ "shall make no law ", + \ ] + call s:compare_lines(expect, lines) + " set mininum indent + setl briopt=min:5 + redraw! + let lines = s:screen_lines2(1, 6, 20) + let expect = [ + \ " 1. Congress ", + \ " shall make no law ", + \ " 2.) Congress ", + \ " shall make no law ", + \ " 3.] Congress ", + \ " shall make no law ", + \ ] + call s:compare_lines(expect, lines) + " set additional handing indent + setl briopt+=list:4 + redraw! + let expect = [ + \ " 1. Congress ", + \ " shall make no ", + \ " law ", + \ " 2.) Congress ", + \ " shall make no ", + \ " law ", + \ " 3.] Congress ", + \ " shall make no ", + \ " law ", + \ ] + let lines = s:screen_lines2(1, 9, 20) + call s:compare_lines(expect, lines) + + " reset linebreak option + " Note: it indents by one additional + " space, because of the leading space. + setl linebreak&vim list listchars=eol:$,space:_ + redraw! + let expect = [ + \ "__1.__Congress_shall", + \ " _make_no_law$ ", + \ "__2.)_Congress_shall", + \ " _make_no_law$ ", + \ "__3.]_Congress_shall", + \ " _make_no_law$ ", + \ ] + let lines = s:screen_lines2(1, 6, 20) + call s:compare_lines(expect, lines) + + " check formatlistpat indent + setl briopt=min:5,list:-1 + setl linebreak list&vim listchars&vim + let &l:flp = '^\s*\d\+\.\?[\]:)}\t ]\s*' + redraw! + let expect = [ + \ " 1. Congress ", + \ " shall make no ", + \ " law ", + \ " 2.) Congress ", + \ " shall make no ", + \ " law ", + \ " 3.] Congress ", + \ " shall make no ", + \ " law ", + \ ] + let lines = s:screen_lines2(1, 9, 20) + call s:compare_lines(expect, lines) + " check formatlistpat indent with different list levels + let &l:flp = '^\s*\*\+\s\+' + redraw! + %delete _ + call setline(1, ['* Congress shall make no law', + \ '*** Congress shall make no law', + \ '**** Congress shall make no law']) + norm! 1gg + let expect = [ + \ "* Congress shall ", + \ " make no law ", + \ "*** Congress shall ", + \ " make no law ", + \ "**** Congress shall ", + \ " make no law ", + \ ] + let lines = s:screen_lines2(1, 6, 20) + call s:compare_lines(expect, lines) + + " check formatlistpat indent with different list level + " showbreak and sbr + setl briopt=min:5,sbr,list:-1,shift:2 + setl showbreak=> + redraw! + let expect = [ + \ "* Congress shall ", + \ "> make no law ", + \ "*** Congress shall ", + \ "> make no law ", + \ "**** Congress shall ", + \ "> make no law ", + \ ] + let lines = s:screen_lines2(1, 6, 20) + call s:compare_lines(expect, lines) + call s:close_windows('set breakindent& briopt& linebreak& list& listchars& showbreak&') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim index 4621207d19..29e578ac6d 100644 --- a/src/nvim/testdir/test_usercommands.vim +++ b/src/nvim/testdir/test_usercommands.vim @@ -238,6 +238,8 @@ func Test_CmdErrors() call assert_fails('com! -complete=custom DoCmd :', 'E467:') call assert_fails('com! -complete=customlist DoCmd :', 'E467:') call assert_fails('com! -complete=behave,CustomComplete DoCmd :', 'E468:') + call assert_fails('com! -complete=file DoCmd :', 'E1208:') + call assert_fails('com! -nargs=0 -complete=file DoCmd :', 'E1208:') call assert_fails('com! -nargs=x DoCmd :', 'E176:') call assert_fails('com! -count=1 -count=2 DoCmd :', 'E177:') call assert_fails('com! -count=x DoCmd :', 'E178:') @@ -306,27 +308,33 @@ func Test_CmdCompletion() call feedkeys(":com DoC\\\"\", 'tx') call assert_equal('"com DoC', @:) - com! -complete=behave DoCmd : + com! -nargs=1 -complete=behave DoCmd : call feedkeys(":DoCmd \\\"\", 'tx') call assert_equal('"DoCmd mswin xterm', @:) - " This does not work. Why? - "call feedkeys(":DoCmd x\\\"\", 'tx') - "call assert_equal('"DoCmd xterm', @:) - - com! -complete=custom,CustomComplete DoCmd : + com! -nargs=* -complete=custom,CustomComplete DoCmd : call feedkeys(":DoCmd \\\"\", 'tx') call assert_equal('"DoCmd January February Mars', @:) - com! -complete=customlist,CustomCompleteList DoCmd : + com! -nargs=? -complete=customlist,CustomCompleteList DoCmd : call feedkeys(":DoCmd \\\"\", 'tx') call assert_equal('"DoCmd Monday Tuesday Wednesday', @:) - com! -complete=custom,CustomCompleteList DoCmd : + com! -nargs=+ -complete=custom,CustomCompleteList DoCmd : call assert_fails("call feedkeys(':DoCmd \', 'tx')", 'E730:') - com! -complete=customlist,CustomComp DoCmd : + com! -nargs=+ -complete=customlist,CustomComp DoCmd : call assert_fails("call feedkeys(':DoCmd \', 'tx')", 'E117:') + + " custom completion without a function + com! -nargs=? -complete=custom, DoCmd + call assert_beeps("call feedkeys(':DoCmd \t', 'tx')") + + " custom completion failure with the wrong function + com! -nargs=? -complete=custom,min DoCmd + call assert_fails("call feedkeys(':DoCmd \t', 'tx')", 'E118:') + + delcom DoCmd endfunc func CallExecute(A, L, P) @@ -459,21 +467,21 @@ func Test_command_list() \ execute('command DoCmd')) " Test with various -complete= argument values (non-exhaustive list) - command! -complete=arglist DoCmd : + command! -nargs=1 -complete=arglist DoCmd : call assert_equal("\n Name Args Address Complete Definition" - \ .. "\n DoCmd 0 arglist :", + \ .. "\n DoCmd 1 arglist :", \ execute('command DoCmd')) - command! -complete=augroup DoCmd : + command! -nargs=* -complete=augroup DoCmd : call assert_equal("\n Name Args Address Complete Definition" - \ .. "\n DoCmd 0 augroup :", + \ .. "\n DoCmd * augroup :", \ execute('command DoCmd')) - command! -complete=custom,CustomComplete DoCmd : + command! -nargs=? -complete=custom,CustomComplete DoCmd : call assert_equal("\n Name Args Address Complete Definition" - \ .. "\n DoCmd 0 custom :", + \ .. "\n DoCmd ? custom :", \ execute('command DoCmd')) - command! -complete=customlist,CustomComplete DoCmd : + command! -nargs=+ -complete=customlist,CustomComplete DoCmd : call assert_equal("\n Name Args Address Complete Definition" - \ .. "\n DoCmd 0 customlist :", + \ .. "\n DoCmd + customlist :", \ execute('command DoCmd')) " Test with various -narg= argument values. diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua index e6a9e11fd9..37331d11c7 100644 --- a/test/functional/api/command_spec.lua +++ b/test/functional/api/command_spec.lua @@ -53,7 +53,7 @@ describe('nvim_get_commands', function() end) it('gets various command attributes', function() - local cmd0 = { addr='arguments', bang=false, bar=false, complete='dir', complete_arg=NIL, count='10', definition='pwd ', name='TestCmd', nargs='0', range='10', register=false, script_id=0, } + local cmd0 = { addr='arguments', bang=false, bar=false, complete='dir', complete_arg=NIL, count='10', definition='pwd ', name='TestCmd', nargs='1', range='10', register=false, script_id=0, } local cmd1 = { addr=NIL, bang=false, bar=false, complete='custom', complete_arg='ListUsers', count=NIL, definition='!finger ', name='Finger', nargs='+', range=NIL, register=false, script_id=1, } local cmd2 = { addr=NIL, bang=true, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R2_foo()', name='Cmd2', nargs='*', range=NIL, register=false, script_id=2, } local cmd3 = { addr=NIL, bang=false, bar=true, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R3_ohyeah()', name='Cmd3', nargs='0', range=NIL, register=false, script_id=3, } @@ -62,7 +62,7 @@ describe('nvim_get_commands', function() command -complete=custom,ListUsers -nargs=+ Finger !finger ]]) eq({Finger=cmd1}, meths.get_commands({builtin=false})) - command('command -complete=dir -addr=arguments -count=10 TestCmd pwd ') + command('command -nargs=1 -complete=dir -addr=arguments -count=10 TestCmd pwd ') eq({Finger=cmd1, TestCmd=cmd0}, meths.get_commands({builtin=false})) source([[