# HG changeset patch # User Bram Moolenaar # Date 1661276705 -7200 # Node ID bbe62ea78aac889546b4824790475dd6f13d259e # Parent 9941dc321348b50a76fa77624e608e1053e8d79a patch 9.0.0247: cannot add padding to virtual text without highlight Commit: https://github.com/vim/vim/commit/f396ce83eebf6c61596184231d39ce4d41eeac04 Author: Bram Moolenaar Date: Tue Aug 23 18:39:37 2022 +0100 patch 9.0.0247: cannot add padding to virtual text without highlight Problem: Cannot add padding to virtual text without highlight. Solution: Add the "text_padding_left" argument. (issue https://github.com/vim/vim/issues/10906) diff --git a/runtime/doc/textprop.txt b/runtime/doc/textprop.txt --- a/runtime/doc/textprop.txt +++ b/runtime/doc/textprop.txt @@ -126,6 +126,7 @@ prop_add({lnum}, {col}, {props}) If {col} is invalid an error is given. *E964* {props} is a dictionary with these fields: + type name of the text property type length length of text in bytes, can only be used for a property that does not continue in another line; can be zero @@ -142,9 +143,10 @@ prop_add({lnum}, {col}, {props}) automatically to a negative number; otherwise zero is used text text to be displayed before {col}, or after the - line if {col} is zero + line if {col} is zero; prepend and/or append + spaces for padding with highlighting *E1294* - text_align when "text" is present and {col} is zero + text_align when "text" is present and {col} is zero; specifies where to display the text: after after the end of the line right right aligned in the window (unless @@ -152,14 +154,20 @@ prop_add({lnum}, {col}, {props}) line) below in the next screen line When omitted "after" is used. Only one - "right" property can fit in earch line. + "right" property can fit in each line, if + there are two ore more these will go in a + separate line (still right aligned). + text_padding_left *E1296* + used when "text" is present and {col} is zero; + padding between the end of the text line + (leftmost column for "below") and the virtual + text, not highlighted text_wrap when "text" is present and {col} is zero, specifies what happens if the text doesn't fit: wrap wrap the text to the next line truncate truncate the text to make it fit When omitted "truncate" is used. - type name of the text property type All fields except "type" are optional. It is an error when both "length" and "end_lnum" or "end_col" diff --git a/src/charset.c b/src/charset.c --- a/src/charset.c +++ b/src/charset.c @@ -957,26 +957,26 @@ init_chartabsize_arg( #ifdef FEAT_PROP_POPUP if (lnum > 0) { - char_u *prop_start; + char_u *prop_start; + int count; - cts->cts_text_prop_count = get_text_props(wp->w_buffer, lnum, - &prop_start, FALSE); - if (cts->cts_text_prop_count > 0) + count = get_text_props(wp->w_buffer, lnum, &prop_start, FALSE); + cts->cts_text_prop_count = count; + if (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); + // aligned. Make it twice as long for the sorting below. + cts->cts_text_props = ALLOC_MULT(textprop_T, count * 2); if (cts->cts_text_props == NULL) cts->cts_text_prop_count = 0; else { - int i; + 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) + mch_memmove(cts->cts_text_props + count, prop_start, + count * sizeof(textprop_T)); + for (i = 0; i < count; ++i) + if (cts->cts_text_props[i + count].tp_id < 0) { cts->cts_has_prop_with_text = TRUE; break; @@ -987,6 +987,27 @@ init_chartabsize_arg( VIM_CLEAR(cts->cts_text_props); cts->cts_text_prop_count = 0; } + else + { + int *text_prop_idxs; + + // Need to sort the array to get any truncation right. + // Do the sorting in the second part of the array, then + // move the sorted props to the first part of the array. + text_prop_idxs = ALLOC_MULT(int, count); + if (text_prop_idxs != NULL) + { + for (i = 0; i < count; ++i) + text_prop_idxs[i] = i + count; + sort_text_props(curbuf, cts->cts_text_props, + text_prop_idxs, count); + // Here we want the reverse order. + for (i = 0; i < count; ++i) + cts->cts_text_props[count - i - 1] = + cts->cts_text_props[text_prop_idxs[i]]; + vim_free(text_prop_idxs); + } + } } } } @@ -1159,6 +1180,11 @@ win_lbr_chartabsize( int col = (int)(s - line); garray_T *gap = &wp->w_buffer->b_textprop_text; + // The "$" for 'list' mode will go between the EOL and + // the text prop, account for that. + if (wp->w_p_list && wp->w_lcs_chars.eol != NUL) + ++vcol; + for (i = 0; i < cts->cts_text_prop_count; ++i) { textprop_T *tp = cts->cts_text_props + i; @@ -1176,46 +1202,21 @@ win_lbr_chartabsize( if (p != NULL) { - int cells = vim_strsize(p); + int cells; if (tp->tp_col == MAXCOL) { - int below = (tp->tp_flags & TP_FLAG_ALIGN_BELOW); - int right = (tp->tp_flags & TP_FLAG_ALIGN_RIGHT); - int wrap = (tp->tp_flags & TP_FLAG_WRAP); - int len = (int)STRLEN(p); - int n_used = len; - - // The "$" for 'list' mode will go between the EOL and - // the text prop, account for that. - if (wp->w_p_list && wp->w_lcs_chars.eol != NUL) - ++vcol; + int n_extra = (int)STRLEN(p); - // Keep in sync with where textprop_size_after_trunc() - // is called in win_line(). - if (!wrap) - { - added = wp->w_width - (vcol + size) % wp->w_width; - cells = textprop_size_after_trunc(wp, - below, added, p, &n_used); - } - if (below) - cells += wp->w_width - (vcol + size) % wp->w_width; - else if (right) - { - len = wp->w_width - vcol % wp->w_width; - if (len > cells + size) - // add the padding for right-alignment - cells = len - size; - else if (len == 0) - // padding to right-align in the next line - cells += cells > wp->w_width ? 0 - :wp->w_width - cells; - } + cells = text_prop_position(wp, tp, + (vcol + size) % wp->w_width, + &n_extra, &p, NULL, NULL); #ifdef FEAT_LINEBREAK no_sbr = TRUE; // don't use 'showbreak' now #endif } + else + cells = vim_strsize(p); cts->cts_cur_text_width += cells; cts->cts_start_incl = tp->tp_flags & TP_FLAG_START_INCL; size += cells; @@ -1231,6 +1232,8 @@ win_lbr_chartabsize( if (tp->tp_col != MAXCOL && tp->tp_col - 1 > col) break; } + if (wp->w_p_list && wp->w_lcs_chars.eol != NUL) + --vcol; } # endif diff --git a/src/drawline.c b/src/drawline.c --- a/src/drawline.c +++ b/src/drawline.c @@ -277,74 +277,123 @@ get_sign_display_info( } #endif -#ifdef FEAT_PROP_POPUP -static textprop_T *current_text_props = NULL; -static buf_T *current_buf = NULL; - +#if defined(FEAT_PROP_POPUP) || defined(PROTO) /* - * Function passed to qsort() to sort text properties. - * Return 1 if "s1" has priority over "s2", -1 if the other way around, zero if - * both have the same priority. + * 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 + * padding, right-align and truncation. Otherwise only the size is computed. + * When "n_attr" is NULL returns the number of screen cells used. + * Otherwise returns TRUE when drawing continues on the next line. */ - static int -text_prop_compare(const void *s1, const void *s2) + int +text_prop_position( + win_T *wp, + textprop_T *tp, + int vcol, // current screen column + int *n_extra, // nr of bytes for virtual text + char_u **p_extra, // virtual text + int *n_attr, // attribute cells, NULL if not used + int *n_attr_skip) // cells to skip attr, NULL if not used { - int idx1, idx2; - textprop_T *tp1, *tp2; - proptype_T *pt1, *pt2; - colnr_T col1, col2; + int right = (tp->tp_flags & TP_FLAG_ALIGN_RIGHT); + 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 + ? tp->tp_len - 1 : 0; + int col_with_padding = vcol + (below ? 0 : padding); + int room = wp->w_width - col_with_padding; + int added = room; + 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); - idx1 = *(int *)s1; - idx2 = *(int *)s2; - tp1 = ¤t_text_props[idx1]; - tp2 = ¤t_text_props[idx2]; - col1 = tp1->tp_col; - col2 = tp2->tp_col; - if (col1 == MAXCOL && col2 == MAXCOL) + if (wrap || right || below || padding > 0 || n_used < *n_extra) { - int flags1 = 0; - int flags2 = 0; + // 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 (right && (wrap || room < PROP_TEXT_MIN_CELLS)) + { + // right-align on next line instead of wrapping if possible + added = wp->w_width - strsize + room; + if (added < 0) + added = 0; + else + n_used = *n_extra; + } + else + added = 0; + } + + // With 'nowrap' add one to show the "extends" character if needed (it + // doesn't show if the text just fits). + if (!wp->w_p_wrap + && n_used < *n_extra + && wp->w_lcs_chars.ext != NUL + && wp->w_p_list) + ++n_used; + + // add 1 for NUL, 2 for when '…' is used + if (n_attr != NULL) + l = alloc(n_used + added + padding + 3); + if (n_attr == NULL || l != NULL) + { + int off = 0; - // 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; + if (n_attr != NULL) + { + vim_memset(l, ' ', added); + off += added; + if (padding > 0) + { + vim_memset(l + off, ' ', padding); + off += padding; + } + vim_strncpy(l + off, *p_extra, n_used); + off += n_used; + } + else + { + off = added + padding + n_used; + cells += added + padding; + } + if (n_attr != NULL) + { + if (n_used < *n_extra && wp->w_p_wrap) + { + char_u *lp = l + off - 1; + + if (has_mbyte) + { + // change last character to '…' + lp -= (*mb_head_off)(l, lp); + STRCPY(lp, "…"); + n_used = lp - l + 3 - padding; + } + else + // change last character to '>' + *lp = '>'; + } + *p_extra = l; + *n_extra = n_used + added + padding; + *n_attr = mb_charlen(*p_extra); + *n_attr_skip = added + padding; + } + } } - // property that inserts text has priority over one that doesn't - if ((tp1->tp_id < 0) != (tp2->tp_id < 0)) - return tp1->tp_id < 0 ? 1 : -1; - - // check highest priority, defined by the type - pt1 = text_prop_type_by_id(current_buf, tp1->tp_type); - pt2 = text_prop_type_by_id(current_buf, tp2->tp_type); - if (pt1 != pt2) - { - if (pt1 == NULL) - return -1; - if (pt2 == NULL) - return 1; - if (pt1->pt_priority != pt2->pt_priority) - return pt1->pt_priority > pt2->pt_priority ? 1 : -1; - } - - // same priority, one that starts first wins - if (col1 != col2) - return col1 < col2 ? 1 : -1; - - // for a property with text the id can be used as tie breaker - if (tp1->tp_id < 0) - return tp1->tp_id > tp2->tp_id ? 1 : -1; - - return 0; + if (n_attr == NULL) + return cells; + return (below && col_with_padding > win_col_off(wp) && !wp->w_p_wrap); } #endif @@ -1219,6 +1268,9 @@ win_line( // Allocate an array for the indexes. text_prop_idxs = ALLOC_MULT(int, text_prop_count); + if (text_prop_idxs == NULL) + VIM_CLEAR(text_props); + area_highlighting = TRUE; extra_check = TRUE; } @@ -1609,8 +1661,9 @@ win_line( { int tpi = text_prop_idxs[pi]; - if (bcol >= text_props[tpi].tp_col - 1 - + text_props[tpi].tp_len) + if (text_props[tpi].tp_col != MAXCOL + && bcol >= text_props[tpi].tp_col - 1 + + text_props[tpi].tp_len) { if (pi + 1 < text_props_active) mch_memmove(text_prop_idxs + pi, @@ -1674,10 +1727,8 @@ win_line( // Sort the properties on priority and/or starting last. // Then combine the attributes, highest priority last. text_prop_follows = FALSE; - current_text_props = text_props; - current_buf = wp->w_buffer; - qsort((void *)text_prop_idxs, (size_t)text_props_active, - sizeof(int), text_prop_compare); + sort_text_props(wp->w_buffer, text_props, + text_prop_idxs, text_props_active); for (pi = 0; pi < text_props_active; ++pi) { @@ -1704,23 +1755,28 @@ win_line( && -text_prop_id <= wp->w_buffer->b_textprop_text.ga_len) { - char_u *p = ((char_u **)wp->w_buffer + textprop_T *tp = &text_props[used_tpi]; + char_u *p = ((char_u **)wp->w_buffer ->b_textprop_text.ga_data)[ -text_prop_id - 1]; // reset the ID in the copy to avoid it being used // again - text_props[used_tpi].tp_id = -MAXCOL; + tp->tp_id = -MAXCOL; if (p != NULL) { - int right = (text_props[used_tpi].tp_flags + int right = (tp->tp_flags & TP_FLAG_ALIGN_RIGHT); - int below = (text_props[used_tpi].tp_flags + int below = (tp->tp_flags & TP_FLAG_ALIGN_BELOW); - int wrap = (text_props[used_tpi].tp_flags - & TP_FLAG_WRAP); + int wrap = (tp->tp_flags & TP_FLAG_WRAP); + int padding = tp->tp_col == MAXCOL + && tp->tp_len > 1 + ? tp->tp_len - 1 : 0; + // Insert virtual text before the current + // character, or add after the end of the line. wlv.p_extra = p; wlv.c_extra = NUL; wlv.c_final = NUL; @@ -1746,72 +1802,30 @@ win_line( // Keep in sync with where // textprop_size_after_trunc() is called in // win_lbr_chartabsize(). - if ((right || below || !wrap) && wp->w_width > 2) + if ((right || below || !wrap || padding > 0) + && wp->w_width > 2) { - int added = wp->w_width - wlv.col; - int n_used = wlv.n_extra; - char_u *l; - int strsize = wrap - ? vim_strsize(wlv.p_extra) - : textprop_size_after_trunc(wp, - below, added, wlv.p_extra, &n_used); - - if (wrap || right || below - || n_used < wlv.n_extra) - { - // Right-align: fill with spaces - if (right) - added -= strsize; - if (added < 0 - || (below - ? wlv.col == 0 || !wp->w_p_wrap - : n_used < wlv.n_extra)) - added = 0; + char_u *prev_p_extra = wlv.p_extra; + int start_line; - // With 'nowrap' add one to show the - // "extends" character if needed (it - // doesn't show it the text just fits). - if (!wp->w_p_wrap - && n_used < wlv.n_extra - && wp->w_lcs_chars.ext != NUL - && wp->w_p_list) - ++n_used; - - // add 1 for NUL, 2 for when '…' is used - l = alloc(n_used + added + 3); - if (l != NULL) - { - vim_memset(l, ' ', added); - vim_strncpy(l + added, wlv.p_extra, - n_used); - if (n_used < wlv.n_extra - && wp->w_p_wrap) - { - char_u *lp = l + added + n_used - 1; - - if (has_mbyte) - { - // change last character to '…' - lp -= (*mb_head_off)(l, lp); - STRCPY(lp, "…"); - n_used = lp - l + 3; - } - else - // change last character to '>' - *lp = '>'; - } - vim_free(p_extra_free2); - wlv.p_extra = p_extra_free2 = l; - wlv.n_extra = n_used + added; - n_attr_skip = added; - n_attr = mb_charlen(wlv.p_extra); - } + // Take care of padding, right-align and + // truncation. + // Shared with win_lbr_chartabsize(), must do + // exactly the same. + start_line = text_prop_position(wp, tp, + wlv.col, + &wlv.n_extra, &wlv.p_extra, + &n_attr, &n_attr_skip); + if (wlv.p_extra != prev_p_extra) + { + // wlv.p_extra was allocated + vim_free(p_extra_free2); + p_extra_free2 = wlv.p_extra; } // When 'wrap' is off then for "below" we need // to start a new line explictly. - if (below && wlv.col > win_col_off(wp) - && !wp->w_p_wrap) + if (start_line) { draw_screen_line(wp, &wlv); diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -1218,6 +1218,8 @@ EXTERN char e_pattern_not_found_str[] INIT(= N_("E486: Pattern not found: %s")); EXTERN char e_argument_must_be_positive[] INIT(= N_("E487: Argument must be positive")); +EXTERN char e_argument_must_be_positive_str[] + INIT(= N_("E487: Argument must be positive: %s")); EXTERN char e_trailing_characters[] INIT(= N_("E488: Trailing characters")); EXTERN char e_trailing_characters_str[] @@ -3319,4 +3321,6 @@ EXTERN char e_can_only_use_text_align_wh #ifdef FEAT_PROP_POPUP EXTERN char e_cannot_specify_both_type_and_types[] INIT(= N_("E1295: Cannot specify both 'type' and 'types'")); +EXTERN char e_can_only_use_left_padding_when_column_is_zero[] + INIT(= N_("E1296: Can only use left padding when column is zero")); #endif diff --git a/src/proto/drawline.pro b/src/proto/drawline.pro --- a/src/proto/drawline.pro +++ b/src/proto/drawline.pro @@ -1,3 +1,4 @@ /* drawline.c */ +int text_prop_position(win_T *wp, textprop_T *tp, int vcol, int *n_extra, char_u **p_extra, int *n_attr, int *n_attr_skip); int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int nochange, int number_only); /* vim: set ft=c : */ diff --git a/src/proto/textprop.pro b/src/proto/textprop.pro --- a/src/proto/textprop.pro +++ b/src/proto/textprop.pro @@ -6,6 +6,7 @@ int prop_add_common(linenr_T start_lnum, int get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change); int prop_count_below(buf_T *buf, linenr_T lnum); int count_props(linenr_T lnum, int only_starting, int last_line); +void sort_text_props(buf_T *buf, textprop_T *props, int *idxs, int count); int find_visible_prop(win_T *wp, int type_id, int id, textprop_T *prop, linenr_T *found_lnum); void add_text_props(linenr_T lnum, textprop_T *text_props, int text_prop_count); proptype_T *text_prop_type_by_id(buf_T *buf, int id); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -800,7 +800,8 @@ typedef struct memline typedef struct textprop_S { colnr_T tp_col; // start column (one based, in bytes) - colnr_T tp_len; // length in bytes + colnr_T tp_len; // length in bytes, when tp_id is negative used + // for left padding plus one int tp_id; // identifier int tp_type; // property type int tp_flags; // TP_FLAG_ values diff --git a/src/testdir/dumps/Test_prop_right_align_twice_2.dump b/src/testdir/dumps/Test_prop_right_align_twice_2.dump --- a/src/testdir/dumps/Test_prop_right_align_twice_2.dump +++ b/src/testdir/dumps/Test_prop_right_align_twice_2.dump @@ -1,8 +1,8 @@ -|s+0&#ffffff0|o|m|e| |m|o|r|e| |t|e|x|t|s|o|m|e| |t|e|x|t| |s|o|m|e| |t|e|x|t| |s|o|m|e| |t|e|x|t| |s|o|m|e| |t|e|x|t| +0&#ffd7ff255|n|o|t|h|i|n|g| |h|e|r|e|S+0#ffffff16#e000002|o|m|e| |e|r@1|o -|r| +0#0000000#ffffff0@60|A+0#ffffff16#e000002|n|o|t|h|e|r| |e|r@1|o|r +|s+0&#ffffff0|o|m|e| |m|o|r|e| |t|e|x|t|s|o|m|e| |t|e|x|t| |s|o|m|e| |t|e|x|t| |s|o|m|e| |t|e|x|t| |s|o|m|e| |t|e|x|t| +0&#ffd7ff255|n|o|t|h|i|n|g| |h|e|r|e| +0&#ffffff0@8 +@65|S+0#ffffff16#e000002|o|m|e| |e|r@1|o|r +| +0#0000000#ffffff0@61|A+0#ffffff16#e000002|n|o|t|h|e|r| |e|r@1|o|r |l+0#0000000#ffffff0|i|n|e| |t|w>o| @66 |~+0#4040ff13&| @73 |~| @73 |~| @73 -|~| @73 | +0#0000000&@56|2|,|8| @10|A|l@1| diff --git a/src/testdir/dumps/Test_prop_text_with_padding_1.dump b/src/testdir/dumps/Test_prop_text_with_padding_1.dump new file mode 100644 --- /dev/null +++ b/src/testdir/dumps/Test_prop_text_with_padding_1.dump @@ -0,0 +1,8 @@ +>S+0&#ffffff0|o|m|e| |t|e|x|t| |t|o| |a|d@1| |v|i|r|t|u|a|l| |t|e|x|t| |t|o|.| @2|a+0&#ffd7ff255|f|t|e|r| +0&#ffffff0@5|r+0&#ffd7ff255|i|g|h|t| |a|l|i|g|n|e|d +| +0&#ffffff0@3|b+0&#ffd7ff255|e|l|o|w| |t|h|e| |l|i|n|e| +0&#ffffff0@41 +|s|e|c|o|n|d| |l|i|n|e| @48 +|A|n|o|t|h|e|r| |l|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |t|o| |m|a|k|e| |t|h|e| |w|r|a|p|.| @5|r+0&#ffd7ff255|i|g|h|t|m|o|s|t +|~+0#4040ff13#ffffff0| @58 +|~| @58 +|~| @58 +| +0#0000000&@41|1|,|1| @10|A|l@1| diff --git a/src/testdir/dumps/Test_prop_text_with_padding_2.dump b/src/testdir/dumps/Test_prop_text_with_padding_2.dump new file mode 100644 --- /dev/null +++ b/src/testdir/dumps/Test_prop_text_with_padding_2.dump @@ -0,0 +1,8 @@ +|x+0&#ffffff0@9|S|o|m|e| |t|e|x|t| |t|o| |a|d@1| |v|i|r|t|u|a|l| |t|e|x|t| |t|o|.| @2|a+0&#ffd7ff255|f|t|e|r| +0&#ffffff0@4|r+0&#ffd7ff255|i|g|… +| +0&#ffffff0@3|b+0&#ffd7ff255|e|l|o|w| |t|h|e| |l|i|n|e| +0&#ffffff0@41 +|s|e|c|o|n|d| |l|i|n|e| @48 +>x|A|n|o|t|h|e|r| |l|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |t|o| |m|a|k|e| |t|h|e| |w|r|a|p|.| @13 +@51|r+0&#ffd7ff255|i|g|h|t|m|o|s|t +|~+0#4040ff13#ffffff0| @58 +|~| @58 +| +0#0000000&@41|3|,|1| @10|A|l@1| diff --git a/src/testdir/dumps/Test_prop_text_with_padding_3.dump b/src/testdir/dumps/Test_prop_text_with_padding_3.dump new file mode 100644 --- /dev/null +++ b/src/testdir/dumps/Test_prop_text_with_padding_3.dump @@ -0,0 +1,8 @@ +>x+0&#ffffff0@10|S|o|m|e| |t|e|x|t| |t|o| |a|d@1| |v|i|r|t|u|a|l| |t|e|x|t| |t|o|.| @2|a+0&#ffd7ff255|f|t|e|r| +0&#ffffff0@7 +@47|r+0&#ffd7ff255|i|g|h|t| |a|l|i|g|n|e|d +| +0&#ffffff0@3|b+0&#ffd7ff255|e|l|o|w| |t|h|e| |l|i|n|e| +0&#ffffff0@41 +|s|e|c|o|n|d| |l|i|n|e| @48 +|x|A|n|o|t|h|e|r| |l|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |t|o| |m|a|k|e| |t|h|e| |w|r|a|p|.| @13 +@51|r+0&#ffd7ff255|i|g|h|t|m|o|s|t +|~+0#4040ff13#ffffff0| @58 +| +0#0000000&@41|1|,|1| @10|A|l@1| diff --git a/src/testdir/dumps/Test_prop_with_text_after_wraps_1.dump b/src/testdir/dumps/Test_prop_with_text_after_wraps_1.dump --- a/src/testdir/dumps/Test_prop_with_text_after_wraps_1.dump +++ b/src/testdir/dumps/Test_prop_with_text_after_wraps_1.dump @@ -1,8 +1,8 @@ |o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| +0&#ffff4012|O|N|E| |a|n|d| |T|W|O| |a|n|d| |T|H|R|E@1| |a|n|d| |F|O|U|R| |a|n|d| |F|I|V|E| +0&#ffffff0@46 -|o|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| +0&#ffff4012|o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e@1| |A|N|D| -|f|o|u|r| |A|N|D| |f|i|v|e| +0&#ffffff0@46 |o|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| @26 +@20| +0&#ffff4012|o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e@1| |A|N|D| |f|o|u|r| |A|N|D| |f|i|v|e +|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| @26 | +0&#ffff4012|o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e@1| |A|N|D| |f|o|u|r| |A|N|D| |f|i|v|e| |l|e|t|s| |w|r|a|p| |a|f|t|e|r| |s|o|m |e| |m|o|r|e| |t|e|x|t| +0&#ffffff0@48 |c|u|r|s|o|r| >h|e|r|e| @48 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 @@ -3041,4 +3041,54 @@ func Test_insert_text_list_mode() call delete('XscriptPropsListMode') endfunc +func Test_insert_text_with_padding() + CheckRunVimInTerminal + + let lines =<< trim END + vim9script + setline(1, ['Some text to add virtual text to.', + 'second line', + 'Another line with some text to make the wrap.']) + prop_type_add('theprop', {highlight: 'DiffChange'}) + prop_add(1, 0, { + type: 'theprop', + text: 'after', + text_align: 'after', + text_padding_left: 3, + }) + prop_add(1, 0, { + type: 'theprop', + text: 'right aligned', + text_align: 'right', + text_padding_left: 5, + }) + prop_add(1, 0, { + type: 'theprop', + text: 'below the line', + text_align: 'below', + text_padding_left: 4, + }) + prop_add(3, 0, { + type: 'theprop', + text: 'rightmost', + text_align: 'right', + text_padding_left: 6, + text_wrap: 'wrap', + }) + END + call writefile(lines, 'XscriptPropsPadded') + let buf = RunVimInTerminal('-S XscriptPropsPadded', #{rows: 8, cols: 60}) + call VerifyScreenDump(buf, 'Test_prop_text_with_padding_1', {}) + + call term_sendkeys(buf, "ggixxxxxxxxxx\") + call term_sendkeys(buf, "3Gix\") + call VerifyScreenDump(buf, 'Test_prop_text_with_padding_2', {}) + + call term_sendkeys(buf, "ggix\") + call VerifyScreenDump(buf, 'Test_prop_text_with_padding_3', {}) + + call StopVimInTerminal(buf) + call delete('XscriptPropsPadded') +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 @@ -171,6 +171,7 @@ prop_add_one( char_u *type_name, int id, char_u *text_arg, + int text_padding_left, int text_flags, linenr_T start_lnum, linenr_T end_lnum, @@ -264,7 +265,10 @@ prop_add_one( { length = 1; // text is placed on one character if (col == 0) + { col = MAXCOL; // after the line + length += text_padding_left; + } } // Allocate the new line with space for the new property. @@ -390,7 +394,7 @@ f_prop_add_list(typval_T *argvars, typva emsg(_(e_invalid_argument)); return; } - if (prop_add_one(buf, type_name, id, NULL, 0, start_lnum, end_lnum, + if (prop_add_one(buf, type_name, id, NULL, 0, 0, start_lnum, end_lnum, start_col, end_col) == FAIL) return; } @@ -428,6 +432,7 @@ prop_add_common( buf_T *buf = default_buf; int id = 0; char_u *text = NULL; + int text_padding_left = 0; int flags = 0; if (dict == NULL || !dict_has_key(dict, "type")) @@ -507,9 +512,20 @@ prop_add_common( } } + if (dict_has_key(dict, "text_padding_left")) + { + text_padding_left = dict_get_number(dict, "text_padding_left"); + if (text_padding_left < 0) + { + semsg(_(e_argument_must_be_positive_str), "text_padding_left"); + goto theend; + } + } + if (dict_has_key(dict, "text_wrap")) { char_u *p = dict_get_string(dict, "text_wrap", FALSE); + if (p == NULL) goto theend; if (STRCMP(p, "wrap") == 0) @@ -529,6 +545,11 @@ prop_add_common( semsg(_(e_invalid_column_number_nr), (long)start_col); goto theend; } + if (start_col > 0 && text_padding_left > 0) + { + emsg(_(e_can_only_use_left_padding_when_column_is_zero)); + goto theend; + } if (dict_arg != NULL && get_bufnr_from_arg(dict_arg, &buf) == FAIL) goto theend; @@ -546,7 +567,7 @@ prop_add_common( // correctly set. buf->b_has_textprop = TRUE; // this is never reset - prop_add_one(buf, type_name, id, text, flags, + prop_add_one(buf, type_name, id, text, text_padding_left, flags, start_lnum, end_lnum, start_col, end_col); text = NULL; @@ -655,6 +676,91 @@ count_props(linenr_T lnum, int only_star return result; } +static textprop_T *text_prop_compare_props; +static buf_T *text_prop_compare_buf; + +/* + * Function passed to qsort() to sort text properties. + * Return 1 if "s1" has priority over "s2", -1 if the other way around, zero if + * both have the same priority. + */ + static int +text_prop_compare(const void *s1, const void *s2) +{ + int idx1, idx2; + textprop_T *tp1, *tp2; + proptype_T *pt1, *pt2; + colnr_T col1, col2; + + idx1 = *(int *)s1; + idx2 = *(int *)s2; + tp1 = &text_prop_compare_props[idx1]; + tp2 = &text_prop_compare_props[idx2]; + col1 = tp1->tp_col; + col2 = tp2->tp_col; + if (col1 == MAXCOL && col2 == MAXCOL) + { + int flags1 = 0; + int flags2 = 0; + + // 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; + } + + // property that inserts text has priority over one that doesn't + if ((tp1->tp_id < 0) != (tp2->tp_id < 0)) + return tp1->tp_id < 0 ? 1 : -1; + + // check highest priority, defined by the type + pt1 = text_prop_type_by_id(text_prop_compare_buf, tp1->tp_type); + pt2 = text_prop_type_by_id(text_prop_compare_buf, tp2->tp_type); + if (pt1 != pt2) + { + if (pt1 == NULL) + return -1; + if (pt2 == NULL) + return 1; + if (pt1->pt_priority != pt2->pt_priority) + return pt1->pt_priority > pt2->pt_priority ? 1 : -1; + } + + // same priority, one that starts first wins + if (col1 != col2) + return col1 < col2 ? 1 : -1; + + // for a property with text the id can be used as tie breaker + if (tp1->tp_id < 0) + return tp1->tp_id > tp2->tp_id ? 1 : -1; + + return 0; +} + +/* + * Sort "count" text properties using an array if indexes "idxs" into the list + * of text props "props" for buffer "buf". + */ + void +sort_text_props( + buf_T *buf, + textprop_T *props, + int *idxs, + int count) +{ + text_prop_compare_buf = buf; + text_prop_compare_props = props; + qsort((void *)idxs, (size_t)count, sizeof(int), text_prop_compare); +} + /* * Find text property "type_id" in the visible lines of window "wp". * Match "id" when it is > 0. diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -732,6 +732,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 247, +/**/ 246, /**/ 245,