]> granicus.if.org Git - vim/commitdiff
patch 9.0.0121: cannot put virtual text after or below a line v9.0.0121
authorBram Moolenaar <Bram@vim.org>
Sun, 31 Jul 2022 16:12:43 +0000 (17:12 +0100)
committerBram Moolenaar <Bram@vim.org>
Sun, 31 Jul 2022 16:12:43 +0000 (17:12 +0100)
Problem:    Cannot put virtual text after or below a line.
Solution:   Add "text_align" and "text_wrap" arguments.

runtime/doc/textprop.txt
src/charset.c
src/drawline.c
src/structs.h
src/testdir/dumps/Test_prop_with_text_after_1.dump [new file with mode: 0644]
src/testdir/test_textprop.vim
src/textprop.c
src/version.c

index d231082f07448687fac64a2cf9f2ecceaa3a31de..a0c8c348d70770446e4306f94abe5572a5822f16 100644 (file)
@@ -141,7 +141,20 @@ prop_add({lnum}, {col}, {props})
                                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}
+                  text         text to be displayed before {col}, or after the
+                               line if {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
+                                  below   in the next screen line
+                               When omitted "after" is used.
+                  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.
 
@@ -162,17 +175,26 @@ prop_add({lnum}, {col}, {props})
                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".
+               When "text" is used and the column is non-zero then this text
+               will be displayed at the start location of the text property
+               after the text.  The text of the buffer line will be shifted
+               to make room.  This is called "virtual text".
+               When the column is zero the virtual text will appear after the
+               buffer text.  The "text_align" and "text_wrap" arguments
+               determine how it is displayed.
                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.
+               the text, or the last character of the line.
                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:
                *E1293*
+               Make sure to use a highlight that makes clear to the user that
+               this is virtual text, otherwise it will be very confusing that
+               the text cannot be edited.
+               To separate the virtual text from the buffer text prepend
+               and/or append spaces to the "text" field.
 
                Can also be used as a |method|: >
                        GetLnum()->prop_add(col, props)
index f3d12cbe86b9a142f2100c97ad927f8ccca0328e..b55468fbdc4ab3bf9015d38064090693affc14cd 100644 (file)
@@ -771,6 +771,7 @@ win_linetabsize(win_T *wp, linenr_T lnum, char_u *line, colnr_T len)
     chartabsize_T cts;
 
     init_chartabsize_arg(&cts, wp, lnum, 0, line, line);
+    cts.cts_with_trailing = len = MAXCOL;
     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);
@@ -1089,15 +1090,24 @@ win_lbr_chartabsize(
            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)
+                   && ((tp->tp_col - 1 >= col && tp->tp_col - 1 < col + size
+                       && -tp->tp_id <= wp->w_buffer->b_textprop_text.ga_len)
+                   || (tp->tp_col == MAXCOL && (s[0] == NUL || s[1] == NUL)
+                                                  && cts->cts_with_trailing)))
            {
                char_u *p = ((char_u **)wp->w_buffer->b_textprop_text.ga_data)[
                                                               -tp->tp_id - 1];
+               int len = (int)STRLEN(p);
+
                // TODO: count screen cells
-               cts->cts_cur_text_width = (int)STRLEN(p);
-               size += cts->cts_cur_text_width;
-               break;
+               if (tp->tp_col == MAXCOL)
+               {
+                   // TODO: truncating
+                   if (tp->tp_flags & TP_FLAG_ALIGN_BELOW)
+                       len += wp->w_width - (vcol + size) % wp->w_width;
+               }
+               cts->cts_cur_text_width += len;
+               size += len;
            }
            if (tp->tp_col - 1 > col)
                break;
index be9ed9d36219a0245ad9229ef2d0c268cf9eb9cd..af8c053b7c747f10287f2ff470c5c8d9c5c7f845 100644 (file)
@@ -208,13 +208,16 @@ static buf_T              *current_buf = NULL;
 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;
-    pt1 = text_prop_type_by_id(current_buf, current_text_props[idx1].tp_type);
-    pt2 = text_prop_type_by_id(current_buf, current_text_props[idx2].tp_type);
+    tp1 = &current_text_props[idx1];
+    tp2 = &current_text_props[idx2];
+    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)
        return 0;
     if (pt1 == NULL)
@@ -223,8 +226,25 @@ text_prop_compare(const void *s1, const void *s2)
        return 1;
     if (pt1->pt_priority != pt2->pt_priority)
        return pt1->pt_priority > pt2->pt_priority ? 1 : -1;
-    col1 = current_text_props[idx1].tp_col;
-    col2 = current_text_props[idx2].tp_col;
+    col1 = tp1->tp_col;
+    col2 = tp2->tp_col;
+    if (col1 == MAXCOL && col2 == MAXCOL)
+    {
+       int flags1 = 0;
+       int flags2 = 0;
+
+       // order on 0: after, 1: right, 2: below
+       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;
+    }
     return col1 == col2 ? 0 : col1 > col2 ? 1 : -1;
 }
 #endif
@@ -281,10 +301,11 @@ win_line(
     int                saved_c_final = 0;
     int                saved_char_attr = 0;
 
-    int                n_attr = 0;             // chars with special attr
-    int                saved_attr2 = 0;        // char_attr saved for n_attr
-    int                n_attr3 = 0;            // chars with overruling special attr
-    int                saved_attr3 = 0;        // char_attr saved for n_attr3
+    int                n_attr = 0;         // chars with special attr
+    int                n_attr_skip = 0;    // chars to skip before using extra_attr
+    int                saved_attr2 = 0;    // char_attr saved for n_attr
+    int                n_attr3 = 0;        // chars with overruling special attr
+    int                saved_attr3 = 0;    // char_attr saved for n_attr3
 
     int                n_skip = 0;             // nr of chars to skip for 'nowrap'
 
@@ -328,6 +349,7 @@ win_line(
     int                text_prop_attr = 0;
     int                text_prop_id = 0;       // active property ID
     int                text_prop_combine = FALSE;
+    int                text_prop_follows = FALSE;  // another text prop to display
 #endif
 #ifdef FEAT_SPELL
     int                has_spell = FALSE;      // this buffer has spell checking
@@ -1472,7 +1494,9 @@ win_line(
 # endif
                // Add any text property that starts in this column.
                while (text_prop_next < text_prop_count
-                          && bcol >= text_props[text_prop_next].tp_col - 1)
+                          && (text_props[text_prop_next].tp_col == MAXCOL
+                             ? *ptr == NUL
+                             : bcol >= text_props[text_prop_next].tp_col - 1))
                {
                    if (bcol <= text_props[text_prop_next].tp_col - 1
                                           + text_props[text_prop_next].tp_len)
@@ -1484,13 +1508,15 @@ win_line(
                text_prop_combine = FALSE;
                text_prop_type = NULL;
                text_prop_id = 0;
-               if (text_props_active > 0)
+               if (text_props_active > 0 && n_extra == 0)
                {
                    int used_tpi = -1;
                    int used_attr = 0;
+                   int other_tpi = -1;
 
                    // 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,
@@ -1511,10 +1537,11 @@ win_line(
                                   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;
+                           other_tpi = used_tpi;
                            used_tpi = tpi;
                        }
                    }
-                   if (n_extra == 0 && text_prop_id < 0 && used_tpi >= 0
+                   if (text_prop_id < 0 && used_tpi >= 0
                            && -text_prop_id
                                      <= wp->w_buffer->b_textprop_text.ga_len)
                    {
@@ -1523,6 +1550,11 @@ win_line(
                                                           -text_prop_id - 1];
                        if (p != NULL)
                        {
+                           int     right = (text_props[used_tpi].tp_flags
+                                                       & TP_FLAG_ALIGN_RIGHT);
+                           int     below = (text_props[used_tpi].tp_flags
+                                                       & TP_FLAG_ALIGN_BELOW);
+
                            p_extra = p;
                            c_extra = NUL;
                            c_final = NUL;
@@ -1530,6 +1562,33 @@ win_line(
                            extra_attr = used_attr;
                            n_attr = n_extra;
                            text_prop_attr = 0;
+                           if (*ptr == NUL)
+                               // don't combine char attr after EOL
+                               text_prop_combine = FALSE;
+
+                           // TODO: truncation if it doesn't fit
+                           if (right || below)
+                           {
+                               int     added = wp->w_width - col;
+                               char_u  *l;
+
+                               // Right-align: fill with spaces
+                               // TODO: count screen columns
+                               if (right)
+                                   added -= n_extra;
+                               if (added < 0 || (below && col == 0))
+                                   added = 0;
+                               l = alloc(n_extra + added + 1);
+                               if (l != NULL)
+                               {
+                                   vim_memset(l, ' ', added);
+                                   STRCPY(l + added, p);
+                                   vim_free(p_extra_free);
+                                   p_extra = p_extra_free = l;
+                                   n_extra += added;
+                                   n_attr_skip = added;
+                               }
+                           }
 
                            // If the cursor is on or after this position,
                            // move it forward.
@@ -1541,6 +1600,10 @@ win_line(
                        // reset the ID in the copy to avoid it being used
                        // again
                        text_props[used_tpi].tp_id = -MAXCOL;
+
+                       // If another text prop follows the condition below at
+                       // the last window column must know.
+                       text_prop_follows = other_tpi != -1;
                    }
                }
            }
@@ -2641,8 +2704,9 @@ win_line(
        }
 #endif
 
-       // Don't override visual selection highlighting.
-       if (n_attr > 0
+       // Use "extra_attr", but don't override visual selection highlighting.
+       // Don't use "extra_attr" until n_attr_skip is zero.
+       if (n_attr_skip == 0 && n_attr > 0
                && draw_state == WL_LINE
                && !attr_pri)
        {
@@ -3188,8 +3252,11 @@ win_line(
            char_attr = saved_attr3;
 
        // restore attributes after last 'listchars' or 'number' char
-       if (n_attr > 0 && draw_state == WL_LINE && --n_attr == 0)
+       if (n_attr > 0 && draw_state == WL_LINE
+                                         && n_attr_skip == 0 && --n_attr == 0)
            char_attr = saved_attr2;
+       if (n_attr_skip > 0)
+           --n_attr_skip;
 
        // At end of screen line and there is more to come: Display the line
        // so far.  If there is no more to display it is caught above.
@@ -3202,6 +3269,9 @@ win_line(
                    || *ptr != NUL
 #ifdef FEAT_DIFF
                    || filler_todo > 0
+#endif
+#ifdef FEAT_PROP_POPUP
+                   || text_prop_follows
 #endif
                    || (wp->w_p_list && wp->w_lcs_chars.eol != NUL
                                                && p_extra != at_end_str)
@@ -3223,7 +3293,10 @@ win_line(
            // '$' and highlighting until last column, break here.
            if ((!wp->w_p_wrap
 #ifdef FEAT_DIFF
-                   && filler_todo <= 0
+                       && filler_todo <= 0
+#endif
+#ifdef FEAT_PROP_POPUP
+                       && !text_prop_follows
 #endif
                    ) || lcs_eol_one == -1)
                break;
@@ -3250,6 +3323,9 @@ win_line(
            if (screen_cur_row == screen_row - 1
 #ifdef FEAT_DIFF
                     && filler_todo <= 0
+#endif
+#ifdef FEAT_PROP_POPUP
+                    && !text_prop_follows
 #endif
                     && wp->w_width == Columns)
            {
index d5276724d8e3034822d2f7af9c6500fc1dce2a17..71114b0ce8eb199ccb41362aa11a94a364873546 100644 (file)
@@ -808,7 +808,13 @@ typedef struct textprop_S
 
 #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
+
+// 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
+                                       // text is truncated
 
 /*
  * Structure defining a property type.
@@ -4575,6 +4581,8 @@ 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_with_trailing;      // include size of trailing props with
+                                       // last character
 #endif
     int                cts_vcol;           // virtual column at current position
 } chartabsize_T;
diff --git a/src/testdir/dumps/Test_prop_with_text_after_1.dump b/src/testdir/dumps/Test_prop_with_text_after_1.dump
new file mode 100644 (file)
index 0000000..27ba686
--- /dev/null
@@ -0,0 +1,6 @@
+>s+0&#ffffff0|o|m|e| |t|e|x|t| |h|e|r|e| |a|n|d| |o|t|h|e|r| |t|e|x|t| |t|h|e|r|e| +0&#ffff4012|A|F|T|E|R| | +0&#ffffff0@10| +0#ffffff16#e000002|R|I|G|H|T| 
+| +0#0000000#5fd7ff255|B|E|L|O|W| | +0&#ffffff0@52
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|,|1| @10|A|l@1| 
index a3a3c00bb49f0307316c7aa5b9f6427a5b1ea827..987df98dd2cb541295b0728a9fc9bbe2a9222e88 100644 (file)
@@ -2213,6 +2213,26 @@ func Test_prop_inserts_text()
   call delete('XscriptPropsWithText')
 endfunc
 
+func Test_props_with_text_after()
+  CheckRunVimInTerminal
+
+  let lines =<< trim END
+      call setline(1, 'some text here and other text there')
+      call prop_type_add('rightprop', #{highlight: 'ErrorMsg'})
+      call prop_type_add('afterprop', #{highlight: 'Search'})
+      call prop_type_add('belowprop', #{highlight: 'DiffAdd'})
+      call prop_add(1, 0, #{type: 'rightprop', text: ' RIGHT ', text_align: 'right'})
+      call prop_add(1, 0, #{type: 'afterprop', text: ' AFTER ', text_align: 'after'})
+      call prop_add(1, 0, #{type: 'belowprop', text: ' BELOW ', text_align: 'below'})
+  END
+  call writefile(lines, 'XscriptPropsWithTextAfter')
+  let buf = RunVimInTerminal('-S XscriptPropsWithTextAfter', #{rows: 6, cols: 60})
+  call VerifyScreenDump(buf, 'Test_prop_with_text_after_1', {})
+
+  call StopVimInTerminal(buf)
+  call delete('XscriptPropsWithTextAfter')
+endfunc
+
 func Test_removed_prop_with_text_cleans_up_array()
   new
   call setline(1, 'some text here')
index 939b1640747c1429308957dcbacdb7419073aa37..f544a3ce5a1701a9ae96e3cc32217115aba770a9 100644 (file)
@@ -163,11 +163,6 @@ f_prop_add(typval_T *argvars, typval_T *rettv)
 
     start_lnum = tv_get_number(&argvars[0]);
     start_col = tv_get_number(&argvars[1]);
-    if (start_col < 1)
-    {
-       semsg(_(e_invalid_column_number_nr), (long)start_col);
-       return;
-    }
     if (argvars[2].v_type != VAR_DICT)
     {
        emsg(_(e_dictionary_required));
@@ -190,6 +185,7 @@ prop_add_one(
        char_u          *type_name,
        int             id,
        char_u          *text_arg,
+       int             text_flags,
        linenr_T        start_lnum,
        linenr_T        end_lnum,
        colnr_T         start_col,
@@ -256,7 +252,7 @@ prop_add_one(
            col = start_col;
        else
            col = 1;
-       if (col - 1 > (colnr_T)textlen)
+       if (col - 1 > (colnr_T)textlen && !(col == 0 && text_arg != NULL))
        {
            semsg(_(e_invalid_column_number_nr), (long)start_col);
            goto theend;
@@ -271,6 +267,13 @@ prop_add_one(
        if (length < 0)
            length = 0;         // zero-width property
 
+       if (text_arg != NULL)
+       {
+           length = 1;         // text is placed on one character
+           if (col == 0)
+               col = MAXCOL;   // after the line
+       }
+
        // Allocate the new line with space for the new property.
        newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T));
        if (newtext == NULL)
@@ -296,8 +299,9 @@ prop_add_one(
        tmp_prop.tp_len = length;
        tmp_prop.tp_id = id;
        tmp_prop.tp_type = type->pt_id;
-       tmp_prop.tp_flags = (lnum > start_lnum ? TP_FLAG_CONT_PREV : 0)
-                         | (lnum < end_lnum ? TP_FLAG_CONT_NEXT : 0);
+       tmp_prop.tp_flags = text_flags
+                           | (lnum > start_lnum ? TP_FLAG_CONT_PREV : 0)
+                           | (lnum < end_lnum ? TP_FLAG_CONT_NEXT : 0);
        mch_memmove(newprops + i * sizeof(textprop_T), &tmp_prop,
                                                           sizeof(textprop_T));
 
@@ -390,7 +394,7 @@ f_prop_add_list(typval_T *argvars, typval_T *rettv UNUSED)
            emsg(_(e_invalid_argument));
            return;
        }
-       if (prop_add_one(buf, type_name, id, NULL, start_lnum, end_lnum,
+       if (prop_add_one(buf, type_name, id, NULL, 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                flags = 0;
 
     if (dict == NULL || !dict_has_key(dict, "type"))
     {
@@ -483,6 +488,45 @@ prop_add_common(
            goto theend;
        // use a default length of 1 to make multiple props show up
        end_col = start_col + 1;
+
+       if (dict_has_key(dict, "text_align"))
+       {
+           char_u *p = dict_get_string(dict, "text_align", FALSE);
+
+           if (p == NULL)
+               goto theend;
+           if (STRCMP(p, "right") == 0)
+               flags |= TP_FLAG_ALIGN_RIGHT;
+           else if (STRCMP(p, "below") == 0)
+               flags |= TP_FLAG_ALIGN_BELOW;
+           else if (STRCMP(p, "after") != 0)
+           {
+               semsg(_(e_invalid_value_for_argument_str_str), "text_align", p);
+               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)
+               flags |= TP_FLAG_WRAP;
+           else if (STRCMP(p, "truncate") != 0)
+           {
+               semsg(_(e_invalid_value_for_argument_str_str), "text_wrap", p);
+               goto theend;
+           }
+       }
+    }
+
+    // Column must be 1 or more for a normal text property; when "text" is
+    // present zero means it goes after the line.
+    if (start_col < (text == NULL ? 1 : 0))
+    {
+       semsg(_(e_invalid_column_number_nr), (long)start_col);
+       goto theend;
     }
 
     if (dict_arg != NULL && get_bufnr_from_arg(dict_arg, &buf) == FAIL)
@@ -501,7 +545,7 @@ prop_add_common(
     // correctly set.
     buf->b_has_textprop = TRUE;  // this is never reset
 
-    prop_add_one(buf, type_name, id, text,
+    prop_add_one(buf, type_name, id, text, flags,
                                    start_lnum, end_lnum, start_col, end_col);
     text = NULL;
 
@@ -1738,22 +1782,32 @@ typedef struct
  */
     static adjustres_T
 adjust_prop(
-       textprop_T *prop,
-       colnr_T col,
-       int added,
-       int flags)
+       textprop_T  *prop,
+       colnr_T     col,
+       int         added,
+       int         flags)
 {
-    proptype_T *pt = text_prop_type_by_id(curbuf, prop->tp_type);
-    int                start_incl = (pt != NULL
-                                   && (pt->pt_flags & PT_FLAG_INS_START_INCL))
+    proptype_T *pt;
+    int                start_incl;
+    int                end_incl;
+    int                droppable;
+    adjustres_T res = {TRUE, FALSE};
+
+    // prop after end of the line doesn't move
+    if (prop->tp_col == MAXCOL)
+    {
+       res.dirty = FALSE;
+       return res;
+    }
+
+    pt = text_prop_type_by_id(curbuf, prop->tp_type);
+    start_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_START_INCL))
                                || (flags & APC_SUBSTITUTE)
                                || (prop->tp_flags & TP_FLAG_CONT_PREV);
-    int                end_incl = (pt != NULL
-                                     && (pt->pt_flags & PT_FLAG_INS_END_INCL))
+    end_incl = (pt != NULL && (pt->pt_flags & PT_FLAG_INS_END_INCL))
                                || (prop->tp_flags & TP_FLAG_CONT_NEXT);
-    // Do not drop zero-width props if they later can increase in size.
-    int                droppable = !(start_incl || end_incl);
-    adjustres_T res = {TRUE, FALSE};
+    // do not drop zero-width props if they later can increase in size
+    droppable = !(start_incl || end_incl);
 
     if (added > 0)
     {
index c5b76797460f0f9f4ca05bea431792c2e8e87d9c..af52ab853ee6226709b40e794c7c3d281dc88791 100644 (file)
@@ -735,6 +735,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    121,
 /**/
     120,
 /**/