# HG changeset patch # User Bram Moolenaar # Date 1662837303 -7200 # Node ID ed6f3d2593dfe9dec2aeba3f4db4dc83412970e9 # Parent b6c11bb32c89602944b4c411e62307ae18515fc4 patch 9.0.0438: cannot put virtual text above a line Commit: https://github.com/vim/vim/commit/04e0ed1ddf399d609dbcb7dbf19e531da1fe6172 Author: Bram Moolenaar Date: Sat Sep 10 20:00:56 2022 +0100 patch 9.0.0438: cannot put virtual text above a line Problem: Cannot put virtual text above a line. Solution: Add the "above" value for "text_align". diff --git a/runtime/doc/textprop.txt b/runtime/doc/textprop.txt --- a/runtime/doc/textprop.txt +++ b/runtime/doc/textprop.txt @@ -153,6 +153,7 @@ prop_add({lnum}, {col}, {props}) the text wraps to the next screen line) below in the next screen line + above just above the line When omitted "after" is used. Only one "right" property can fit in each line, if there are two ore more these will go in a diff --git a/src/charset.c b/src/charset.c --- a/src/charset.c +++ b/src/charset.c @@ -1069,40 +1069,6 @@ lbr_chartabsize_adv(chartabsize_T *cts) return retval; } -#if defined(FEAT_PROP_POPUP) || defined(PROTO) -/* - * Return the cell size of virtual text after truncation. - */ - int -textprop_size_after_trunc( - win_T *wp, - int below, - int added, - char_u *text, - int *n_used_ptr) -{ - int space = below ? wp->w_width : added; - int len = (int)STRLEN(text); - int strsize = 0; - int n_used; - - // if the remaining size is to small wrap - // anyway and use the next line - if (space < PROP_TEXT_MIN_CELLS) - space += wp->w_width; - for (n_used = 0; n_used < len; n_used += (*mb_ptr2len)(text + n_used)) - { - int clen = ptr2cells(text + n_used); - - if (strsize + clen > space) - break; - strsize += clen; - } - *n_used_ptr = n_used; - return strsize; -} -#endif - /* * 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 @@ -1142,6 +1108,7 @@ win_lbr_chartabsize( #if defined(FEAT_PROP_POPUP) cts->cts_cur_text_width = 0; + cts->cts_first_char = 0; #endif #if defined(FEAT_LINEBREAK) || defined(FEAT_PROP_POPUP) @@ -1194,9 +1161,12 @@ win_lbr_chartabsize( if (tp->tp_id < 0 && ((tp->tp_col - 1 >= col && tp->tp_col - 1 < col + charlen) - || (tp->tp_col == MAXCOL && (s[0] == NUL || s[1] == NUL) - && cts->cts_with_trailing)) - && -tp->tp_id - 1 < gap->ga_len) + || (tp->tp_col == MAXCOL + && ((tp->tp_flags & TP_FLAG_ALIGN_ABOVE) + ? col == 0 + : (s[0] == NUL || s[1] == NUL) + && cts->cts_with_trailing))) + && tp->tp_id - 1 < gap->ga_len) { char_u *p = ((char_u **)gap->ga_data)[-tp->tp_id - 1]; @@ -1218,6 +1188,8 @@ win_lbr_chartabsize( else cells = vim_strsize(p); cts->cts_cur_text_width += cells; + if (tp->tp_flags & TP_FLAG_ALIGN_ABOVE) + cts->cts_first_char += cells; cts->cts_start_incl = tp->tp_flags & TP_FLAG_START_INCL; size += cells; if (*s == TAB) @@ -1564,6 +1536,11 @@ getvcol( #endif break; } +#ifdef FEAT_PROP_POPUP + if (cursor == &wp->w_virtcol && cts.cts_ptr == cts.cts_line) + // do not count the virtual text above for w_curswant + wp->w_virtcol_first_char = cts.cts_first_char; +#endif if (posptr != NULL && cts.cts_ptr >= posptr) // character at pos->col diff --git a/src/drawline.c b/src/drawline.c --- a/src/drawline.c +++ b/src/drawline.c @@ -279,6 +279,38 @@ get_sign_display_info( #if defined(FEAT_PROP_POPUP) || defined(PROTO) /* + * Return the cell size of virtual text after truncation. + */ + static int +textprop_size_after_trunc( + win_T *wp, + int flags, // TP_FLAG_ALIGN_* + int added, + char_u *text, + int *n_used_ptr) +{ + int space = (flags & (TP_FLAG_ALIGN_BELOW | TP_FLAG_ALIGN_ABOVE)) + ? wp->w_width : added; + int len = (int)STRLEN(text); + int strsize = 0; + int n_used; + + // if the remaining size is to small wrap anyway and use the next line + if (space < PROP_TEXT_MIN_CELLS) + space += wp->w_width; + for (n_used = 0; n_used < len; n_used += (*mb_ptr2len)(text + n_used)) + { + int clen = ptr2cells(text + n_used); + + if (strsize + clen > space) + break; + strsize += clen; + } + *n_used_ptr = n_used; + return strsize; +} + +/* * Take care of padding, right-align and truncation of virtual text after a * line. * if "n_attr" is not NULL then "n_extra" and "p_extra" are adjusted for any @@ -297,6 +329,7 @@ text_prop_position( int *n_attr_skip) // cells to skip attr, NULL if not used { int right = (tp->tp_flags & TP_FLAG_ALIGN_RIGHT); + int above = (tp->tp_flags & TP_FLAG_ALIGN_ABOVE); int below = (tp->tp_flags & TP_FLAG_ALIGN_BELOW); int wrap = (tp->tp_flags & TP_FLAG_WRAP); int padding = tp->tp_col == MAXCOL && tp->tp_len > 1 @@ -304,36 +337,45 @@ text_prop_position( int col_with_padding = vcol + (below ? 0 : padding); int col_off = 0; int room = wp->w_width - col_with_padding; - int added = room; + int before = room; // spaces before the text + int after = 0; // spaces after the text int n_used = *n_extra; char_u *l = NULL; int strsize = vim_strsize(*p_extra); - int cells = wrap ? strsize - : textprop_size_after_trunc(wp, below, added, *p_extra, &n_used); + int cells = wrap ? strsize : textprop_size_after_trunc(wp, + tp->tp_flags, before, *p_extra, &n_used); - if (wrap || right || below || padding > 0 || n_used < *n_extra) + if (wrap || right || above || below || padding > 0 || n_used < *n_extra) { - // Right-align: fill with spaces - if (right) - added -= cells; - if (added < 0 - || !(right || below) - || (below - ? (col_with_padding == 0 || !wp->w_p_wrap) - : (n_used < *n_extra))) + if (above) + { + before = 0; + after = wp->w_width - cells; + } + else { - if (right && (wrap || room < PROP_TEXT_MIN_CELLS)) + // Right-align: fill with before + if (right) + before -= cells; + if (before < 0 + || !(right || below) + || (below + ? (col_with_padding == 0 || !wp->w_p_wrap) + : (n_used < *n_extra))) { - // right-align on next line instead of wrapping if possible - col_off = win_col_off(wp) + win_col_off2(wp); - added = wp->w_width - col_off - strsize + room; - if (added < 0) - added = 0; + if (right && (wrap || room < PROP_TEXT_MIN_CELLS)) + { + // right-align on next line instead of wrapping if possible + col_off = win_col_off(wp) + win_col_off2(wp); + before = wp->w_width - col_off - strsize + room; + if (before < 0) + before = 0; + else + n_used = *n_extra; + } else - n_used = *n_extra; + before = 0; } - else - added = 0; } // With 'nowrap' add one to show the "extends" character if needed (it @@ -346,15 +388,15 @@ text_prop_position( // add 1 for NUL, 2 for when '…' is used if (n_attr != NULL) - l = alloc(n_used + added + padding + 3); + l = alloc(n_used + before + after + padding + 3); if (n_attr == NULL || l != NULL) { int off = 0; if (n_attr != NULL) { - vim_memset(l, ' ', added); - off += added; + vim_memset(l, ' ', before); + off += before; if (padding > 0) { vim_memset(l + off, ' ', padding); @@ -365,8 +407,8 @@ text_prop_position( } else { - off = added + padding + n_used; - cells += added + padding; + off = before + after + padding + n_used; + cells += before + after + padding; } if (n_attr != NULL) { @@ -385,10 +427,16 @@ text_prop_position( // change last character to '>' *lp = '>'; } + else if (after > 0) + { + vim_memset(l + off, ' ', after); + l[off + after] = NUL; + } + *p_extra = l; - *n_extra = n_used + added + padding; + *n_extra = n_used + before + after + padding; *n_attr = mb_charlen(*p_extra); - *n_attr_skip = added + padding + col_off; + *n_attr_skip = before + padding + col_off; } } } @@ -1694,11 +1742,14 @@ win_line( // text prop can show. while (text_prop_next < text_prop_count && (text_props[text_prop_next].tp_col == MAXCOL - ? (*ptr == NUL + ? ((*ptr == NUL && (wp->w_p_wrap || wlv.row == startrow || (text_props[text_prop_next].tp_flags & TP_FLAG_ALIGN_BELOW))) + || (bcol == 0 && + (text_props[text_prop_next].tp_flags + & TP_FLAG_ALIGN_ABOVE))) : bcol >= text_props[text_prop_next].tp_col - 1)) { if (text_props[text_prop_next].tp_col == MAXCOL @@ -1773,6 +1824,8 @@ win_line( { int right = (tp->tp_flags & TP_FLAG_ALIGN_RIGHT); + int above = (tp->tp_flags + & TP_FLAG_ALIGN_ABOVE); int below = (tp->tp_flags & TP_FLAG_ALIGN_BELOW); int wrap = (tp->tp_flags & TP_FLAG_WRAP); @@ -1797,18 +1850,15 @@ win_line( // don't combine char attr after EOL text_prop_flags &= ~PT_FLAG_COMBINE; #ifdef FEAT_LINEBREAK - if (below || right || !wrap) + if (above || below || right || !wrap) { // no 'showbreak' before "below" text property - // or after "right" text property + // or after "above" or "right" text property need_showbreak = FALSE; dont_use_showbreak = TRUE; } #endif - // Keep in sync with where - // textprop_size_after_trunc() is called in - // win_lbr_chartabsize(). - if ((right || below || !wrap || padding > 0) + if ((right || above || below || !wrap || padding > 0) && wp->w_width > 2) { char_u *prev_p_extra = wlv.p_extra; diff --git a/src/misc2.c b/src/misc2.c --- a/src/misc2.c +++ b/src/misc2.c @@ -85,7 +85,7 @@ getviscol2(colnr_T col, colnr_T coladd U } /* - * Try to advance the Cursor to the specified screen column. + * Try to advance the Cursor to the specified screen column "wantcol". * If virtual editing: fine tune the cursor position. * Note that all virtual positions off the end of a line should share * a curwin->w_cursor.col value (n.b. this is equal to STRLEN(line)), @@ -94,29 +94,30 @@ getviscol2(colnr_T col, colnr_T coladd U * return OK if desired column is reached, FAIL if not */ int -coladvance(colnr_T wcol) +coladvance(colnr_T wantcol) { - int rc = getvpos(&curwin->w_cursor, wcol); + int rc = getvpos(&curwin->w_cursor, wantcol); - if (wcol == MAXCOL || rc == FAIL) + if (wantcol == MAXCOL || rc == FAIL) curwin->w_valid &= ~VALID_VIRTCOL; else if (*ml_get_cursor() != TAB) { // Virtcol is valid when not on a TAB curwin->w_valid |= VALID_VIRTCOL; - curwin->w_virtcol = wcol; + curwin->w_virtcol = wantcol; } return rc; } /* - * Return in "pos" the position of the cursor advanced to screen column "wcol". + * Return in "pos" the position of the cursor advanced to screen column + * "wantcol". * return OK if desired column is reached, FAIL if not */ int -getvpos(pos_T *pos, colnr_T wcol) +getvpos(pos_T *pos, colnr_T wantcol) { - return coladvance2(pos, FALSE, virtual_active(), wcol); + return coladvance2(pos, FALSE, virtual_active(), wantcol); } static int @@ -156,8 +157,8 @@ coladvance2( } else { - int width = curwin->w_width - win_col_off(curwin); - chartabsize_T cts; + int width = curwin->w_width - win_col_off(curwin); + chartabsize_T cts; if (finetune && curwin->w_p_wrap @@ -183,6 +184,9 @@ coladvance2( init_chartabsize_arg(&cts, curwin, pos->lnum, 0, line, line); while (cts.cts_vcol <= wcol && *cts.cts_ptr != NUL) { +#ifdef FEAT_PROP_POPUP + int at_start = cts.cts_ptr == cts.cts_line; +#endif // Count a tab for what it's worth (if list mode not on) #ifdef FEAT_LINEBREAK csize = win_lbr_chartabsize(&cts, &head); @@ -191,6 +195,11 @@ coladvance2( csize = lbr_chartabsize_adv(&cts); #endif cts.cts_vcol += csize; +#ifdef FEAT_PROP_POPUP + if (at_start) + // do not count the columns for virtual text above + cts.cts_vcol -= cts.cts_first_char; +#endif } col = cts.cts_vcol; idx = (int)(cts.cts_ptr - line); @@ -2400,7 +2409,7 @@ update_mouseshape(int shape_idx) /* * Change directory to "new_dir". Search 'cdpath' for relative directory - * names, otherwise just mch_chdir(). + * names. */ int vim_chdir(char_u *new_dir) diff --git a/src/move.c b/src/move.c --- a/src/move.c +++ b/src/move.c @@ -476,7 +476,11 @@ update_curswant(void) if (curwin->w_set_curswant) { validate_virtcol(); - curwin->w_curswant = curwin->w_virtcol; + curwin->w_curswant = curwin->w_virtcol +#ifdef FEAT_PROP_POPUP + - curwin->w_virtcol_first_char +#endif + ; curwin->w_set_curswant = FALSE; } } @@ -835,6 +839,9 @@ validate_virtcol_win(win_T *wp) check_cursor_moved(wp); if (!(wp->w_valid & VALID_VIRTCOL)) { +#ifdef FEAT_PROP_POPUP + wp->w_virtcol_first_char = 0; +#endif getvvcol(wp, &wp->w_cursor, NULL, &(wp->w_virtcol), NULL); #ifdef FEAT_SYN_HL redraw_for_cursorcolumn(wp); @@ -982,6 +989,11 @@ curs_columns( if (!(curwin->w_valid & VALID_CROW)) curs_rows(curwin); +#ifdef FEAT_PROP_POPUP + // will be set by getvvcol() but not reset + curwin->w_virtcol_first_char = 0; +#endif + /* * Compute the number of virtual columns. */ diff --git a/src/proto/charset.pro b/src/proto/charset.pro --- a/src/proto/charset.pro +++ b/src/proto/charset.pro @@ -34,7 +34,6 @@ void init_chartabsize_arg(chartabsize_T void clear_chartabsize_arg(chartabsize_T *cts); int lbr_chartabsize(chartabsize_T *cts); int lbr_chartabsize_adv(chartabsize_T *cts); -int textprop_size_after_trunc(win_T *wp, int below, int added, char_u *text, int *n_used_ptr); 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); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -813,12 +813,13 @@ typedef struct textprop_S #define TP_FLAG_CONT_PREV 0x2 // property was continued from prev line // without these text is placed after the end of the line -#define TP_FLAG_ALIGN_RIGHT 0x10 // virtual text is right-aligned -#define TP_FLAG_ALIGN_BELOW 0x20 // virtual text on next screen line - -#define TP_FLAG_WRAP 0x40 // virtual text wraps - when missing +#define TP_FLAG_ALIGN_RIGHT 0x010 // virtual text is right-aligned +#define TP_FLAG_ALIGN_ABOVE 0x020 // virtual text above the line +#define TP_FLAG_ALIGN_BELOW 0x040 // virtual text on next screen line + +#define TP_FLAG_WRAP 0x080 // virtual text wraps - when missing // text is truncated -#define TP_FLAG_START_INCL 0x80 // "start_incl" copied from proptype +#define TP_FLAG_START_INCL 0x100 // "start_incl" copied from proptype #define PROP_TEXT_MIN_CELLS 4 // minimun number of cells to use for // the text, even when truncating @@ -3678,6 +3679,11 @@ struct window_S // more than one screen line or when // w_leftcol is non-zero +#ifdef FEAT_PROP_POPUP + colnr_T w_virtcol_first_char; // offset for w_virtcol when there are + // virtual text properties above the + // line +#endif /* * w_wrow and w_wcol specify the cursor position in the window. * This is related to positions in the window, not in the display or @@ -4607,6 +4613,7 @@ typedef struct { textprop_T *cts_text_props; // text props (allocated) char cts_has_prop_with_text; // TRUE if if a property inserts text int cts_cur_text_width; // width of current inserted text + int cts_first_char; // width text props above the line int cts_with_trailing; // include size of trailing props with // last character int cts_start_incl; // prop has true "start_incl" arg diff --git a/src/testdir/dumps/Test_prop_with_text_above_1.dump b/src/testdir/dumps/Test_prop_with_text_above_1.dump new file mode 100644 --- /dev/null +++ b/src/testdir/dumps/Test_prop_with_text_above_1.dump @@ -0,0 +1,9 @@ +|f+0&#ffff4012|i|r|s|t| |t|h|i|n|g| |a|b|o|v|e| @42 +|s+0&#ffd7ff255|e|c|o|n|d| |t|h|i|n|g| |a|b|o|v|e| @41 +|o+0&#ffffff0|n|e| |t|w|o| @52 +|t|h|r>e@1| |f|o|u|r| @49 +|a+0&#ffff4012|n|o|t|h|e|r| |t|h|i|n|g| @46 +|f+0&#ffffff0|i|v|e| |s|i|x| @51 +|~+0#4040ff13&| @58 +|~| @58 +| +0#0000000&@41|2|,|4| @10|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 @@ -2848,6 +2848,26 @@ func Test_props_with_text_below_nowrap() call delete('XscriptPropsBelowNowrap') endfunc +func Test_props_with_text_above() + CheckRunVimInTerminal + + let lines =<< trim END + call setline(1, ['one two', 'three four', 'five six']) + call prop_type_add('above1', #{highlight: 'Search'}) + call prop_type_add('above2', #{highlight: 'DiffChange'}) + call prop_add(1, 0, #{type: 'above1', text: 'first thing above', text_align: 'above'}) + call prop_add(1, 0, #{type: 'above2', text: 'second thing above', text_align: 'above'}) + call prop_add(3, 0, #{type: 'above1', text: 'another thing', text_align: 'above'}) + + normal gglllj + END + call writefile(lines, 'XscriptPropsWithTextAbove', 'D') + let buf = RunVimInTerminal('-S XscriptPropsWithTextAbove', #{rows: 9, cols: 60}) + call VerifyScreenDump(buf, 'Test_prop_with_text_above_1', {}) + + call StopVimInTerminal(buf) +endfunc + func Test_props_with_text_override() CheckRunVimInTerminal diff --git a/src/textprop.c b/src/textprop.c --- a/src/textprop.c +++ b/src/textprop.c @@ -497,6 +497,8 @@ prop_add_common( } if (STRCMP(p, "right") == 0) flags |= TP_FLAG_ALIGN_RIGHT; + else if (STRCMP(p, "above") == 0) + flags |= TP_FLAG_ALIGN_ABOVE; else if (STRCMP(p, "below") == 0) flags |= TP_FLAG_ALIGN_BELOW; else if (STRCMP(p, "after") != 0) @@ -673,6 +675,21 @@ count_props(linenr_T lnum, int only_star static textprop_T *text_prop_compare_props; static buf_T *text_prop_compare_buf; +/* Score for sorting on position of the text property: 0: above, + * 1: after (default), 2: right, 3: below (comes last) + */ + static int +text_prop_order(int flags) +{ + if (flags & TP_FLAG_ALIGN_ABOVE) + return 0; + if (flags & TP_FLAG_ALIGN_RIGHT) + return 2; + if (flags & TP_FLAG_ALIGN_BELOW) + return 3; + return 1; +} + /* * Function passed to qsort() to sort text properties. * Return 1 if "s1" has priority over "s2", -1 if the other way around, zero if @@ -694,21 +711,13 @@ text_prop_compare(const void *s1, const col2 = tp2->tp_col; if (col1 == MAXCOL && col2 == MAXCOL) { - int flags1 = 0; - int flags2 = 0; + int order1 = text_prop_order(tp1->tp_flags); + int order2 = text_prop_order(tp2->tp_flags); - // both props add text are after the line, order on 0: after (default), - // 1: right, 2: below (comes last) - if (tp1->tp_flags & TP_FLAG_ALIGN_RIGHT) - flags1 = 1; - if (tp1->tp_flags & TP_FLAG_ALIGN_BELOW) - flags1 = 2; - if (tp2->tp_flags & TP_FLAG_ALIGN_RIGHT) - flags2 = 1; - if (tp2->tp_flags & TP_FLAG_ALIGN_BELOW) - flags2 = 2; - if (flags1 != flags2) - return flags1 < flags2 ? 1 : -1; + // both props add text before or after the line, sort on order where it + // is added + if (order1 != order2) + return order1 < order2 ? 1 : -1; } // property that inserts text has priority over one that doesn't diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -704,6 +704,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 438, +/**/ 437, /**/ 436,