# HG changeset patch # User Bram Moolenaar # Date 1658769306 -7200 # Node ID 057c26b5c33ad2f4b3a836a0263623637c964bf1 # Parent 67f31c24291b3df2a5d5b1d4a1eca47b31d8753d patch 9.0.0067: cannot show virtual text Commit: https://github.com/vim/vim/commit/7f9969c559b51446632ac7e8f76cde07e7d0078d Author: Bram Moolenaar Date: Mon Jul 25 18:13:54 2022 +0100 patch 9.0.0067: cannot show virtual text Problem: Cannot show virtual text. Solution: Initial changes for virtual text support, using text properties. diff --git a/runtime/doc/textprop.txt b/runtime/doc/textprop.txt --- a/runtime/doc/textprop.txt +++ b/runtime/doc/textprop.txt @@ -137,7 +137,11 @@ prop_add({lnum}, {col}, {props}) bufnr buffer to add the property to; when omitted the current buffer is used id user defined ID for the property; must be a - number; when omitted zero is used + number, should be positive; when using "text" + then "id" must not be present and will be set + automatically to a negative number; otherwise + zero is used + text text to be displayed at {col} type name of the text property type All fields except "type" are optional. @@ -157,6 +161,17 @@ prop_add({lnum}, {col}, {props}) "type" will first be looked up in the buffer the property is added to. When not found, the global property types are used. If not found an error is given. + *virtual-text* + When "text" is used this text will be displayed at the start + location of the text property. The text of the buffer line + will be shifted to make room. This is called "virtual text". + The text will be displayed but it is not part of the actual + buffer line, the cursor cannot be placed on it. A mouse click + in the text will move the cursor to the first character after + the text. + A negative "id" will be chosen and is returned. Once a + property with "text" has been added for a buffer then using a + negative "id" for any other property will give an error. Can also be used as a |method|: > GetLnum()->prop_add(col, props) @@ -181,6 +196,9 @@ prop_add_list({props}, [[{lnum}, {col}, two items {end-lnum} and {end-col} specify the position just after the text. + It is not possible to add a text property with a "text" field + here. + Example: call prop_add_list(#{type: 'MyProp', id: 2}, \ [[1, 4, 1, 7], diff --git a/src/beval.c b/src/beval.c --- a/src/beval.c +++ b/src/beval.c @@ -47,7 +47,7 @@ find_word_under_cursor( { // Not past end of the file. lbuf = ml_get_buf(wp->w_buffer, lnum, FALSE); - if (col <= win_linetabsize(wp, lbuf, (colnr_T)MAXCOL)) + if (col <= win_linetabsize(wp, lnum, lbuf, (colnr_T)MAXCOL)) { // Not past end of line. if (getword) diff --git a/src/charset.c b/src/charset.c --- a/src/charset.c +++ b/src/charset.c @@ -12,8 +12,8 @@ #if defined(HAVE_WCHAR_H) # include // for towupper() and towlower() #endif -static int win_nolbr_chartabsize(win_T *wp, char_u *s, colnr_T col, int *headp); +static int win_nolbr_chartabsize(chartabsize_T *cts, int *headp); static unsigned nr2hex(unsigned c); static int chartab_initialized = FALSE; @@ -737,8 +737,9 @@ win_chartabsize(win_T *wp, char_u *p, co #endif /* - * Return the number of characters the string 's' will take on the screen, + * Return the number of characters the string "s" will take on the screen, * taking into account the size of a tab. + * Does not handle text properties, since "s" is not a buffer line. */ int linetabsize(char_u *s) @@ -747,32 +748,34 @@ linetabsize(char_u *s) } /* - * Like linetabsize(), but starting at column "startcol". + * Like linetabsize(), but "s" starts at column "startcol". */ int linetabsize_col(int startcol, char_u *s) { - colnr_T col = startcol; - char_u *line = s; // pointer to start of line, for breakindent + chartabsize_T cts; - while (*s != NUL) - col += lbr_chartabsize_adv(line, &s, col); - return (int)col; + init_chartabsize_arg(&cts, curwin, 0, startcol, s, s); + while (*cts.cts_ptr != NUL) + cts.cts_vcol += lbr_chartabsize_adv(&cts); + clear_chartabsize_arg(&cts); + return (int)cts.cts_vcol; } /* * Like linetabsize(), but for a given window instead of the current one. */ int -win_linetabsize(win_T *wp, char_u *line, colnr_T len) +win_linetabsize(win_T *wp, linenr_T lnum, char_u *line, colnr_T len) { - colnr_T col = 0; - char_u *s; + chartabsize_T cts; - for (s = line; *s != NUL && (len == MAXCOL || s < line + len); - MB_PTR_ADV(s)) - col += win_lbr_chartabsize(wp, line, s, col, NULL); - return (int)col; + init_chartabsize_arg(&cts, wp, lnum, 0, line, line); + for ( ; *cts.cts_ptr != NUL && (len == MAXCOL || cts.cts_ptr < line + len); + MB_PTR_ADV(cts.cts_ptr)) + cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); + clear_chartabsize_arg(&cts); + return (int)cts.cts_vcol; } /* @@ -893,25 +896,101 @@ vim_isprintc_strict(int c) } /* - * like chartabsize(), but also check for line breaks on the screen + * Prepare the structure passed to chartabsize functions. + * "line" is the start of the line, "ptr" is the first relevant character. + * When "lnum" is zero do not use text properties that insert text. + */ + void +init_chartabsize_arg( + chartabsize_T *cts, + win_T *wp, + linenr_T lnum, + colnr_T col, + char_u *line, + char_u *ptr) +{ + cts->cts_win = wp; + cts->cts_lnum = lnum; + cts->cts_vcol = col; + cts->cts_line = line; + cts->cts_ptr = ptr; +#ifdef FEAT_PROP_POPUP + cts->cts_text_prop_count = 0; + cts->cts_has_prop_with_text = FALSE; + cts->cts_cur_text_width = 0; + if (lnum > 0) + { + char_u *prop_start; + + cts->cts_text_prop_count = get_text_props(wp->w_buffer, lnum, + &prop_start, FALSE); + if (cts->cts_text_prop_count > 0) + { + // Make a copy of the properties, so that they are properly + // aligned. + cts->cts_text_props = ALLOC_MULT(textprop_T, + cts->cts_text_prop_count); + if (cts->cts_text_props == NULL) + cts->cts_text_prop_count = 0; + else + { + int i; + + mch_memmove(cts->cts_text_props, prop_start, + cts->cts_text_prop_count * sizeof(textprop_T)); + for (i = 0; i < cts->cts_text_prop_count; ++i) + if (cts->cts_text_props[i].tp_id < 0) + { + cts->cts_has_prop_with_text = TRUE; + break; + } + if (!cts->cts_has_prop_with_text) + { + // won't use the text properties, free them + vim_free(cts->cts_text_props); + cts->cts_text_prop_count = 0; + } + } + } + } +#endif +} + +/* + * Free any allocated item in "cts". + */ + void +clear_chartabsize_arg(chartabsize_T *cts) +{ + if (cts->cts_text_prop_count > 0) + vim_free(cts->cts_text_props); +} + +/* + * Like chartabsize(), but also check for line breaks on the screen and text + * properties that insert text. */ int -lbr_chartabsize( - char_u *line UNUSED, // start of the line - unsigned char *s, - colnr_T col) +lbr_chartabsize(chartabsize_T *cts) { -#ifdef FEAT_LINEBREAK - if (!curwin->w_p_lbr && *get_showbreak_value(curwin) == NUL - && !curwin->w_p_bri) +#if defined(FEAT_LINEBREAK) || defined(FEAT_PROP_POPUP) + if (1 +# ifdef FEAT_LINEBREAK + && !curwin->w_p_lbr && *get_showbreak_value(curwin) == NUL + && !curwin->w_p_bri +# endif +# ifdef FEAT_PROP_POPUP + && !cts->cts_has_prop_with_text +#endif + ) { #endif if (curwin->w_p_wrap) - return win_nolbr_chartabsize(curwin, s, col, NULL); - RET_WIN_BUF_CHARTABSIZE(curwin, curbuf, s, col) -#ifdef FEAT_LINEBREAK + return win_nolbr_chartabsize(cts, NULL); + RET_WIN_BUF_CHARTABSIZE(curwin, curbuf, cts->cts_ptr, cts->cts_vcol) +#if defined(FEAT_LINEBREAK) || defined(FEAT_PROP_POPUP) } - return win_lbr_chartabsize(curwin, line == NULL ? s : line, s, col, NULL); + return win_lbr_chartabsize(cts, NULL); #endif } @@ -919,19 +998,19 @@ lbr_chartabsize( * Call lbr_chartabsize() and advance the pointer. */ int -lbr_chartabsize_adv( - char_u *line, // start of the line - char_u **s, - colnr_T col) +lbr_chartabsize_adv(chartabsize_T *cts) { int retval; - retval = lbr_chartabsize(line, *s, col); - MB_PTR_ADV(*s); + retval = lbr_chartabsize(cts); + MB_PTR_ADV(cts->cts_ptr); return retval; } /* + * Return the screen size of the character indicated by "cts". + * "cts->cts_cur_text_width" is set to the extra size for a text property that + * inserts text. * This function is used very often, keep it fast!!!! * * If "headp" not NULL, set *headp to the size of what we for 'showbreak' @@ -940,17 +1019,18 @@ lbr_chartabsize_adv( */ int win_lbr_chartabsize( - win_T *wp, - char_u *line UNUSED, // start of the line - char_u *s, - colnr_T col, - int *headp UNUSED) + chartabsize_T *cts, + int *headp UNUSED) { + win_T *wp = cts->cts_win; + char_u *line = cts->cts_line; // start of the line + char_u *s = cts->cts_ptr; + colnr_T vcol = cts->cts_vcol; #ifdef FEAT_LINEBREAK int c; int size; colnr_T col2; - colnr_T col_adj = 0; // col + screen size of tab + colnr_T col_adj = 0; // vcol + screen size of tab colnr_T colmax; int added; int mb_added = 0; @@ -959,23 +1039,66 @@ win_lbr_chartabsize( int tab_corr = (*s == TAB); int n; char_u *sbr; +#endif +#if defined(FEAT_PROP_POPUP) + cts->cts_cur_text_width = 0; +#endif + +#if defined(FEAT_LINEBREAK) || defined(FEAT_PROP_POPUP) /* - * No 'linebreak', 'showbreak' and 'breakindent': return quickly. + * No 'linebreak', 'showbreak', 'breakindent' and text properties that + * insert text: return quickly. */ - if (!wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL) + if (1 +# ifdef FEAT_LINEBREAK + && !wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL +# endif +# ifdef FEAT_PROP_POPUP + && !cts->cts_has_prop_with_text +# endif + ) #endif { if (wp->w_p_wrap) - return win_nolbr_chartabsize(wp, s, col, headp); - RET_WIN_BUF_CHARTABSIZE(wp, wp->w_buffer, s, col) + return win_nolbr_chartabsize(cts, headp); + RET_WIN_BUF_CHARTABSIZE(wp, wp->w_buffer, s, vcol) } -#ifdef FEAT_LINEBREAK +#if defined(FEAT_LINEBREAK) || defined(FEAT_PROP_POPUP) /* - * First get normal size, without 'linebreak' + * First get the normal size, without 'linebreak' or text properties */ - size = win_chartabsize(wp, s, col); + size = win_chartabsize(wp, s, vcol); + +# ifdef FEAT_PROP_POPUP + if (cts->cts_has_prop_with_text) + { + int i; + int col = (int)(s - line); + + for (i = 0; i < cts->cts_text_prop_count; ++i) + { + textprop_T *tp = cts->cts_text_props + i; + + if (tp->tp_id < 0 + && tp->tp_col - 1 >= col && tp->tp_col - 1 < col + size + && -tp->tp_id <= wp->w_buffer->b_textprop_text.ga_len) + { + char_u *p = ((char_u **)wp->w_buffer->b_textprop_text.ga_data)[ + -tp->tp_id - 1]; + // TODO: count screen cells + cts->cts_cur_text_width = STRLEN(p); + size += cts->cts_cur_text_width; + break; + } + if (tp->tp_col - 1 > col) + break; + } + } +# endif + +# ifdef FEAT_LINEBREAK c = *s; if (tab_corr) col_adj = size - 1; @@ -995,14 +1118,14 @@ win_lbr_chartabsize( * non-blank after a blank. */ numberextra = win_col_off(wp); - col2 = col; + col2 = vcol; colmax = (colnr_T)(wp->w_width - numberextra - col_adj); - if (col >= colmax) + if (vcol >= colmax) { colmax += col_adj; n = colmax + win_col_off2(wp); if (n > 0) - colmax += (((col - colmax) / n) + 1) * n - col_adj; + colmax += (((vcol - colmax) / n) + 1) * n - col_adj; } for (;;) @@ -1013,19 +1136,19 @@ win_lbr_chartabsize( if (!(c != NUL && (VIM_ISBREAK(c) || (!VIM_ISBREAK(c) - && (col2 == col || !VIM_ISBREAK((int)*ps)))))) + && (col2 == vcol || !VIM_ISBREAK((int)*ps)))))) break; col2 += win_chartabsize(wp, s, col2); if (col2 >= colmax) // doesn't fit { - size = colmax - col + col_adj; + size = colmax - vcol + col_adj; break; } } } else if (has_mbyte && size == 2 && MB_BYTE2LEN(*s) > 1 - && wp->w_p_wrap && in_win_border(wp, col)) + && wp->w_p_wrap && in_win_border(wp, vcol)) { ++size; // Count the ">" in the last column. mb_added = 1; @@ -1039,33 +1162,33 @@ win_lbr_chartabsize( */ added = 0; sbr = c == NUL ? empty_option : get_showbreak_value(wp); - if ((*sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && col != 0) + if ((*sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && vcol != 0) { colnr_T sbrlen = 0; int numberwidth = win_col_off(wp); numberextra = numberwidth; - col += numberextra + mb_added; - if (col >= (colnr_T)wp->w_width) + vcol += numberextra + mb_added; + if (vcol >= (colnr_T)wp->w_width) { - col -= wp->w_width; + vcol -= wp->w_width; numberextra = wp->w_width - (numberextra - win_col_off2(wp)); - if (col >= numberextra && numberextra > 0) - col %= numberextra; + if (vcol >= numberextra && numberextra > 0) + vcol %= numberextra; if (*sbr != NUL) { sbrlen = (colnr_T)MB_CHARLEN(sbr); - if (col >= sbrlen) - col -= sbrlen; + if (vcol >= sbrlen) + vcol -= sbrlen; } - if (col >= numberextra && numberextra > 0) - col = col % numberextra; - else if (col > 0 && numberextra > 0) - col += numberwidth - win_col_off2(wp); + if (vcol >= numberextra && numberextra > 0) + vcol = vcol % numberextra; + else if (vcol > 0 && numberextra > 0) + vcol += numberwidth - win_col_off2(wp); numberwidth -= win_col_off2(wp); } - if (col == 0 || col + size + sbrlen > (colnr_T)wp->w_width) + if (vcol == 0 || vcol + size + sbrlen > (colnr_T)wp->w_width) { added = 0; if (*sbr != NUL) @@ -1074,8 +1197,8 @@ win_lbr_chartabsize( { // calculate effective window width int width = (colnr_T)wp->w_width - sbrlen - numberwidth; - int prev_width = col - ? ((colnr_T)wp->w_width - (sbrlen + col)) : 0; + int prev_width = vcol + ? ((colnr_T)wp->w_width - (sbrlen + vcol)) : 0; if (width <= 0) width = (colnr_T)1; @@ -1091,28 +1214,32 @@ win_lbr_chartabsize( added += get_breakindent_win(wp, line); size += added; - if (col != 0) + if (vcol != 0) added = 0; } } if (headp != NULL) *headp = added + mb_added; return size; +# endif #endif } /* - * Like win_lbr_chartabsize(), except that we know 'linebreak' is off and - * 'wrap' is on. This means we need to check for a double-byte character that - * doesn't fit at the end of the screen line. + * Like win_lbr_chartabsize(), except that we know 'linebreak' is off, 'wrap' + * is on and there are no properties that insert text. This means we need to + * check for a double-byte character that doesn't fit at the end of the screen + * line. + * Only uses "cts_win", "cts_ptr" and "cts_vcol" from "cts". */ static int win_nolbr_chartabsize( - win_T *wp, - char_u *s, - colnr_T col, - int *headp) + chartabsize_T *cts, + int *headp) { + win_T *wp = cts->cts_win; + char_u *s = cts->cts_ptr; + colnr_T col = cts->cts_vcol; int n; if (*s == TAB && (!wp->w_p_list || wp->w_lcs_chars.tab1)) @@ -1187,6 +1314,7 @@ getvcol( #endif int ts = wp->w_buffer->b_p_ts; int c; + chartabsize_T cts; vcol = 0; line = ptr = ml_get_buf(wp->w_buffer, pos->lnum, FALSE); @@ -1209,16 +1337,21 @@ getvcol( posptr -= (*mb_head_off)(line, posptr); } + init_chartabsize_arg(&cts, wp, pos->lnum, 0, line, line); + /* * This function is used very often, do some speed optimizations. * When 'list', 'linebreak', 'showbreak' and 'breakindent' are not set - * use a simple loop. + * and there are no text properties with "text" use a simple loop. * Also use this when 'list' is set but tabs take their normal size. */ if ((!wp->w_p_list || wp->w_lcs_chars.tab1 != NUL) #ifdef FEAT_LINEBREAK && !wp->w_p_lbr && *get_showbreak_value(wp) == NUL && !wp->w_p_bri #endif +#ifdef FEAT_PROP_POPUP + && !cts.cts_has_prop_with_text +#endif ) { for (;;) @@ -1274,29 +1407,39 @@ getvcol( { for (;;) { - // A tab gets expanded, depending on the current column + // A tab gets expanded, depending on the current column. + // Other things also take up space. head = 0; - incr = win_lbr_chartabsize(wp, line, ptr, vcol, &head); + incr = win_lbr_chartabsize(&cts, &head); // make sure we don't go past the end of the line - if (*ptr == NUL) + if (*cts.cts_ptr == NUL) { incr = 1; // NUL at end of line only takes one column break; } - if (posptr != NULL && ptr >= posptr) // character at pos->col + if (posptr != NULL && cts.cts_ptr >= posptr) + // character at pos->col break; - vcol += incr; - MB_PTR_ADV(ptr); + cts.cts_vcol += incr; + MB_PTR_ADV(cts.cts_ptr); } + vcol = cts.cts_vcol; + ptr = cts.cts_ptr; } + clear_chartabsize_arg(&cts); + if (start != NULL) *start = vcol + head; if (end != NULL) *end = vcol + incr - 1; if (cursor != NULL) { +#ifdef FEAT_PROP_POPUP + // cursor is after inserted text + vcol += cts.cts_cur_text_width; +#endif if (*ptr == TAB && (State & MODE_NORMAL) && !wp->w_p_list diff --git a/src/drawline.c b/src/drawline.c --- a/src/drawline.c +++ b/src/drawline.c @@ -326,6 +326,7 @@ win_line( int text_props_active = 0; proptype_T *text_prop_type = NULL; int text_prop_attr = 0; + int text_prop_id = 0; // active property ID int text_prop_combine = FALSE; #endif #ifdef FEAT_SPELL @@ -816,15 +817,21 @@ win_line( v = wp->w_leftcol; if (v > 0 && !number_only) { - char_u *prev_ptr = ptr; + char_u *prev_ptr = ptr; + chartabsize_T cts; + int charsize; - while (vcol < v && *ptr != NUL) + init_chartabsize_arg(&cts, wp, lnum, vcol, line, ptr); + while (cts.cts_vcol < v && *cts.cts_ptr != NUL) { - c = win_lbr_chartabsize(wp, line, ptr, (colnr_T)vcol, NULL); - vcol += c; - prev_ptr = ptr; - MB_PTR_ADV(ptr); + charsize = win_lbr_chartabsize(&cts, NULL); + cts.cts_vcol += charsize; + prev_ptr = cts.cts_ptr; + MB_PTR_ADV(cts.cts_ptr); } + vcol = cts.cts_vcol; + ptr = cts.cts_ptr; + clear_chartabsize_arg(&cts); // When: // - 'cuc' is set, or @@ -844,11 +851,11 @@ win_line( // that character but skip the first few screen characters. if (vcol > v) { - vcol -= c; + vcol -= charsize; ptr = prev_ptr; // If the character fits on the screen, don't need to skip it. // Except for a TAB. - if (( (*mb_ptr2cells)(ptr) >= c || *ptr == TAB) && col == 0) + if (( (*mb_ptr2cells)(ptr) >= charsize || *ptr == TAB) && col == 0) n_skip = v - vcol; } @@ -1476,8 +1483,12 @@ win_line( text_prop_attr = 0; text_prop_combine = FALSE; text_prop_type = NULL; + text_prop_id = 0; if (text_props_active > 0) { + int used_tpi; + int used_attr = 0; + // Sort the properties on priority and/or starting last. // Then combine the attributes, highest priority last. current_text_props = text_props; @@ -1491,16 +1502,44 @@ win_line( proptype_T *pt = text_prop_type_by_id( wp->w_buffer, text_props[tpi].tp_type); - if (pt != NULL && pt->pt_hl_id > 0) + if (pt != NULL && pt->pt_hl_id > 0 + && text_props[tpi].tp_id != -MAXCOL) { - int pt_attr = syn_id2attr(pt->pt_hl_id); - + used_attr = syn_id2attr(pt->pt_hl_id); text_prop_type = pt; text_prop_attr = - hl_combine_attr(text_prop_attr, pt_attr); + hl_combine_attr(text_prop_attr, used_attr); text_prop_combine = pt->pt_flags & PT_FLAG_COMBINE; + text_prop_id = text_props[tpi].tp_id; + used_tpi = tpi; } } + if (n_extra == 0 && text_prop_id < 0 + && -text_prop_id + <= wp->w_buffer->b_textprop_text.ga_len) + { + char_u *p = ((char_u **)wp->w_buffer + ->b_textprop_text.ga_data)[ + -text_prop_id - 1]; + if (p != NULL) + { + p_extra = p; + n_extra = STRLEN(p); + extra_attr = used_attr; + n_attr = n_extra; + text_prop_attr = 0; + + // If the cursor is on or after this position, + // move it forward. + if (wp == curwin + && lnum == curwin->w_cursor.lnum + && curwin->w_cursor.col >= vcol) + curwin->w_cursor.col += n_extra; + } + // reset the ID in the copy to avoid it being used + // again + text_props[used_tpi].tp_id = -MAXCOL; + } } } #endif @@ -2025,10 +2064,10 @@ win_line( int mb_off = has_mbyte ? (*mb_head_off)(line, ptr - 1) : 0; char_u *p = ptr - (mb_off + 1); + chartabsize_T cts; - // TODO: is passing p for start of the line OK? - n_extra = win_lbr_chartabsize(wp, line, p, (colnr_T)vcol, - NULL) - 1; + init_chartabsize_arg(&cts, wp, lnum, vcol, line, p); + n_extra = win_lbr_chartabsize(&cts, NULL) - 1; // We have just drawn the showbreak value, no need to add // space for it again. @@ -2069,6 +2108,7 @@ win_line( if (!wp->w_p_list) c = ' '; } + clear_chartabsize_arg(&cts); } #endif diff --git a/src/edit.c b/src/edit.c --- a/src/edit.c +++ b/src/edit.c @@ -4905,6 +4905,8 @@ ins_tab(void) colnr_T want_vcol, vcol; int change_col = -1; int save_list = curwin->w_p_list; + char_u *tab = (char_u *)"\t"; + chartabsize_T cts; /* * Get the current line. For MODE_VREPLACE state, don't make real @@ -4950,12 +4952,14 @@ ins_tab(void) getvcol(curwin, &fpos, &vcol, NULL, NULL); getvcol(curwin, cursor, &want_vcol, NULL, NULL); + init_chartabsize_arg(&cts, curwin, 0, vcol, tab, tab); + // Use as many TABs as possible. Beware of 'breakindent', 'showbreak' // and 'linebreak' adding extra virtual columns. while (VIM_ISWHITE(*ptr)) { - i = lbr_chartabsize(NULL, (char_u *)"\t", vcol); - if (vcol + i > want_vcol) + i = lbr_chartabsize(&cts); + if (cts.cts_vcol + i > want_vcol) break; if (*ptr != TAB) { @@ -4970,21 +4974,27 @@ ins_tab(void) } ++fpos.col; ++ptr; - vcol += i; + cts.cts_vcol += i; } + vcol = cts.cts_vcol; + clear_chartabsize_arg(&cts); if (change_col >= 0) { - int repl_off = 0; - char_u *line = ptr; + int repl_off = 0; // Skip over the spaces we need. - while (vcol < want_vcol && *ptr == ' ') + init_chartabsize_arg(&cts, curwin, 0, vcol, ptr, ptr); + while (cts.cts_vcol < want_vcol && *cts.cts_ptr == ' ') { - vcol += lbr_chartabsize(line, ptr, vcol); - ++ptr; + cts.cts_vcol += lbr_chartabsize(&cts); + ++cts.cts_ptr; ++repl_off; } + ptr = cts.cts_ptr; + vcol = cts.cts_vcol; + clear_chartabsize_arg(&cts); + if (vcol > want_vcol) { // Must have a char with 'showbreak' just before it. @@ -5220,10 +5230,10 @@ ins_digraph(void) int ins_copychar(linenr_T lnum) { - int c; - int temp; - char_u *ptr, *prev_ptr; - char_u *line; + int c; + char_u *ptr, *prev_ptr; + char_u *line; + chartabsize_T cts; if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { @@ -5233,16 +5243,19 @@ ins_copychar(linenr_T lnum) // try to advance to the cursor column validate_virtcol(); - temp = 0; - line = ptr = ml_get(lnum); - prev_ptr = ptr; - while ((colnr_T)temp < curwin->w_virtcol && *ptr != NUL) + line = ml_get(lnum); + prev_ptr = line; + init_chartabsize_arg(&cts, curwin, lnum, 0, line, line); + while (cts.cts_vcol < curwin->w_virtcol && *cts.cts_ptr != NUL) { - prev_ptr = ptr; - temp += lbr_chartabsize_adv(line, &ptr, (colnr_T)temp); + prev_ptr = cts.cts_ptr; + cts.cts_vcol += lbr_chartabsize_adv(&cts); } - if ((colnr_T)temp > curwin->w_virtcol) + if (cts.cts_vcol > curwin->w_virtcol) ptr = prev_ptr; + else + ptr = cts.cts_ptr; + clear_chartabsize_arg(&cts); c = (*mb_ptr2char)(ptr); if (c == NUL) diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3310,3 +3310,7 @@ EXTERN char e_invalid_argument_nr[] EXTERN char e_cmdline_window_already_open[] INIT(= N_("E1292: Command-line window is already open")); #endif +#ifdef FEAT_PROP_POPUP +EXTERN char e_cannot_use_negative_id_after_adding_textprop_with_text[] + INIT(= N_("E1291: Cannot use a negative id after adding a textprop with text")); +#endif diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -2218,7 +2218,7 @@ static funcentry_T global_functions[] = {"prompt_setprompt", 2, 2, FEARG_1, arg2_buffer_string, ret_void, JOB_FUNC(f_prompt_setprompt)}, {"prop_add", 3, 3, FEARG_1, arg3_number_number_dict, - ret_void, PROP_FUNC(f_prop_add)}, + ret_number, PROP_FUNC(f_prop_add)}, {"prop_add_list", 2, 2, FEARG_1, arg2_dict_any_list_any, ret_void, PROP_FUNC(f_prop_add_list)}, {"prop_clear", 1, 3, FEARG_1, arg3_number_number_dict, diff --git a/src/getchar.c b/src/getchar.c --- a/src/getchar.c +++ b/src/getchar.c @@ -3210,7 +3210,7 @@ vgetorpeek(int advance) && (c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, 3, 25L)) == 0) { - colnr_T col = 0, vcol; + colnr_T col = 0; char_u *ptr; if (mode_displayed) @@ -3242,24 +3242,30 @@ vgetorpeek(int advance) { if (did_ai) { + chartabsize_T cts; + /* * We are expecting to truncate the trailing * white-space, so find the last non-white * character -- webb */ - col = vcol = curwin->w_wcol = 0; + curwin->w_wcol = 0; ptr = ml_get_curline(); - while (col < curwin->w_cursor.col) + init_chartabsize_arg(&cts, curwin, + curwin->w_cursor.lnum, 0, ptr, ptr); + while (cts.cts_ptr < ptr + curwin->w_cursor.col) { - if (!VIM_ISWHITE(ptr[col])) - curwin->w_wcol = vcol; - vcol += lbr_chartabsize(ptr, ptr + col, - vcol); + if (!VIM_ISWHITE(*cts.cts_ptr)) + curwin->w_wcol = cts.cts_vcol; + cts.cts_vcol += lbr_chartabsize(&cts); if (has_mbyte) - col += (*mb_ptr2len)(ptr + col); + cts.cts_ptr += + (*mb_ptr2len)(cts.cts_ptr); else - ++col; + ++cts.cts_ptr; } + clear_chartabsize_arg(&cts); + curwin->w_wrow = curwin->w_cline_row + curwin->w_wcol / curwin->w_width; curwin->w_wcol %= curwin->w_width; diff --git a/src/indent.c b/src/indent.c --- a/src/indent.c +++ b/src/indent.c @@ -1350,26 +1350,28 @@ change_indent( new_cursor_col = curwin->w_cursor.col; else { + chartabsize_T cts; + // Compute the screen column where the cursor should be. vcol = get_indent() - vcol; curwin->w_virtcol = (colnr_T)((vcol < 0) ? 0 : vcol); // Advance the cursor until we reach the right screen column. - vcol = last_vcol = 0; - new_cursor_col = -1; + last_vcol = 0; ptr = ml_get_curline(); - while (vcol <= (int)curwin->w_virtcol) + init_chartabsize_arg(&cts, curwin, 0, 0, ptr, ptr); + while (cts.cts_vcol <= (int)curwin->w_virtcol) { - last_vcol = vcol; - if (has_mbyte && new_cursor_col >= 0) - new_cursor_col += (*mb_ptr2len)(ptr + new_cursor_col); - else - ++new_cursor_col; - if (ptr[new_cursor_col] == NUL) + last_vcol = cts.cts_vcol; + if (cts.cts_vcol > 0) + MB_PTR_ADV(cts.cts_ptr); + if (*cts.cts_ptr == NUL) break; - vcol += lbr_chartabsize(ptr, ptr + new_cursor_col, (colnr_T)vcol); + cts.cts_vcol += lbr_chartabsize(&cts); } vcol = last_vcol; + new_cursor_col = cts.cts_ptr - cts.cts_line; + clear_chartabsize_arg(&cts); // May need to insert spaces to be able to position the cursor on // the right screen column. @@ -2064,14 +2066,18 @@ get_lisp_indent(void) amount = 2; else { - char_u *line = that; + char_u *line = that; + chartabsize_T cts; - amount = 0; - while (*that && col) + init_chartabsize_arg(&cts, curwin, pos->lnum, 0, line, line); + while (*cts.cts_ptr != NUL && col > 0) { - amount += lbr_chartabsize_adv(line, &that, (colnr_T)amount); + cts.cts_vcol += lbr_chartabsize_adv(&cts); col--; } + amount = cts.cts_vcol; + that = cts.cts_ptr; + clear_chartabsize_arg(&cts); // Some keywords require "body" indenting rules (the // non-standard-lisp ones are Scheme special forms): @@ -2091,11 +2097,16 @@ get_lisp_indent(void) } firsttry = amount; - while (VIM_ISWHITE(*that)) + init_chartabsize_arg(&cts, curwin, (colnr_T)(that - line), + amount, line, that); + while (VIM_ISWHITE(*cts.cts_ptr)) { - amount += lbr_chartabsize(line, that, (colnr_T)amount); - ++that; + cts.cts_vcol += lbr_chartabsize(&cts); + ++cts.cts_ptr; } + that = cts.cts_ptr; + amount = cts.cts_vcol; + clear_chartabsize_arg(&cts); if (*that && *that != ';') // not a comment line { @@ -2107,42 +2118,47 @@ get_lisp_indent(void) parencount = 0; quotecount = 0; + init_chartabsize_arg(&cts, curwin, + (colnr_T)(that - line), amount, line, that); if (vi_lisp || (*that != '"' && *that != '\'' && *that != '#' && (*that < '0' || *that > '9'))) { - while (*that - && (!VIM_ISWHITE(*that) + while (*cts.cts_ptr + && (!VIM_ISWHITE(*cts.cts_ptr) || quotecount || parencount) - && (!((*that == '(' || *that == '[') + && (!((*cts.cts_ptr == '(' + || *cts.cts_ptr == '[') && !quotecount && !parencount && vi_lisp))) { - if (*that == '"') + if (*cts.cts_ptr == '"') quotecount = !quotecount; - if ((*that == '(' || *that == '[') + if ((*cts.cts_ptr == '(' || *cts.cts_ptr == '[') && !quotecount) ++parencount; - if ((*that == ')' || *that == ']') + if ((*cts.cts_ptr == ')' || *cts.cts_ptr == ']') && !quotecount) --parencount; - if (*that == '\\' && *(that+1) != NUL) - amount += lbr_chartabsize_adv( - line, &that, (colnr_T)amount); - amount += lbr_chartabsize_adv( - line, &that, (colnr_T)amount); + if (*cts.cts_ptr == '\\' + && *(cts.cts_ptr+1) != NUL) + cts.cts_vcol += lbr_chartabsize_adv(&cts); + cts.cts_vcol += lbr_chartabsize_adv(&cts); } } - while (VIM_ISWHITE(*that)) + while (VIM_ISWHITE(*cts.cts_ptr)) { - amount += lbr_chartabsize( - line, that, (colnr_T)amount); - that++; + cts.cts_vcol += lbr_chartabsize(&cts); + ++cts.cts_ptr; } + that = cts.cts_ptr; + amount = cts.cts_vcol; + clear_chartabsize_arg(&cts); + if (!*that || *that == ';') amount = firsttry; } diff --git a/src/misc1.c b/src/misc1.c --- a/src/misc1.c +++ b/src/misc1.c @@ -397,7 +397,7 @@ plines_win_nofold(win_T *wp, linenr_T ln s = ml_get_buf(wp->w_buffer, lnum, FALSE); if (*s == NUL) // empty line return 1; - col = win_linetabsize(wp, s, (colnr_T)MAXCOL); + col = win_linetabsize(wp, lnum, s, (colnr_T)MAXCOL); /* * If list mode is on, then the '$' at the end of the line may take up one @@ -427,10 +427,10 @@ plines_win_nofold(win_T *wp, linenr_T ln plines_win_col(win_T *wp, linenr_T lnum, long column) { long col; - char_u *s; int lines = 0; int width; char_u *line; + chartabsize_T cts; #ifdef FEAT_DIFF // Check for filler lines above this buffer line. When folded the result @@ -444,25 +444,27 @@ plines_win_col(win_T *wp, linenr_T lnum, if (wp->w_width == 0) return lines + 1; - line = s = ml_get_buf(wp->w_buffer, lnum, FALSE); + line = ml_get_buf(wp->w_buffer, lnum, FALSE); - col = 0; - while (*s != NUL && --column >= 0) + init_chartabsize_arg(&cts, wp, lnum, 0, line, line); + while (*cts.cts_ptr != NUL && --column >= 0) { - col += win_lbr_chartabsize(wp, line, s, (colnr_T)col, NULL); - MB_PTR_ADV(s); + cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); + MB_PTR_ADV(cts.cts_ptr); } /* - * If *s is a TAB, and the TAB is not displayed as ^I, and we're not in - * MODE_INSERT state, then col must be adjusted so that it represents the - * last screen position of the TAB. This only fixes an error when the TAB - * wraps from one screen line to the next (when 'columns' is not a multiple - * of 'ts') -- webb. + * If *cts.cts_ptr is a TAB, and the TAB is not displayed as ^I, and we're + * not in MODE_INSERT state, then col must be adjusted so that it + * represents the last screen position of the TAB. This only fixes an + * error when the TAB wraps from one screen line to the next (when + * 'columns' is not a multiple of 'ts') -- webb. */ - if (*s == TAB && (State & MODE_NORMAL) + col = cts.cts_vcol; + if (*cts.cts_ptr == TAB && (State & MODE_NORMAL) && (!wp->w_p_list || wp->w_lcs_chars.tab1)) - col += win_lbr_chartabsize(wp, line, s, (colnr_T)col, NULL) - 1; + col += win_lbr_chartabsize(&cts, NULL) - 1; + clear_chartabsize_arg(&cts); /* * Add column offset for 'number', 'relativenumber', 'foldcolumn', etc. diff --git a/src/misc2.c b/src/misc2.c --- a/src/misc2.c +++ b/src/misc2.c @@ -128,7 +128,6 @@ coladvance2( { colnr_T wcol = wcol_arg; int idx; - char_u *ptr; char_u *line; colnr_T col = 0; int csize = 0; @@ -158,6 +157,7 @@ coladvance2( else { int width = curwin->w_width - win_col_off(curwin); + chartabsize_T cts; if (finetune && curwin->w_p_wrap @@ -180,19 +180,22 @@ coladvance2( } } - ptr = line; - while (col <= wcol && *ptr != NUL) + init_chartabsize_arg(&cts, curwin, pos->lnum, 0, line, line); + while (cts.cts_vcol <= wcol && *cts.cts_ptr != NUL) { // Count a tab for what it's worth (if list mode not on) #ifdef FEAT_LINEBREAK - csize = win_lbr_chartabsize(curwin, line, ptr, col, &head); - MB_PTR_ADV(ptr); + csize = win_lbr_chartabsize(&cts, &head); + MB_PTR_ADV(cts.cts_ptr); #else - csize = lbr_chartabsize_adv(line, &ptr, col); + csize = lbr_chartabsize_adv(&cts); #endif - col += csize; + cts.cts_vcol += csize; } - idx = (int)(ptr - line); + col = cts.cts_vcol; + idx = (int)(cts.cts_ptr - line); + clear_chartabsize_arg(&cts); + /* * Handle all the special cases. The virtual_active() check * is needed to ensure that a virtual position off the end of diff --git a/src/mouse.c b/src/mouse.c --- a/src/mouse.c +++ b/src/mouse.c @@ -3101,18 +3101,20 @@ mouse_find_win(int *rowp, int *colp, mou int vcol2col(win_T *wp, linenr_T lnum, int vcol) { - // try to advance to the specified column - int count = 0; - char_u *ptr; - char_u *line; + char_u *line; + chartabsize_T cts; - line = ptr = ml_get_buf(wp->w_buffer, lnum, FALSE); - while (count < vcol && *ptr != NUL) + // try to advance to the specified column + line = ml_get_buf(wp->w_buffer, lnum, FALSE); + init_chartabsize_arg(&cts, wp, lnum, 0, line, line); + while (cts.cts_vcol < vcol && *cts.cts_ptr != NUL) { - count += win_lbr_chartabsize(wp, line, ptr, count, NULL); - MB_PTR_ADV(ptr); + cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); + MB_PTR_ADV(cts.cts_ptr); } - return (int)(ptr - line); + clear_chartabsize_arg(&cts); + + return (int)(cts.cts_ptr - line); } #endif diff --git a/src/ops.c b/src/ops.c --- a/src/ops.c +++ b/src/ops.c @@ -307,7 +307,8 @@ shift_block(oparg_T *oap, int amount) if (!left) { - int tabs = 0, spaces = 0; + int tabs = 0, spaces = 0; + chartabsize_T cts; /* * 1. Get start vcol @@ -332,13 +333,20 @@ shift_block(oparg_T *oap, int amount) else ++bd.textstart; } - for ( ; VIM_ISWHITE(*bd.textstart); ) + + // TODO: is passing bd.textstart for start of the line OK? + init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, + bd.start_vcol, bd.textstart, bd.textstart); + for ( ; VIM_ISWHITE(*cts.cts_ptr); ) { - // TODO: is passing bd.textstart for start of the line OK? - incr = lbr_chartabsize_adv(bd.textstart, &bd.textstart, bd.start_vcol); + incr = lbr_chartabsize_adv(&cts); total += incr; - bd.start_vcol += incr; + cts.cts_vcol += incr; } + bd.textstart = cts.cts_ptr; + bd.start_vcol = cts.cts_vcol; + clear_chartabsize_arg(&cts); + // OK, now total=all the VWS reqd, and textstart points at the 1st // non-ws char in the block. #ifdef FEAT_VARTABS @@ -381,6 +389,7 @@ shift_block(oparg_T *oap, int amount) size_t shift_amount; char_u *non_white = bd.textstart; colnr_T non_white_col; + chartabsize_T cts; /* * Firstly, let's find the first non-whitespace character that is @@ -399,11 +408,16 @@ shift_block(oparg_T *oap, int amount) // The character's column is in "bd.start_vcol". non_white_col = bd.start_vcol; - while (VIM_ISWHITE(*non_white)) + init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, + non_white_col, bd.textstart, non_white); + while (VIM_ISWHITE(*cts.cts_ptr)) { - incr = lbr_chartabsize_adv(bd.textstart, &non_white, non_white_col); - non_white_col += incr; + incr = lbr_chartabsize_adv(&cts); + cts.cts_vcol += incr; } + non_white_col = cts.cts_vcol; + non_white = cts.cts_ptr; + clear_chartabsize_arg(&cts); block_space_width = non_white_col - oap->start_vcol; // We will shift by "total" or "block_space_width", whichever is less. @@ -423,18 +437,19 @@ shift_block(oparg_T *oap, int amount) // column number. if (bd.startspaces) verbatim_copy_width -= bd.start_char_vcols; - while (verbatim_copy_width < destination_col) + init_chartabsize_arg(&cts, curwin, 0, verbatim_copy_width, + bd.textstart, verbatim_copy_end); + while (cts.cts_vcol < destination_col) { - char_u *line = verbatim_copy_end; - - // TODO: is passing verbatim_copy_end for start of the line OK? - incr = lbr_chartabsize(line, verbatim_copy_end, - verbatim_copy_width); - if (verbatim_copy_width + incr > destination_col) + incr = lbr_chartabsize(&cts); + if (cts.cts_vcol + incr > destination_col) break; - verbatim_copy_width += incr; - MB_PTR_ADV(verbatim_copy_end); + cts.cts_vcol += incr; + MB_PTR_ADV(cts.cts_ptr); } + verbatim_copy_width = cts.cts_vcol; + verbatim_copy_end = cts.cts_ptr; + clear_chartabsize_arg(&cts); // If "destination_col" is different from the width of the initial // part of the line that will be copied, it means we encountered a tab @@ -703,8 +718,6 @@ op_delete(oparg_T *oap) * Put deleted text into register 1 and shift number registers if the * delete contains a line break, or when using a specific operator (Vi * compatible) - * Use the register name from before adjust_clip_reg() may have - * changed it. */ if (oap->motion_type == MLINE || oap->line_count > 1 || oap->use_reg_one) @@ -2213,6 +2226,7 @@ block_prep( char_u *line; char_u *prev_pstart; char_u *prev_pend; + chartabsize_T cts; #ifdef FEAT_LINEBREAK int lbr_saved = curwin->w_p_lbr; @@ -2232,14 +2246,14 @@ block_prep( bdp->start_char_vcols = 0; line = ml_get(lnum); - pstart = line; prev_pstart = line; - while (bdp->start_vcol < oap->start_vcol && *pstart) + init_chartabsize_arg(&cts, curwin, lnum, bdp->start_vcol, line, line); + while (cts.cts_vcol < oap->start_vcol && *cts.cts_ptr != NUL) { // Count a tab for what it's worth (if list mode not on) - incr = lbr_chartabsize(line, pstart, bdp->start_vcol); - bdp->start_vcol += incr; - if (VIM_ISWHITE(*pstart)) + incr = lbr_chartabsize(&cts); + cts.cts_vcol += incr; + if (VIM_ISWHITE(*cts.cts_ptr)) { bdp->pre_whitesp += incr; bdp->pre_whitesp_c++; @@ -2249,9 +2263,13 @@ block_prep( bdp->pre_whitesp = 0; bdp->pre_whitesp_c = 0; } - prev_pstart = pstart; - MB_PTR_ADV(pstart); + prev_pstart = cts.cts_ptr; + MB_PTR_ADV(cts.cts_ptr); } + bdp->start_vcol = cts.cts_vcol; + pstart = cts.cts_ptr; + clear_chartabsize_arg(&cts); + bdp->start_char_vcols = incr; if (bdp->start_vcol < oap->start_vcol) // line too short { @@ -2295,14 +2313,20 @@ block_prep( } else { + init_chartabsize_arg(&cts, curwin, lnum, bdp->end_vcol, + line, pend); prev_pend = pend; - while (bdp->end_vcol <= oap->end_vcol && *pend != NUL) + while (cts.cts_vcol <= oap->end_vcol && *cts.cts_ptr != NUL) { - // Count a tab for what it's worth (if list mode not on) - prev_pend = pend; - incr = lbr_chartabsize_adv(line, &pend, bdp->end_vcol); - bdp->end_vcol += incr; + // count a tab for what it's worth (if list mode not on) + prev_pend = cts.cts_ptr; + incr = lbr_chartabsize_adv(&cts); + cts.cts_vcol += incr; } + bdp->end_vcol = cts.cts_vcol; + pend = cts.cts_ptr; + clear_chartabsize_arg(&cts); + if (bdp->end_vcol <= oap->end_vcol && (!is_del || oap->op_type == OP_APPEND diff --git a/src/popupwin.c b/src/popupwin.c --- a/src/popupwin.c +++ b/src/popupwin.c @@ -1371,7 +1371,7 @@ popup_adjust_position(win_T *wp) // "margin_width" is added to "len" where it matters. if (wp->w_width < maxwidth) wp->w_width = maxwidth; - len = win_linetabsize(wp, ml_get_buf(wp->w_buffer, lnum, FALSE), + len = win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum, FALSE), (colnr_T)MAXCOL); wp->w_width = w_width; diff --git a/src/proto/charset.pro b/src/proto/charset.pro --- a/src/proto/charset.pro +++ b/src/proto/charset.pro @@ -17,7 +17,7 @@ int vim_strnsize(char_u *s, int len); int chartabsize(char_u *p, colnr_T col); int linetabsize(char_u *s); int linetabsize_col(int startcol, char_u *s); -int win_linetabsize(win_T *wp, char_u *line, colnr_T len); +int win_linetabsize(win_T *wp, linenr_T lnum, char_u *line, colnr_T len); int vim_isIDc(int c); int vim_isNormalIDc(int c); int vim_iswordc(int c); @@ -28,9 +28,11 @@ int vim_isfilec(int c); int vim_isfilec_or_wc(int c); int vim_isprintc(int c); int vim_isprintc_strict(int c); -int lbr_chartabsize(char_u *line, unsigned char *s, colnr_T col); -int lbr_chartabsize_adv(char_u *line, char_u **s, colnr_T col); -int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *headp); +void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum, colnr_T col, char_u *line, char_u *ptr); +void clear_chartabsize_arg(chartabsize_T *cts); +int lbr_chartabsize(chartabsize_T *cts); +int lbr_chartabsize_adv(chartabsize_T *cts); +int win_lbr_chartabsize(chartabsize_T *cts, int *headp); void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end); colnr_T getvcol_nolist(pos_T *posp); void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end); diff --git a/src/proto/textprop.pro b/src/proto/textprop.pro --- a/src/proto/textprop.pro +++ b/src/proto/textprop.pro @@ -2,7 +2,7 @@ int find_prop_type_id(char_u *name, buf_T *buf); void f_prop_add(typval_T *argvars, typval_T *rettv); void f_prop_add_list(typval_T *argvars, typval_T *rettv); -void prop_add_common(linenr_T start_lnum, colnr_T start_col, dict_T *dict, buf_T *default_buf, typval_T *dict_arg); +int prop_add_common(linenr_T start_lnum, colnr_T start_col, dict_T *dict, buf_T *default_buf, typval_T *dict_arg); int get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change); int count_props(linenr_T lnum, int only_starting); int find_visible_prop(win_T *wp, int type_id, int id, textprop_T *prop, linenr_T *found_lnum); diff --git a/src/regexp.c b/src/regexp.c --- a/src/regexp.c +++ b/src/regexp.c @@ -1303,7 +1303,7 @@ reg_match_visual(void) rex.line = reg_getline(rex.lnum); rex.input = rex.line + col; - cols = win_linetabsize(wp, rex.line, col); + cols = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, rex.line, col); if (cols < start || cols > end - (*p_sel == 'e')) return FALSE; } diff --git a/src/regexp_bt.c b/src/regexp_bt.c --- a/src/regexp_bt.c +++ b/src/regexp_bt.c @@ -3441,7 +3441,9 @@ regmatch( case RE_VCOL: if (!re_num_cmp((long_u)win_linetabsize( rex.reg_win == NULL ? curwin : rex.reg_win, - rex.line, (colnr_T)(rex.input - rex.line)) + 1, scan)) + rex.reg_firstlnum + rex.lnum, + rex.line, + (colnr_T)(rex.input - rex.line)) + 1, scan)) status = RA_NOMATCH; break; diff --git a/src/regexp_nfa.c b/src/regexp_nfa.c --- a/src/regexp_nfa.c +++ b/src/regexp_nfa.c @@ -6775,7 +6775,9 @@ nfa_regmatch( } if (!result) result = nfa_re_num_cmp(t->state->val, op, - (long_u)win_linetabsize(wp, rex.line, col) + 1); + (long_u)win_linetabsize(wp, + rex.reg_firstlnum + rex.lnum, + rex.line, col) + 1); if (result) { add_here = TRUE; diff --git a/src/register.c b/src/register.c --- a/src/register.c +++ b/src/register.c @@ -1820,8 +1820,9 @@ do_put( bd.textcol = 0; for (i = 0; i < y_size; ++i) { - int spaces = 0; - char shortline; + int spaces = 0; + char shortline; + chartabsize_T cts; bd.startspaces = 0; bd.endspaces = 0; @@ -1839,13 +1840,19 @@ do_put( // get the old line and advance to the position to insert at oldp = ml_get_curline(); oldlen = (int)STRLEN(oldp); - for (ptr = oldp; vcol < col && *ptr; ) + init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, 0, + oldp, oldp); + + while (cts.cts_vcol < col && *cts.cts_ptr != NUL) { // Count a tab for what it's worth (if list mode not on) - incr = lbr_chartabsize_adv(oldp, &ptr, vcol); - vcol += incr; + incr = lbr_chartabsize_adv(&cts); + cts.cts_vcol += incr; } + vcol = cts.cts_vcol; + ptr = cts.cts_ptr; bd.textcol = (colnr_T)(ptr - oldp); + clear_chartabsize_arg(&cts); shortline = (vcol < col) || (vcol == col && !*ptr) ; @@ -1876,8 +1883,15 @@ do_put( // calculate number of spaces required to fill right side of // block spaces = y_width + 1; + init_chartabsize_arg(&cts, curwin, 0, 0, + y_array[i], y_array[i]); for (j = 0; j < yanklen; j++) - spaces -= lbr_chartabsize(NULL, &y_array[i][j], 0); + { + spaces -= lbr_chartabsize(&cts); + ++cts.cts_ptr; + cts.cts_vcol = 0; + } + clear_chartabsize_arg(&cts); if (spaces < 0) spaces = 0; } diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -806,8 +806,9 @@ typedef struct textprop_S int tp_flags; // TP_FLAG_ values } textprop_T; -#define TP_FLAG_CONT_NEXT 1 // property continues in next line -#define TP_FLAG_CONT_PREV 2 // property was continued from prev line +#define TP_FLAG_CONT_NEXT 0x1 // property continues in next line +#define TP_FLAG_CONT_PREV 0x2 // property was continued from prev line +#define TP_VIRTUAL 0x4 // virtual text, uses tp_id /* * Structure defining a property type. @@ -3074,6 +3075,7 @@ struct file_buffer #ifdef FEAT_PROP_POPUP int b_has_textprop; // TRUE when text props were added hashtab_T *b_proptypes; // text property types local to buffer + garray_T b_textprop_text; // stores text for props, index by (-id - 1) #endif #if defined(FEAT_BEVAL) && defined(FEAT_EVAL) @@ -4560,3 +4562,18 @@ typedef struct { char_u *str; int score; } fuzmatch_str_T; + +// Argument for lbr_chartabsize(). +typedef struct { + win_T *cts_win; + linenr_T cts_lnum; // zero when not using text properties + char_u *cts_line; // start of the line + char_u *cts_ptr; // current position in line +#ifdef FEAT_PROP_POPUP + int cts_text_prop_count; // number of text props + textprop_T *cts_text_props; // text props (allocated) or NULL + char cts_has_prop_with_text; // TRUE if if a property inserts text + int cts_cur_text_width; // width of current inserted text +#endif + int cts_vcol; // virtual column at current position +} chartabsize_T; diff --git a/src/testdir/dumps/Test_prop_inserts_text.dump b/src/testdir/dumps/Test_prop_inserts_text.dump new file mode 100644 --- /dev/null +++ b/src/testdir/dumps/Test_prop_inserts_text.dump @@ -0,0 +1,6 @@ +|i+0&#ffffff0|n|s|e|r|t| |s|o|m|e| |t|e|x|t| |S+0#ffffff16#e000002|O|M|E| |h+0#0000000#ffffff0|e|r|e| |a|n|d| |o|t|h|e|r| |t|e|x|t| |O+0&#ffff4012|T|H|E|R| |t+0&#ffffff0|h|e|r|e| |a|n|d| |s|o +|m|e| |m|o|r|e| |t|e|x|t| |a|f|t|e|r| |M+0fd7ff255|O|R|E| |w+0&#ffffff0|r|a|p@1|i|n|g> @27 +|~+0#4040ff13&| @58 +|~| @58 +|~| @58 +| +0#0000000&@41|1|,|7@1|-|9|3| @6|A|l@1| diff --git a/src/testdir/test_textprop.vim b/src/testdir/test_textprop.vim --- a/src/testdir/test_textprop.vim +++ b/src/testdir/test_textprop.vim @@ -2187,4 +2187,27 @@ func Test_props_do_not_affect_byte_offse bwipe! endfunc +func Test_prop_inserts_text() + CheckRunVimInTerminal + + " Just a basic check for now + let lines =<< trim END + call setline(1, 'insert some text here and other text there and some more text after wrapping') + call prop_type_add('someprop', #{highlight: 'ErrorMsg'}) + call prop_type_add('otherprop', #{highlight: 'Search'}) + call prop_type_add('moreprop', #{highlight: 'DiffAdd'}) + call prop_add(1, 18, #{type: 'someprop', text: 'SOME '}) + call prop_add(1, 38, #{type: 'otherprop', text: 'OTHER '}) + call prop_add(1, 69, #{type: 'moreprop', text: 'MORE '}) + redraw + normal $ + END + call writefile(lines, 'XscriptPropsWithText') + let buf = RunVimInTerminal('-S XscriptPropsWithText', #{rows: 6, cols: 60}) + call VerifyScreenDump(buf, 'Test_prop_inserts_text', {}) + + call StopVimInTerminal(buf) + call delete('XscriptPropsWithText') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/textprop.c b/src/textprop.c --- a/src/textprop.c +++ b/src/textprop.c @@ -150,7 +150,7 @@ get_bufnr_from_arg(typval_T *arg, buf_T * prop_add({lnum}, {col}, {props}) */ void -f_prop_add(typval_T *argvars, typval_T *rettv UNUSED) +f_prop_add(typval_T *argvars, typval_T *rettv) { linenr_T start_lnum; colnr_T start_col; @@ -174,20 +174,22 @@ f_prop_add(typval_T *argvars, typval_T * return; } - prop_add_common(start_lnum, start_col, argvars[2].vval.v_dict, - curbuf, &argvars[2]); + rettv->vval.v_number = prop_add_common(start_lnum, start_col, + argvars[2].vval.v_dict, curbuf, &argvars[2]); } /* * Attach a text property 'type_name' to the text starting * at [start_lnum, start_col] and ending at [end_lnum, end_col] in - * the buffer 'buf' and assign identifier 'id'. + * the buffer "buf" and assign identifier "id". + * When "text" is not NULL add it to buf->b_textprop_text[-id - 1]. */ static int prop_add_one( buf_T *buf, char_u *type_name, int id, + char_u *text_arg, linenr_T start_lnum, linenr_T end_lnum, colnr_T start_col, @@ -202,26 +204,43 @@ prop_add_one( char_u *newtext; int i; textprop_T tmp_prop; + char_u *text = text_arg; + int res = FAIL; type = lookup_prop_type(type_name, buf); if (type == NULL) - return FAIL; + goto theend; if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count) { semsg(_(e_invalid_line_number_nr), (long)start_lnum); - return FAIL; + goto theend; } if (end_lnum < start_lnum || end_lnum > buf->b_ml.ml_line_count) { semsg(_(e_invalid_line_number_nr), (long)end_lnum); - return FAIL; + goto theend; } if (buf->b_ml.ml_mfp == NULL) { emsg(_(e_cannot_add_text_property_to_unloaded_buffer)); - return FAIL; + goto theend; + } + + if (text != NULL) + { + garray_T *gap = &buf->b_textprop_text; + + // double check we got the right ID + if (-id - 1 != gap->ga_len) + iemsg("text prop ID mismatch"); + if (gap->ga_growsize == 0) + ga_init2(gap, sizeof(char *), 50); + if (ga_grow(gap, 1) == FAIL) + goto theend; + ((char_u **)gap->ga_data)[gap->ga_len++] = text; + text = NULL; } for (lnum = start_lnum; lnum <= end_lnum; ++lnum) @@ -240,7 +259,7 @@ prop_add_one( if (col - 1 > (colnr_T)textlen) { semsg(_(e_invalid_column_number_nr), (long)start_col); - return FAIL; + goto theend; } if (lnum == end_lnum) @@ -255,7 +274,7 @@ prop_add_one( // Allocate the new line with space for the new property. newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T)); if (newtext == NULL) - return FAIL; + goto theend; // Copy the text, including terminating NUL. mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen); @@ -295,7 +314,11 @@ prop_add_one( } changed_lines_buf(buf, start_lnum, end_lnum + 1, 0); - return OK; + res = OK; + +theend: + vim_free(text); + return res; } /* @@ -367,7 +390,7 @@ f_prop_add_list(typval_T *argvars, typva emsg(_(e_invalid_argument)); return; } - if (prop_add_one(buf, type_name, id, start_lnum, end_lnum, + if (prop_add_one(buf, type_name, id, NULL, start_lnum, end_lnum, start_col, end_col) == FAIL) return; } @@ -376,11 +399,22 @@ f_prop_add_list(typval_T *argvars, typva } /* + * Get the next ID to use for a textprop with text in buffer "buf". + */ + static int +get_textprop_id(buf_T *buf) +{ + // TODO: recycle deleted entries + return -(buf->b_textprop_text.ga_len + 1); +} + +/* * Shared between prop_add() and popup_create(). * "dict_arg" is the function argument of a dict containing "bufnr". * it is NULL for popup_create(). + * Returns the "id" used for "text" or zero. */ - void + int prop_add_common( linenr_T start_lnum, colnr_T start_col, @@ -393,11 +427,12 @@ prop_add_common( char_u *type_name; buf_T *buf = default_buf; int id = 0; + char_u *text = NULL; if (dict == NULL || !dict_has_key(dict, "type")) { emsg(_(e_missing_property_type_name)); - return; + goto theend; } type_name = dict_get_string(dict, "type", FALSE); @@ -407,7 +442,7 @@ prop_add_common( if (end_lnum < start_lnum) { semsg(_(e_invalid_value_for_argument_str), "end_lnum"); - return; + goto theend; } } else @@ -420,7 +455,7 @@ prop_add_common( if (length < 0 || end_lnum > start_lnum) { semsg(_(e_invalid_value_for_argument_str), "length"); - return; + goto theend; } end_col = start_col + length; } @@ -430,7 +465,7 @@ prop_add_common( if (end_col <= 0) { semsg(_(e_invalid_value_for_argument_str), "end_col"); - return; + goto theend; } } else if (start_lnum == end_lnum) @@ -441,17 +476,40 @@ prop_add_common( if (dict_has_key(dict, "id")) id = dict_get_number(dict, "id"); + if (dict_has_key(dict, "text")) + { + text = dict_get_string(dict, "text", TRUE); + if (text == NULL) + goto theend; + // use a default length of 1 to make multiple props show up + end_col = start_col + 1; + } + if (dict_arg != NULL && get_bufnr_from_arg(dict_arg, &buf) == FAIL) - return; + goto theend; + + if (id < 0 && buf->b_textprop_text.ga_len > 0) + { + emsg(_(e_cannot_use_negative_id_after_adding_textprop_with_text)); + goto theend; + } + if (text != NULL) + id = get_textprop_id(buf); // This must be done _before_ we add the property because property changes // trigger buffer (memline) reorganisation, which needs this flag to be // correctly set. buf->b_has_textprop = TRUE; // this is never reset - prop_add_one(buf, type_name, id, start_lnum, end_lnum, start_col, end_col); + prop_add_one(buf, type_name, id, text, + start_lnum, end_lnum, start_col, end_col); + text = NULL; redraw_buf_later(buf, VALID); + +theend: + vim_free(text); + return id; } /* @@ -954,9 +1012,9 @@ get_props_in_line( if ((prop_types == NULL || prop_type_or_id_in_list(prop_types, prop_types_len, prop.tp_type)) - && (prop_ids == NULL || - prop_type_or_id_in_list(prop_ids, prop_ids_len, - prop.tp_id))) + && (prop_ids == NULL + || prop_type_or_id_in_list(prop_ids, prop_ids_len, + prop.tp_id))) { dict_T *d = dict_alloc(); diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -736,6 +736,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 67, +/**/ 66, /**/ 65,