# HG changeset patch # User Bram Moolenaar # Date 1653563704 -7200 # Node ID 644b0f0541de6aacaa884f80eb84ad4ad4278466 # Parent 05930f3364a26c5e8fdc06de63393374fcb9c455 patch 8.2.5019: cannot get the first screen column of a character Commit: https://github.com/vim/vim/commit/0f7a3e1de6f71e8e1423fe594890d6aa7f94e132 Author: LemonBoy Date: Thu May 26 12:10:37 2022 +0100 patch 8.2.5019: cannot get the first screen column of a character Problem: Cannot get the first screen column of a character. Solution: Let virtcol() optionally return a list. (closes https://github.com/vim/vim/issues/10482, closes #7964) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -689,7 +689,8 @@ undotree() List undo file tree uniq({list} [, {func} [, {dict}]]) List remove adjacent duplicates from a list values({dict}) List values in {dict} -virtcol({expr}) Number screen column of cursor or mark +virtcol({expr} [, {list}]) Number or List + screen column of cursor or mark visualmode([expr]) String last visual mode used wildmenumode() Number whether 'wildmenu' mode is active win_execute({id}, {command} [, {silent}]) @@ -780,6 +781,7 @@ add({object}, {expr}) *add()* and({expr}, {expr}) *and()* Bitwise AND on the two arguments. The arguments are converted to a number. A List, Dict or Float argument causes an error. + Also see `or()` and `xor()`. Example: > :let flag = and(bits, 0x80) < Can also be used as a |method|: > @@ -936,13 +938,14 @@ autocmd_add({acmds}) *autocmd_add()* item is ignored. cmd Ex command to execute for this autocmd event event autocmd event name. Refer to |autocmd-events|. + TODO: currently only accepts one event. group autocmd group name. Refer to |autocmd-groups|. If this group doesn't exist then it is created. If not specified or empty, then the default group is used. nested boolean flag, set to v:true to add a nested autocmd. Refer to |autocmd-nested|. - once boolean flag, set to v:true to add a autocmd + once boolean flag, set to v:true to add an autocmd which executes only once. Refer to |autocmd-once|. pattern autocmd pattern string. Refer to @@ -952,7 +955,7 @@ autocmd_add({acmds}) *autocmd_add()* commands associated with the specified autocmd event and group and add the {cmd}. This is useful to avoid adding the same command - multiple times for a autocmd event in a group. + multiple times for an autocmd event in a group. Returns v:true on success and v:false on failure. Examples: > @@ -9727,7 +9730,7 @@ values({dict}) *values()* Can also be used as a |method|: > mydict->values() -virtcol({expr}) *virtcol()* +virtcol({expr} [, {list}]) *virtcol()* The result is a Number, which is the screen column of the file position given with {expr}. That is, the last screen position occupied by the character at that position, when the screen @@ -9736,13 +9739,17 @@ virtcol({expr}) *virtcol()* the . For example, for a in column 1, with 'ts' set to 8, it returns 8. |conceal| is ignored. For the byte position use |col()|. + For the use of {expr} see |col()|. - When 'virtualedit' is used {expr} can be [lnum, col, off], where - "off" is the offset in screen columns from the start of the - character. E.g., a position within a or after the last - character. When "off" is omitted zero is used. - When Virtual editing is active in the current mode, a position - beyond the end of the line can be returned. |'virtualedit'| + + When 'virtualedit' is used {expr} can be [lnum, col, off], + where "off" is the offset in screen columns from the start of + the character. E.g., a position within a or after the + last character. When "off" is omitted zero is used. When + Virtual editing is active in the current mode, a position + beyond the end of the line can be returned. Also see + |'virtualedit'| + The accepted positions are: . the cursor position $ the end of the cursor line (the result is the @@ -9754,11 +9761,22 @@ virtcol({expr}) *virtcol()* cursor is the end). When not in Visual mode returns the cursor position. Differs from |'<| in that it's updated right away. + + If {list} is present and non-zero then virtcol() returns a List + with the first and last screen position occupied by the + character. + Note that only marks in the current file can be used. Examples: > - virtcol(".") with text "foo^Lbar", with cursor on the "^L", returns 5 - virtcol("$") with text "foo^Lbar", returns 9 - virtcol("'t") with text " there", with 't at 'h', returns 6 + " With text "foo^Lbar" and cursor on the "^L": + + virtcol(".") " returns 5 + virtcol(".", 1) " returns [4, 5] + virtcol("$") " returns 9 + + " With text " there", with 't at 'h': + + virtcol("'t") " returns 6 < The first column is 1. 0 is returned for an error. A more advanced example that echoes the maximum length of all lines: > diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -994,6 +994,7 @@ static argcheck_T arg2_string_dict[] = { static argcheck_T arg2_string_list_number[] = {arg_string, arg_list_number}; static argcheck_T arg2_string_number[] = {arg_string, arg_number}; static argcheck_T arg2_string_or_list_dict[] = {arg_string_or_list_any, arg_dict_any}; +static argcheck_T arg2_string_or_list_bool[] = {arg_string_or_list_any, arg_bool}; static argcheck_T arg2_string_string_or_number[] = {arg_string, arg_string_or_nr}; static argcheck_T arg3_any_list_dict[] = {NULL, arg_list_any, arg_dict_any}; static argcheck_T arg3_buffer_lnum_lnum[] = {arg_buffer, arg_lnum, arg_lnum}; @@ -1458,6 +1459,20 @@ ret_getreg(int argcount, } static type_T * +ret_virtcol(int argcount, + type2_T *argtypes UNUSED, + type_T **decl_type) +{ + // Assume that if the second argument is passed it's non-zero + if (argcount == 2) + { + *decl_type = &t_list_any; + return &t_list_number; + } + return &t_number; +} + + static type_T * ret_maparg(int argcount, type2_T *argtypes UNUSED, type_T **decl_type UNUSED) @@ -2665,8 +2680,8 @@ static funcentry_T global_functions[] = ret_first_arg, f_uniq}, {"values", 1, 1, FEARG_1, arg1_dict_any, ret_list_any, f_values}, - {"virtcol", 1, 1, FEARG_1, arg1_string_or_list_any, - ret_number, f_virtcol}, + {"virtcol", 1, 2, FEARG_1, arg2_string_or_list_bool, + ret_virtcol, f_virtcol}, {"visualmode", 0, 1, 0, arg1_bool, ret_string, f_visualmode}, {"wildmenumode", 0, 0, 0, NULL, @@ -10380,23 +10395,26 @@ f_type(typval_T *argvars, typval_T *rett } /* - * "virtcol(string)" function + * "virtcol(string, bool)" function */ static void f_virtcol(typval_T *argvars, typval_T *rettv) { - colnr_T vcol = 0; + colnr_T vcol_start = 0; + colnr_T vcol_end = 0; pos_T *fp; int fnum = curbuf->b_fnum; int len; if (in_vim9script() - && check_for_string_or_list_arg(argvars, 0) == FAIL) + && (check_for_string_or_list_arg(argvars, 0) == FAIL + || (argvars[1].v_type != VAR_UNKNOWN + && check_for_bool_arg(argvars, 1) == FAIL))) return; fp = var2fpos(&argvars[0], FALSE, &fnum, FALSE); if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count - && fnum == curbuf->b_fnum) + && fnum == curbuf->b_fnum) { // Limit the column to a valid value, getvvcol() doesn't check. if (fp->col < 0) @@ -10407,11 +10425,23 @@ f_virtcol(typval_T *argvars, typval_T *r if (fp->col > len) fp->col = len; } - getvvcol(curwin, fp, NULL, NULL, &vcol); - ++vcol; - } - - rettv->vval.v_number = vcol; + getvvcol(curwin, fp, &vcol_start, NULL, &vcol_end); + ++vcol_start; + ++vcol_end; + } + + if (argvars[1].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[1])) + { + if (rettv_list_alloc(rettv) == OK) + { + list_append_number(rettv->vval.v_list, vcol_start); + list_append_number(rettv->vval.v_list, vcol_end); + } + else + rettv->vval.v_number = 0; + } + else + rettv->vval.v_number = vcol_end; } /* diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim --- a/src/testdir/test_functions.vim +++ b/src/testdir/test_functions.vim @@ -2947,4 +2947,15 @@ func Test_exepath() endif endfunc +" Test for virtcol() +func Test_virtcol() + enew! + call setline(1, "the\tquick\tbrown\tfox") + norm! 4| + call assert_equal(8, virtcol('.')) + call assert_equal(8, virtcol('.', v:false)) + call assert_equal([4, 8], virtcol('.', v:true)) + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -4494,14 +4494,23 @@ def Test_values() enddef def Test_virtcol() - v9.CheckDefAndScriptFailure(['virtcol(1.1)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1222: String or List required for argument 1']) - v9.CheckDefExecAndScriptFailure(['virtcol("")'], 'E1209: Invalid value for a line number') + v9.CheckDefAndScriptFailure(['virtcol(1.1)'], [ + 'E1013: Argument 1: type mismatch, expected string but got float', + 'E1222: String or List required for argument 1']) + v9.CheckDefAndScriptFailure(['virtcol(".", "a")'], [ + 'E1013: Argument 2: type mismatch, expected bool but got string', + 'E1212: Bool required for argument 2']) + v9.CheckDefExecAndScriptFailure(['virtcol("")'], + 'E1209: Invalid value for a line number') new - setline(1, ['abcdefgh']) + setline(1, ['abcde和平fgh']) cursor(1, 4) assert_equal(4, virtcol('.')) + assert_equal([4, 4], virtcol('.', 1)) + cursor(1, 6) + assert_equal([6, 7], virtcol('.', 1)) assert_equal(4, virtcol([1, 4])) - assert_equal(9, virtcol([1, '$'])) + assert_equal(13, virtcol([1, '$'])) assert_equal(0, virtcol([10, '$'])) bw! enddef diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -735,6 +735,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 5019, +/**/ 5018, /**/ 5017,