# HG changeset patch # User Bram Moolenaar # Date 1610307004 -3600 # Node ID 87671ccc6c6b555dd8ba9f15b141a65cf2925771 # Parent 73ddb200924c898e4963abc0b7825d4e2995d02e patch 8.2.2324: not easy to get mark en cursor posotion by character count Commit: https://github.com/vim/vim/commit/6f02b00bb0958f70bc15534e115b4c6dadff0e06 Author: Bram Moolenaar Date: Sun Jan 10 20:22:54 2021 +0100 patch 8.2.2324: not easy to get mark en cursor posotion by character count Problem: Not easy to get mark en cursor posotion by character count. Solution: Add functions that use character index. (Yegappan Lakshmanan, closes #7648) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1,4 +1,4 @@ -*eval.txt* For Vim version 8.2. Last change: 2020 Dec 29 +*eval.txt* For Vim version 8.2. Last change: 2021 Jan 10 VIM REFERENCE MANUAL by Bram Moolenaar @@ -49,7 +49,7 @@ There are ten types of variables: *Number* *Integer* Number A 32 or 64 bit signed number. |expr-number| The number of bits is available in |v:numbersize|. - Examples: -123 0x10 0177 0b1011 + Examples: -123 0x10 0177 0o177 0b1011 Float A floating point number. |floating-point-format| *Float* {only when compiled with the |+float| feature} @@ -97,9 +97,10 @@ the Number. Examples: Conversion from a String to a Number only happens in legacy Vim script, not in Vim9 script. It is done by converting the first digits to a number. Hexadecimal "0xf9", Octal "017" or "0o17", and Binary "0b10" -numbers are recognized (NOTE: when using |scriptversion-4| octal with a -leading "0" is not recognized). If the String doesn't start with digits, the -result is zero. +numbers are recognized +NOTE: when using |scriptversion-4| octal with a leading "0" is not recognized. +The 0o notation requires patch 8.2.0886. +If the String doesn't start with digits, the result is zero. Examples: String "456" --> Number 456 ~ String "6bar" --> Number 6 ~ @@ -1150,7 +1151,7 @@ expr7 *expr7* For '!' |TRUE| becomes |FALSE|, |FALSE| becomes |TRUE| (one). For '-' the sign of the number is changed. -For '+' the number is unchanged. +For '+' the number is unchanged. Note: "++" has no effect. A String will be converted to a Number first. @@ -1191,6 +1192,7 @@ start with one! If the length of the String is less than the index, the result is an empty String. A negative index always results in an empty string (reason: backward compatibility). Use [-1:] to get the last byte or character. +In Vim9 script a negative index is used like with a list: count from the end. If expr8 is a |List| then it results the item at index expr1. See |list-index| for possible index values. If the index is out of range this results in an @@ -1318,8 +1320,8 @@ When using the lambda form there must be number ------ number number constant *expr-number* - *hex-number* *octal-number* *binary-number* - + + *0x* *hex-number* *0o* *octal-number* *binary-number* Decimal, Hexadecimal (starting with 0x or 0X), Binary (starting with 0b or 0B) and Octal (starting with 0, 0o or 0O). @@ -1572,7 +1574,7 @@ Note how execute() is used to execute an Lambda expressions have internal names like '42'. If you get an error for a lambda expression, you can find what it is with the following command: > - :function {'42'} + :function 42 See also: |numbered-function| ============================================================================== @@ -2475,12 +2477,13 @@ ch_status({handle} [, {options}]) changenr() Number current change number char2nr({expr} [, {utf8}]) Number ASCII/UTF8 value of first char in {expr} charclass({string}) Number character class of {string} +charcol({expr}) Number column number of cursor or mark charidx({string}, {idx} [, {countcc}]) Number char index of byte {idx} in {string} chdir({dir}) String change current working directory cindent({lnum}) Number C indent for line {lnum} clearmatches([{win}]) none clear all matches -col({expr}) Number column nr of cursor or mark +col({expr}) Number column byte index of cursor or mark complete({startcol}, {matches}) none set Insert mode completion complete_add({expr}) Number add completion match complete_check() Number check for key typed during completion @@ -2558,6 +2561,7 @@ getbufvar({expr}, {varname} [, {def}]) getchangelist([{expr}]) List list of change list items getchar([expr]) Number get one character from the user getcharmod() Number modifiers for the last typed character +getcharpos({expr}) List position of cursor, mark, etc. getcharsearch() Dict last character search getcmdline() String return the current command-line getcmdpos() Number return cursor position in command-line @@ -2566,6 +2570,7 @@ getcmdwintype() String return current getcompletion({pat}, {type} [, {filtered}]) List list of cmdline completion matches getcurpos([{winnr}]) List position of the cursor +getcursorcharpos([{winnr}]) List character position of the cursor getcwd([{winnr} [, {tabnr}]]) String get the current working directory getenv({name}) String return environment variable getfontname([{name}]) String name of font being used @@ -2828,8 +2833,10 @@ setbufline({expr}, {lnum}, {text}) setbufvar({expr}, {varname}, {val}) none set {varname} in buffer {expr} to {val} setcellwidths({list}) none set character cell width overrides +setcharpos({expr}, {list}) Number set the {expr} position to {list} setcharsearch({dict}) Dict set character search from {dict} setcmdpos({pos}) Number set cursor position in command-line +setcursorcharpos({list}) Number move cursor to position in {list} setenv({name}, {val}) none set environment variable setfperm({fname}, {mode}) Number set {fname} file permissions to {mode} setline({lnum}, {line}) Number set line {lnum} to {line} @@ -3513,8 +3520,8 @@ byteidxcomp({expr}, {nr}) *byteidxco < The first and third echo result in 3 ('e' plus composing character is 3 bytes), the second echo results in 1 ('e' is one byte). - Only works differently from byteidx() when 'encoding' is set to - a Unicode encoding. + Only works differently from byteidx() when 'encoding' is set + to a Unicode encoding. Can also be used as a |method|: > GetName()->byteidxcomp(idx) @@ -3590,6 +3597,18 @@ charclass({string}) *charclass()* other specific Unicode class The class is used in patterns and word motions. + *charcol()* +charcol({expr}) Same as |col()| but returns the character index of the column + position given with {expr} instead of the byte position. + + Example: + With the cursor on '세' in line 5 with text "여보세요": > + charcol('.') returns 3 + col('.') returns 7 + +< Can also be used as a |method|: > + GetPos()->col() +< *charidx()* charidx({string}, {idx} [, {countcc}]) Return the character index of the byte at {idx} in {string}. @@ -3680,7 +3699,8 @@ col({expr}) The result is a Number, whic out of range then col() returns zero. To get the line number use |line()|. To get both use |getpos()|. - For the screen column position use |virtcol()|. + For the screen column position use |virtcol()|. For the + character position use |charcol()|. Note that only marks in the current file can be used. Examples: > col(".") column of cursor @@ -3981,6 +4001,9 @@ cursor({list}) This is like the return value of |getpos()| or |getcurpos()|, but without the first item. + To position the cursor using the character count, use + |setcursorcharpos()|. + Does not change the jumplist. If {lnum} is greater than the number of lines in the buffer, the cursor will be positioned at the last line in the buffer. @@ -5220,6 +5243,20 @@ getcharmod() *getcharmod()* character itself are obtained. Thus Shift-a results in "A" without a modifier. + *getcharpos()* +getcharpos({expr}) + Get the position for {expr}. Same as |getpos()| but the column + number in the returned List is a character index instead of + a byte index. + + Example: + With the cursor on '세' in line 5 with text "여보세요": > + getcharpos('.') returns [0, 5, 3, 0] + getpos('.') returns [0, 5, 7, 0] +< + Can also be used as a |method|: > + GetMark()->getcharpos() + getcharsearch() *getcharsearch()* Return the current character search information as a {dict} with the following entries: @@ -5345,8 +5382,11 @@ getcurpos([{winid}]) includes an extra "curswant" item in the list: [0, lnum, col, off, curswant] ~ The "curswant" number is the preferred column when moving the - cursor vertically. Also see |getpos()|. - The first "bufnum" item is always zero. + cursor vertically. Also see |getcursorcharpos()| and + |getpos()|. + The first "bufnum" item is always zero. The byte position of + the cursor is returned in 'col'. To get the character + position, use |getcursorcharpos()|. The optional {winid} argument can specify the window. It can be the window number or the |window-ID|. The last known @@ -5360,7 +5400,24 @@ getcurpos([{winid}]) call setpos('.', save_cursor) < Note that this only works within the window. See |winrestview()| for restoring more state. - *getcwd()* + + Can also be used as a |method|: > + GetWinid()->getcurpos() + +< *getcursorcharpos()* +getcursorcharpos([{winid}]) + Same as |getcurpos()| but the column number in the returned + List is a character index instead of a byte index. + + Example: + With the cursor on '보' in line 3 with text "여보세요": > + getcursorcharpos() returns [0, 3, 2, 0, 3] + getcurpos() returns [0, 3, 4, 0, 3] + +< Can also be used as a |method|: > + GetWinid()->getcursorcharpos() + +< *getcwd()* getcwd([{winnr} [, {tabnr}]]) The result is a String, which is the name of the current working directory. @@ -5667,16 +5724,18 @@ getpos({expr}) Get the position for {exp Note that for '< and '> Visual mode matters: when it is "V" (visual line mode) the column of '< is zero and the column of '> is a large number. + The column number in the returned List is the byte position + within the line. To get the character position in the line, + use |getcharpos()| This can be used to save and restore the position of a mark: > let save_a_mark = getpos("'a") ... call setpos("'a", save_a_mark) -< Also see |getcurpos()| and |setpos()|. +< Also see |getcharpos()|, |getcurpos()| and |setpos()|. Can also be used as a |method|: > GetMark()->getpos() - getqflist([{what}]) *getqflist()* Returns a |List| with all the current quickfix errors. Each list item is a dictionary with these entries: @@ -7542,8 +7601,10 @@ matchstrpos({expr}, {pat} [, {start} [, < *max()* -max({expr}) Return the maximum value of all items in {expr}. - {expr} can be a |List| or a |Dictionary|. For a Dictionary, +max({expr}) Return the maximum value of all items in {expr}. Example: > + echo max([apples, pears, oranges]) + +< {expr} can be a |List| or a |Dictionary|. For a Dictionary, it returns the maximum of all values in the Dictionary. If {expr} is neither a List nor a Dictionary, or one of the items in {expr} cannot be used as a Number this results in @@ -7613,8 +7674,10 @@ menu_info({name} [, {mode}]) *menu_in < *min()* -min({expr}) Return the minimum value of all items in {expr}. - {expr} can be a |List| or a |Dictionary|. For a Dictionary, +min({expr}) Return the minimum value of all items in {expr}. Example: > + echo min([apples, pears, oranges]) + +< {expr} can be a |List| or a |Dictionary|. For a Dictionary, it returns the minimum of all values in the Dictionary. If {expr} is neither a List nor a Dictionary, or one of the items in {expr} cannot be used as a Number this results in @@ -7631,13 +7694,13 @@ mkdir({name} [, {path} [, {prot}]]) necessary. Otherwise it must be "". If {prot} is given it is used to set the protection bits of - the new directory. The default is 0755 (rwxr-xr-x: r/w for - the user readable for others). Use 0700 to make it unreadable - for others. This is only used for the last part of {name}. - Thus if you create /tmp/foo/bar then /tmp/foo will be created - with 0755. + the new directory. The default is 0o755 (rwxr-xr-x: r/w for + the user, readable for others). Use 0o700 to make it + unreadable for others. This is only used for the last part of + {name}. Thus if you create /tmp/foo/bar then /tmp/foo will be + created with 0o755. Example: > - :call mkdir($HOME . "/tmp/foo/bar", "p", 0700) + :call mkdir($HOME . "/tmp/foo/bar", "p", 0o700) < This function is not available in the |sandbox|. @@ -9200,6 +9263,19 @@ setcellwidths({list}) *setcellwidths < You can use the script $VIMRUNTIME/tools/emoji_list.vim to see the effect for known emoji characters. +setcharpos({expr}, {list}) *setcharpos()* + Same as |setpos()| but uses the specified column number as the + character index instead of the byte index in the line. + + Example: + With the text "여보세요" in line 8: > + call setcharpos('.', [0, 8, 4, 0]) +< positions the cursor on the fourth character '요'. > + call setpos('.', [0, 8, 4, 0]) +< positions the cursor on the second character '보'. + + Can also be used as a |method|: > + GetPosition()->setcharpos('.') setcharsearch({dict}) *setcharsearch()* Set the current character search information to {dict}, @@ -9242,6 +9318,21 @@ setcmdpos({pos}) *setcmdpos()* Can also be used as a |method|: > GetPos()->setcmdpos() +setcursorcharpos({lnum}, {col} [, {off}]) *setcursorcharpos()* +setcursorcharpos({list}) + Same as |cursor()| but uses the specified column number as the + character index instead of the byte index in the line. + + Example: + With the text "여보세요" in line 4: > + call setcursorcharpos(4, 3) +< positions the cursor on the third character '세'. > + call cursor(4, 3) +< positions the cursor on the first character '여'. + + Can also be used as a |method|: > + GetCursorPos()->setcursorcharpos() + setenv({name}, {val}) *setenv()* Set environment variable {name} to {val}. When {val} is |v:null| the environment variable is deleted. @@ -9353,7 +9444,8 @@ setpos({expr}, {list}) "lnum" and "col" are the position in the buffer. The first column is 1. Use a zero "lnum" to delete a mark. If "col" is - smaller than 1 then 1 is used. + smaller than 1 then 1 is used. To use the character count + instead of the byte count, use |setcharpos()|. The "off" number is only used when 'virtualedit' is set. Then it is the offset in screen columns from the start of the @@ -9373,7 +9465,7 @@ setpos({expr}, {list}) Returns 0 when the position could be set, -1 otherwise. An error message is given if {expr} is invalid. - Also see |getpos()| and |getcurpos()|. + Also see |setcharpos()|, |getpos()| and |getcurpos()|. This does not restore the preferred column for moving vertically; if you set the cursor position with this, |j| and diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -753,6 +753,11 @@ Cursor and mark position: *cursor-funct screenchar() get character code at a screen line/row screenchars() get character codes at a screen line/row screenstring() get string of characters at a screen line/row + charcol() character number of the cursor or a mark + getcharpos() get character position of cursor, mark, etc. + setcharpos() set character position of cursor, mark, etc. + getcursorcharpos() get character position of the cursor + setcursorcharpos() set character position of the cursor Working with text in the current buffer: *text-functions* getline() get a line or list of lines from the buffer diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -5054,6 +5054,61 @@ string2float( #endif /* + * Convert the specified byte index of line 'lnum' in buffer 'buf' to a + * character index. Works only for loaded buffers. Returns -1 on failure. + * The index of the first character is one. + */ + int +buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx) +{ + char_u *str; + + if (buf == NULL || buf->b_ml.ml_mfp == NULL) + return -1; + + if (lnum > buf->b_ml.ml_line_count) + lnum = buf->b_ml.ml_line_count; + + str = ml_get_buf(buf, lnum, FALSE); + if (str == NULL) + return -1; + + if (*str == NUL) + return 1; + + return mb_charlen_len(str, byteidx + 1); +} + +/* + * Convert the specified character index of line 'lnum' in buffer 'buf' to a + * byte index. Works only for loaded buffers. Returns -1 on failure. The index + * of the first byte and the first character is one. + */ + int +buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx) +{ + char_u *str; + char_u *t; + + if (buf == NULL || buf->b_ml.ml_mfp == NULL) + return -1; + + if (lnum > buf->b_ml.ml_line_count) + lnum = buf->b_ml.ml_line_count; + + str = ml_get_buf(buf, lnum, FALSE); + if (str == NULL) + return -1; + + // Convert the character offset to a byte offset + t = str; + while (*t != NUL && --charidx > 0) + t += mb_ptr2len(t); + + return t - str + 1; +} + +/* * Translate a String variable into a position. * Returns NULL when there is an error. */ @@ -5061,7 +5116,8 @@ string2float( var2fpos( typval_T *varp, int dollar_lnum, // TRUE when $ is last line - int *fnum) // set to fnum for '0, 'A, etc. + int *fnum, // set to fnum for '0, 'A, etc. + int charcol) // return character column { char_u *name; static pos_T pos; @@ -5083,7 +5139,10 @@ var2fpos( pos.lnum = list_find_nr(l, 0L, &error); if (error || pos.lnum <= 0 || pos.lnum > curbuf->b_ml.ml_line_count) return NULL; // invalid line number - len = (long)STRLEN(ml_get(pos.lnum)); + if (charcol) + len = (long)mb_charlen(ml_get(pos.lnum)); + else + len = (long)STRLEN(ml_get(pos.lnum)); // Get the column number // We accept "$" for the column number: last column. @@ -5118,18 +5177,29 @@ var2fpos( if (name == NULL) return NULL; if (name[0] == '.') // cursor - return &curwin->w_cursor; + { + pos = curwin->w_cursor; + if (charcol) + pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col) - 1; + return &pos; + } if (name[0] == 'v' && name[1] == NUL) // Visual start { if (VIsual_active) - return &VIsual; - return &curwin->w_cursor; + pos = VIsual; + else + pos = curwin->w_cursor; + if (charcol) + pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col) - 1; + return &pos; } if (name[0] == '\'') // mark { pp = getmark_buf_fnum(curbuf, name[1], FALSE, fnum); if (pp == NULL || pp == (pos_T *)-1 || pp->lnum <= 0) return NULL; + if (charcol) + pp->col = buf_byteidx_to_charidx(curbuf, pp->lnum, pp->col) - 1; return pp; } @@ -5164,7 +5234,10 @@ var2fpos( else { pos.lnum = curwin->w_cursor.lnum; - pos.col = (colnr_T)STRLEN(ml_get_curline()); + if (charcol) + pos.col = (colnr_T)mb_charlen(ml_get_curline()); + else + pos.col = (colnr_T)STRLEN(ml_get_curline()); } return &pos; } @@ -5184,7 +5257,8 @@ list2fpos( typval_T *arg, pos_T *posp, int *fnump, - colnr_T *curswantp) + colnr_T *curswantp, + int charcol) { list_T *l = arg->vval.v_list; long i = 0; @@ -5216,6 +5290,18 @@ list2fpos( n = list_find_nr(l, i++, NULL); // col if (n < 0) return FAIL; + // If character position is specified, then convert to byte position + if (charcol) + { + buf_T *buf; + + // Get the text for the specified line in a loaded buffer + buf = buflist_findnr(fnump == NULL ? curbuf->b_fnum : *fnump); + if (buf == NULL || buf->b_ml.ml_mfp == NULL) + return FAIL; + + n = buf_charidx_to_byteidx(buf, posp->lnum, n); + } posp->col = n; n = list_find_nr(l, i, NULL); // off diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -47,6 +47,7 @@ static void f_ceil(typval_T *argvars, ty #endif static void f_changenr(typval_T *argvars, typval_T *rettv); static void f_char2nr(typval_T *argvars, typval_T *rettv); +static void f_charcol(typval_T *argvars, typval_T *rettv); static void f_charidx(typval_T *argvars, typval_T *rettv); static void f_col(typval_T *argvars, typval_T *rettv); static void f_confirm(typval_T *argvars, typval_T *rettv); @@ -87,12 +88,14 @@ static void f_function(typval_T *argvars static void f_garbagecollect(typval_T *argvars, typval_T *rettv); static void f_get(typval_T *argvars, typval_T *rettv); static void f_getchangelist(typval_T *argvars, typval_T *rettv); +static void f_getcharpos(typval_T *argvars, typval_T *rettv); static void f_getcharsearch(typval_T *argvars, typval_T *rettv); static void f_getenv(typval_T *argvars, typval_T *rettv); static void f_getfontname(typval_T *argvars, typval_T *rettv); static void f_getjumplist(typval_T *argvars, typval_T *rettv); static void f_getpid(typval_T *argvars, typval_T *rettv); static void f_getcurpos(typval_T *argvars, typval_T *rettv); +static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv); static void f_getpos(typval_T *argvars, typval_T *rettv); static void f_getreg(typval_T *argvars, typval_T *rettv); static void f_getreginfo(typval_T *argvars, typval_T *rettv); @@ -190,7 +193,9 @@ static void f_searchdecl(typval_T *argva static void f_searchpair(typval_T *argvars, typval_T *rettv); static void f_searchpairpos(typval_T *argvars, typval_T *rettv); static void f_searchpos(typval_T *argvars, typval_T *rettv); +static void f_setcharpos(typval_T *argvars, typval_T *rettv); static void f_setcharsearch(typval_T *argvars, typval_T *rettv); +static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv); static void f_setenv(typval_T *argvars, typval_T *rettv); static void f_setfperm(typval_T *argvars, typval_T *rettv); static void f_setpos(typval_T *argvars, typval_T *rettv); @@ -790,6 +795,8 @@ static funcentry_T global_functions[] = ret_number, f_char2nr}, {"charclass", 1, 1, FEARG_1, NULL, ret_number, f_charclass}, + {"charcol", 1, 1, FEARG_1, NULL, + ret_number, f_charcol}, {"charidx", 2, 3, FEARG_1, NULL, ret_number, f_charidx}, {"chdir", 1, 1, FEARG_1, NULL, @@ -928,6 +935,8 @@ static funcentry_T global_functions[] = ret_number, f_getchar}, {"getcharmod", 0, 0, 0, NULL, ret_number, f_getcharmod}, + {"getcharpos", 1, 1, FEARG_1, NULL, + ret_list_number, f_getcharpos}, {"getcharsearch", 0, 0, 0, NULL, ret_dict_any, f_getcharsearch}, {"getcmdline", 0, 0, 0, NULL, @@ -942,6 +951,8 @@ static funcentry_T global_functions[] = ret_list_string, f_getcompletion}, {"getcurpos", 0, 1, FEARG_1, NULL, ret_list_number, f_getcurpos}, + {"getcursorcharpos", 0, 1, FEARG_1, NULL, + ret_list_number, f_getcursorcharpos}, {"getcwd", 0, 2, FEARG_1, NULL, ret_string, f_getcwd}, {"getenv", 1, 1, FEARG_1, NULL, @@ -1394,10 +1405,14 @@ static funcentry_T global_functions[] = ret_void, f_setbufvar}, {"setcellwidths", 1, 1, FEARG_1, NULL, ret_void, f_setcellwidths}, + {"setcharpos", 2, 2, FEARG_2, NULL, + ret_number, f_setcharpos}, {"setcharsearch", 1, 1, FEARG_1, NULL, ret_void, f_setcharsearch}, {"setcmdpos", 1, 1, FEARG_1, NULL, ret_number, f_setcmdpos}, + {"setcursorcharpos", 1, 3, FEARG_1, NULL, + ret_number, f_setcursorcharpos}, {"setenv", 2, 2, FEARG_2, NULL, ret_void, f_setenv}, {"setfperm", 2, 2, FEARG_1, NULL, @@ -2424,6 +2439,61 @@ f_char2nr(typval_T *argvars, typval_T *r } /* + * Get the current cursor column and store it in 'rettv'. If 'charcol' is TRUE, + * returns the character index of the column. Otherwise, returns the byte index + * of the column. + */ + static void +get_col(typval_T *argvars, typval_T *rettv, int charcol) +{ + colnr_T col = 0; + pos_T *fp; + int fnum = curbuf->b_fnum; + + fp = var2fpos(&argvars[0], FALSE, &fnum, charcol); + if (fp != NULL && fnum == curbuf->b_fnum) + { + if (fp->col == MAXCOL) + { + // '> can be MAXCOL, get the length of the line then + if (fp->lnum <= curbuf->b_ml.ml_line_count) + col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; + else + col = MAXCOL; + } + else + { + col = fp->col + 1; + // col(".") when the cursor is on the NUL at the end of the line + // because of "coladd" can be seen as an extra column. + if (virtual_active() && fp == &curwin->w_cursor) + { + char_u *p = ml_get_cursor(); + + if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p, + curwin->w_virtcol - curwin->w_cursor.coladd)) + { + int l; + + if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL) + col += l; + } + } + } + } + rettv->vval.v_number = col; +} + +/* + * "charcol()" function + */ + static void +f_charcol(typval_T *argvars, typval_T *rettv) +{ + get_col(argvars, rettv, TRUE); +} + +/* * "charidx()" function */ static void @@ -2497,42 +2567,7 @@ get_optional_window(typval_T *argvars, i static void f_col(typval_T *argvars, typval_T *rettv) { - colnr_T col = 0; - pos_T *fp; - int fnum = curbuf->b_fnum; - - fp = var2fpos(&argvars[0], FALSE, &fnum); - if (fp != NULL && fnum == curbuf->b_fnum) - { - if (fp->col == MAXCOL) - { - // '> can be MAXCOL, get the length of the line then - if (fp->lnum <= curbuf->b_ml.ml_line_count) - col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; - else - col = MAXCOL; - } - else - { - col = fp->col + 1; - // col(".") when the cursor is on the NUL at the end of the line - // because of "coladd" can be seen as an extra column. - if (virtual_active() && fp == &curwin->w_cursor) - { - char_u *p = ml_get_cursor(); - - if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p, - curwin->w_virtcol - curwin->w_cursor.coladd)) - { - int l; - - if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL) - col += l; - } - } - } - } - rettv->vval.v_number = col; + get_col(argvars, rettv, FALSE); } /* @@ -2633,26 +2668,24 @@ f_cosh(typval_T *argvars, typval_T *rett #endif /* - * "cursor(lnum, col)" function, or - * "cursor(list)" - * - * Moves the cursor to the specified line and column. - * Returns 0 when the position could be set, -1 otherwise. - */ - static void -f_cursor(typval_T *argvars, typval_T *rettv) + * Set the cursor position. + * If 'charcol' is TRUE, then use the column number as a character offet. + * Otherwise use the column number as a byte offset. + */ + static void +set_cursorpos(typval_T *argvars, typval_T *rettv, int charcol) { long line, col; long coladd = 0; int set_curswant = TRUE; rettv->vval.v_number = -1; - if (argvars[1].v_type == VAR_UNKNOWN) + if (argvars[0].v_type == VAR_LIST) { pos_T pos; colnr_T curswant = -1; - if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) + if (list2fpos(argvars, &pos, NULL, &curswant, charcol) == FAIL) { emsg(_(e_invarg)); return; @@ -2666,15 +2699,24 @@ f_cursor(typval_T *argvars, typval_T *re set_curswant = FALSE; } } - else + else if ((argvars[0].v_type == VAR_NUMBER || + argvars[0].v_type == VAR_STRING) + && argvars[1].v_type == VAR_NUMBER) { line = tv_get_lnum(argvars); if (line < 0) semsg(_(e_invarg2), tv_get_string(&argvars[0])); col = (long)tv_get_number_chk(&argvars[1], NULL); + if (charcol) + col = buf_charidx_to_byteidx(curbuf, line, col); if (argvars[2].v_type != VAR_UNKNOWN) coladd = (long)tv_get_number_chk(&argvars[2], NULL); } + else + { + emsg(_(e_invarg)); + return; + } if (line < 0 || col < 0 || coladd < 0) return; // type error; errmsg already given if (line > 0) @@ -2693,6 +2735,19 @@ f_cursor(typval_T *argvars, typval_T *re rettv->vval.v_number = 0; } +/* + * "cursor(lnum, col)" function, or + * "cursor(list)" + * + * Moves the cursor to the specified line and column. + * Returns 0 when the position could be set, -1 otherwise. + */ + static void +f_cursor(typval_T *argvars, typval_T *rettv) +{ + set_cursorpos(argvars, rettv, FALSE); +} + #ifdef MSWIN /* * "debugbreak()" function @@ -3887,6 +3942,88 @@ f_getchangelist(typval_T *argvars, typva #endif } + static void +getpos_both( + typval_T *argvars, + typval_T *rettv, + int getcurpos, + int charcol) +{ + pos_T *fp = NULL; + pos_T pos; + win_T *wp = curwin; + list_T *l; + int fnum = -1; + + if (rettv_list_alloc(rettv) == OK) + { + l = rettv->vval.v_list; + if (getcurpos) + { + if (argvars[0].v_type != VAR_UNKNOWN) + { + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp != NULL) + fp = &wp->w_cursor; + } + else + fp = &curwin->w_cursor; + if (fp != NULL && charcol) + { + pos = *fp; + pos.col = buf_byteidx_to_charidx(wp->w_buffer, pos.lnum, + pos.col) - 1; + fp = &pos; + } + } + else + fp = var2fpos(&argvars[0], TRUE, &fnum, charcol); + if (fnum != -1) + list_append_number(l, (varnumber_T)fnum); + else + list_append_number(l, (varnumber_T)0); + list_append_number(l, (fp != NULL) ? (varnumber_T)fp->lnum + : (varnumber_T)0); + list_append_number(l, (fp != NULL) + ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) + : (varnumber_T)0); + list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : + (varnumber_T)0); + if (getcurpos) + { + int save_set_curswant = curwin->w_set_curswant; + colnr_T save_curswant = curwin->w_curswant; + colnr_T save_virtcol = curwin->w_virtcol; + + if (wp == curwin) + update_curswant(); + list_append_number(l, wp == NULL ? 0 : wp->w_curswant == MAXCOL + ? (varnumber_T)MAXCOL : (varnumber_T)wp->w_curswant + 1); + + // Do not change "curswant", as it is unexpected that a get + // function has a side effect. + if (wp == curwin && save_set_curswant) + { + curwin->w_set_curswant = save_set_curswant; + curwin->w_curswant = save_curswant; + curwin->w_virtcol = save_virtcol; + curwin->w_valid &= ~VALID_VIRTCOL; + } + } + } + else + rettv->vval.v_number = FALSE; +} + +/* + * "getcharpos()" function + */ + static void +f_getcharpos(typval_T *argvars UNUSED, typval_T *rettv) +{ + getpos_both(argvars, rettv, FALSE, TRUE); +} + /* * "getcharsearch()" function */ @@ -4019,77 +4156,19 @@ f_getpid(typval_T *argvars UNUSED, typva rettv->vval.v_number = mch_get_pid(); } - static void -getpos_both( - typval_T *argvars, - typval_T *rettv, - int getcurpos) -{ - pos_T *fp = NULL; - win_T *wp = curwin; - list_T *l; - int fnum = -1; - - if (rettv_list_alloc(rettv) == OK) - { - l = rettv->vval.v_list; - if (getcurpos) - { - if (argvars[0].v_type != VAR_UNKNOWN) - { - wp = find_win_by_nr_or_id(&argvars[0]); - if (wp != NULL) - fp = &wp->w_cursor; - } - else - fp = &curwin->w_cursor; - } - else - fp = var2fpos(&argvars[0], TRUE, &fnum); - if (fnum != -1) - list_append_number(l, (varnumber_T)fnum); - else - list_append_number(l, (varnumber_T)0); - list_append_number(l, (fp != NULL) ? (varnumber_T)fp->lnum - : (varnumber_T)0); - list_append_number(l, (fp != NULL) - ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) - : (varnumber_T)0); - list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : - (varnumber_T)0); - if (getcurpos) - { - int save_set_curswant = curwin->w_set_curswant; - colnr_T save_curswant = curwin->w_curswant; - colnr_T save_virtcol = curwin->w_virtcol; - - if (wp == curwin) - update_curswant(); - list_append_number(l, wp == NULL ? 0 : wp->w_curswant == MAXCOL - ? (varnumber_T)MAXCOL : (varnumber_T)wp->w_curswant + 1); - - // Do not change "curswant", as it is unexpected that a get - // function has a side effect. - if (wp == curwin && save_set_curswant) - { - curwin->w_set_curswant = save_set_curswant; - curwin->w_curswant = save_curswant; - curwin->w_virtcol = save_virtcol; - curwin->w_valid &= ~VALID_VIRTCOL; - } - } - } - else - rettv->vval.v_number = FALSE; -} - /* * "getcurpos()" function */ static void f_getcurpos(typval_T *argvars, typval_T *rettv) { - getpos_both(argvars, rettv, TRUE); + getpos_both(argvars, rettv, TRUE, FALSE); +} + + static void +f_getcursorcharpos(typval_T *argvars, typval_T *rettv) +{ + getpos_both(argvars, rettv, TRUE, TRUE); } /* @@ -4098,7 +4177,7 @@ f_getcurpos(typval_T *argvars, typval_T static void f_getpos(typval_T *argvars, typval_T *rettv) { - getpos_both(argvars, rettv, FALSE); + getpos_both(argvars, rettv, FALSE, FALSE); } /* @@ -6183,14 +6262,14 @@ f_line(typval_T *argvars, typval_T *rett == OK) { check_cursor(); - fp = var2fpos(&argvars[0], TRUE, &fnum); + fp = var2fpos(&argvars[0], TRUE, &fnum, FALSE); } restore_win_noblock(save_curwin, save_curtab, TRUE); } } else // use current window - fp = var2fpos(&argvars[0], TRUE, &fnum); + fp = var2fpos(&argvars[0], TRUE, &fnum, FALSE); if (fp != NULL) lnum = fp->lnum; @@ -8065,6 +8144,60 @@ f_searchpos(typval_T *argvars, typval_T list_append_number(rettv->vval.v_list, (varnumber_T)n); } +/* + * Set the cursor or mark position. + * If 'charpos' is TRUE, then use the column number as a character offet. + * Otherwise use the column number as a byte offset. + */ + static void +set_position(typval_T *argvars, typval_T *rettv, int charpos) +{ + pos_T pos; + int fnum; + char_u *name; + colnr_T curswant = -1; + + rettv->vval.v_number = -1; + + name = tv_get_string_chk(argvars); + if (name != NULL) + { + if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) == OK) + { + if (pos.col != MAXCOL && --pos.col < 0) + pos.col = 0; + if ((name[0] == '.' && name[1] == NUL)) + { + // set cursor; "fnum" is ignored + curwin->w_cursor = pos; + if (curswant >= 0) + { + curwin->w_curswant = curswant - 1; + curwin->w_set_curswant = FALSE; + } + check_cursor(); + rettv->vval.v_number = 0; + } + else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) + { + // set mark + if (setmark_pos(name[1], &pos, fnum) == OK) + rettv->vval.v_number = 0; + } + else + emsg(_(e_invarg)); + } + } +} +/* + * "setcharpos()" function + */ + static void +f_setcharpos(typval_T *argvars, typval_T *rettv) +{ + set_position(argvars, rettv, TRUE); +} + static void f_setcharsearch(typval_T *argvars, typval_T *rettv UNUSED) { @@ -8107,6 +8240,15 @@ f_setcharsearch(typval_T *argvars, typva } /* + * "setcursorcharpos" function + */ + static void +f_setcursorcharpos(typval_T *argvars, typval_T *rettv UNUSED) +{ + set_cursorpos(argvars, rettv, TRUE); +} + +/* * "setenv()" function */ static void @@ -8165,41 +8307,7 @@ f_setfperm(typval_T *argvars, typval_T * static void f_setpos(typval_T *argvars, typval_T *rettv) { - pos_T pos; - int fnum; - char_u *name; - colnr_T curswant = -1; - - rettv->vval.v_number = -1; - name = tv_get_string_chk(argvars); - if (name != NULL) - { - if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) - { - if (pos.col != MAXCOL && --pos.col < 0) - pos.col = 0; - if (name[0] == '.' && name[1] == NUL) - { - // set cursor; "fnum" is ignored - curwin->w_cursor = pos; - if (curswant >= 0) - { - curwin->w_curswant = curswant - 1; - curwin->w_set_curswant = FALSE; - } - check_cursor(); - rettv->vval.v_number = 0; - } - else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) - { - // set mark - if (setmark_pos(name[1], &pos, fnum) == OK) - rettv->vval.v_number = 0; - } - else - emsg(_(e_invarg)); - } - } + set_position(argvars, rettv, FALSE); } /* @@ -9947,7 +10055,7 @@ f_virtcol(typval_T *argvars, typval_T *r int fnum = curbuf->b_fnum; int len; - fp = var2fpos(&argvars[0], FALSE, &fnum); + fp = var2fpos(&argvars[0], FALSE, &fnum, FALSE); if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count && fnum == curbuf->b_fnum) { diff --git a/src/proto/eval.pro b/src/proto/eval.pro --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -55,8 +55,10 @@ char_u *echo_string_core(typval_T *tv, c char_u *echo_string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID); char_u *string_quote(char_u *str, int function); int string2float(char_u *text, float_T *value); -pos_T *var2fpos(typval_T *varp, int dollar_lnum, int *fnum); -int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp); +int buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx); +int buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx); +pos_T *var2fpos(typval_T *varp, int dollar_lnum, int *fnum, int charcol); +int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, int char_col); int get_env_len(char_u **arg); int get_id_len(char_u **arg); int get_name_len(char_u **arg, char_u **alias, int evaluate, int verbose); diff --git a/src/tag.c b/src/tag.c --- a/src/tag.c +++ b/src/tag.c @@ -4201,7 +4201,7 @@ tagstack_push_items(win_T *wp, list_T *l // parse 'from' for the cursor position before the tag jump if ((di = dict_find(itemdict, (char_u *)"from", -1)) == NULL) continue; - if (list2fpos(&di->di_tv, &mark, &fnum, NULL) != OK) + if (list2fpos(&di->di_tv, &mark, &fnum, NULL, FALSE) != OK) continue; if ((tagname = dict_get_string(itemdict, (char_u *)"tagname", TRUE)) == NULL) diff --git a/src/testdir/test_cursor_func.vim b/src/testdir/test_cursor_func.vim --- a/src/testdir/test_cursor_func.vim +++ b/src/testdir/test_cursor_func.vim @@ -1,4 +1,4 @@ -" Tests for cursor(). +" Tests for cursor() and other functions that get/set the cursor position func Test_wrong_arguments() call assert_fails('call cursor(1. 3)', 'E474:') @@ -123,4 +123,187 @@ func Test_screenpos_number() bwipe! endfunc +func SaveVisualStartCharPos() + call add(g:VisualStartPos, getcharpos('v')) + return '' +endfunc + +" Test for the getcharpos() function +func Test_getcharpos() + call assert_fails('call getcharpos({})', 'E731:') + call assert_equal([0, 0, 0, 0], getcharpos(0)) + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + + " Test for '.' and '$' + normal 1G + call assert_equal([0, 1, 1, 0], getcharpos('.')) + call assert_equal([0, 4, 1, 0], getcharpos('$')) + normal 2G6l + call assert_equal([0, 2, 7, 0], getcharpos('.')) + normal 3G$ + call assert_equal([0, 3, 1, 0], getcharpos('.')) + normal 4G$ + call assert_equal([0, 4, 9, 0], getcharpos('.')) + + " Test for a mark + normal 2G7lmmgg + call assert_equal([0, 2, 8, 0], getcharpos("'m")) + delmarks m + call assert_equal([0, 0, 0, 0], getcharpos("'m")) + + " Test for the visual start column + vnoremap SaveVisualStartCharPos() + let g:VisualStartPos = [] + exe "normal 2G6lv$\ohh\o\" + call assert_equal([[0, 2, 7, 0], [0, 2, 9, 0], [0, 2, 5, 0]], g:VisualStartPos) + call assert_equal([0, 2, 9, 0], getcharpos('v')) + let g:VisualStartPos = [] + exe "normal 3Gv$\o\" + call assert_equal([[0, 3, 1, 0], [0, 3, 1, 0]], g:VisualStartPos) + let g:VisualStartPos = [] + exe "normal 1Gv$\o\" + call assert_equal([[0, 1, 1, 0], [0, 1, 1, 0]], g:VisualStartPos) + vunmap + + %bw! +endfunc + +" Test for the setcharpos() function +func Test_setcharpos() + call assert_equal(-1, setcharpos('.', test_null_list())) + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + call setcharpos('.', [0, 1, 1, 0]) + call assert_equal([1, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 2, 7, 0]) + call assert_equal([2, 9], [line('.'), col('.')]) + call setcharpos('.', [0, 3, 4, 0]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 3, 1, 0]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 4, 0, 0]) + call assert_equal([4, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 4, 20, 0]) + call assert_equal([4, 9], [line('.'), col('.')]) + + " Test for mark + delmarks m + call setcharpos("'m", [0, 2, 9, 0]) + normal `m + call assert_equal([2, 11], [line('.'), col('.')]) + + %bw! + call assert_equal(-1, setcharpos('.', [10, 3, 1, 0])) +endfunc + +func SaveVisualStartCharCol() + call add(g:VisualStartCol, charcol('v')) + return '' +endfunc + +" Test for the charcol() function +func Test_charcol() + call assert_fails('call charcol({})', 'E731:') + call assert_equal(0, charcol(0)) + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + + " Test for '.' and '$' + normal 1G + call assert_equal(1, charcol('.')) + call assert_equal(1, charcol('$')) + normal 2G6l + call assert_equal(7, charcol('.')) + call assert_equal(10, charcol('$')) + normal 3G$ + call assert_equal(1, charcol('.')) + call assert_equal(2, charcol('$')) + normal 4G$ + call assert_equal(9, charcol('.')) + call assert_equal(10, charcol('$')) + + " Test for [lnum, '$'] + call assert_equal(1, charcol([1, '$'])) + call assert_equal(10, charcol([2, '$'])) + call assert_equal(2, charcol([3, '$'])) + call assert_equal(0, charcol([5, '$'])) + + " Test for a mark + normal 2G7lmmgg + call assert_equal(8, charcol("'m")) + delmarks m + call assert_equal(0, charcol("'m")) + + " Test for the visual start column + vnoremap SaveVisualStartCharCol() + let g:VisualStartCol = [] + exe "normal 2G6lv$\ohh\o\" + call assert_equal([7, 9, 5], g:VisualStartCol) + call assert_equal(9, charcol('v')) + let g:VisualStartCol = [] + exe "normal 3Gv$\o\" + call assert_equal([1, 1], g:VisualStartCol) + let g:VisualStartCol = [] + exe "normal 1Gv$\o\" + call assert_equal([1, 1], g:VisualStartCol) + vunmap + + %bw! +endfunc + +" Test for getcursorcharpos() +func Test_getcursorcharpos() + call assert_equal(getcursorcharpos(), getcursorcharpos(0)) + call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(-1)) + call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(1999)) + + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + normal 1G9l + call assert_equal([0, 1, 1, 0, 1], getcursorcharpos()) + normal 2G9l + call assert_equal([0, 2, 9, 0, 14], getcursorcharpos()) + normal 3G9l + call assert_equal([0, 3, 1, 0, 1], getcursorcharpos()) + normal 4G9l + call assert_equal([0, 4, 9, 0, 9], getcursorcharpos()) + + let winid = win_getid() + normal 2G5l + wincmd w + call assert_equal([0, 2, 6, 0, 11], getcursorcharpos(winid)) + %bw! +endfunc + +" Test for setcursorcharpos() +func Test_setcursorcharpos() + call assert_fails('call setcursorcharpos(test_null_list())', 'E474:') + call assert_fails('call setcursorcharpos([1])', 'E474:') + call assert_fails('call setcursorcharpos([1, 1, 1, 1, 1])', 'E474:') + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + normal G + call setcursorcharpos([1, 1]) + call assert_equal([1, 1], [line('.'), col('.')]) + call setcursorcharpos([2, 7, 0]) + call assert_equal([2, 9], [line('.'), col('.')]) + call setcursorcharpos(3, 4) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcursorcharpos([3, 1]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcursorcharpos([4, 0, 0, 0]) + call assert_equal([4, 1], [line('.'), col('.')]) + call setcursorcharpos([4, 20]) + call assert_equal([4, 9], [line('.'), col('.')]) + normal 1G + call setcursorcharpos([100, 100, 100, 100]) + call assert_equal([4, 9], [line('.'), col('.')]) + normal 1G + call setcursorcharpos('$', 1) + call assert_equal([4, 1], [line('.'), col('.')]) + + %bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/typval.c b/src/typval.c --- a/src/typval.c +++ b/src/typval.c @@ -1579,7 +1579,7 @@ tv_get_lnum(typval_T *argvars) if (lnum <= 0) // no valid number, try using arg like line() { int fnum; - pos_T *fp = var2fpos(&argvars[0], TRUE, &fnum); + pos_T *fp = var2fpos(&argvars[0], TRUE, &fnum, FALSE); if (fp != NULL) lnum = fp->lnum; diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2324, +/**/ 2323, /**/ 2322,