Patch 9.0.0067
Problem:    Cannot show virtual text.
Solution:   Initial changes for virtual text support, using text properties.
Files:      runtime/doc/textprop.txt, src/beval.c, src/charset.c,
            src/drawline.c, src/edit.c, src/errors.h, src/evalfunc.c,
            src/getchar.c, src/indent.c, src/misc1.c, src/misc2.c,
            src/mouse.c, src/ops.c, src/popupwin.c, src/proto/charset.pro,
            src/proto/textprop.pro, src/regexp.c, src/regexp_bt.c,
            src/regexp_nfa.c, src/register.c, src/structs.h, src/textprop.c,
            src/testdir/test_textprop.vim,
            src/testdir/dumps/Test_prop_inserts_text.dump


*** ../vim-9.0.0066/runtime/doc/textprop.txt    2022-06-28 11:21:06.000000000 
+0100
--- runtime/doc/textprop.txt    2022-07-25 15:42:47.103101003 +0100
***************
*** 137,143 ****
                   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
                   type         name of the text property type
                All fields except "type" are optional.
  
--- 137,147 ----
                   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, 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,162 ****
--- 161,177 ----
                "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,186 ****
--- 196,204 ----
                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],
*** ../vim-9.0.0066/src/beval.c 2022-05-27 17:18:23.000000000 +0100
--- src/beval.c 2022-07-25 15:42:47.103101003 +0100
***************
*** 47,53 ****
        {
            // Not past end of the file.
            lbuf = ml_get_buf(wp->w_buffer, lnum, FALSE);
!           if (col <= win_linetabsize(wp, lbuf, (colnr_T)MAXCOL))
            {
                // Not past end of line.
                if (getword)
--- 47,53 ----
        {
            // Not past end of the file.
            lbuf = ml_get_buf(wp->w_buffer, lnum, FALSE);
!           if (col <= win_linetabsize(wp, lnum, lbuf, (colnr_T)MAXCOL))
            {
                // Not past end of line.
                if (getword)
*** ../vim-9.0.0066/src/charset.c       2022-05-22 19:57:32.000000000 +0100
--- src/charset.c       2022-07-25 16:54:09.730345955 +0100
***************
*** 12,19 ****
  #if defined(HAVE_WCHAR_H)
  # include <wchar.h>       // for towupper() and towlower()
  #endif
- static int win_nolbr_chartabsize(win_T *wp, char_u *s, colnr_T col, int 
*headp);
  
  static unsigned nr2hex(unsigned c);
  
  static int    chartab_initialized = FALSE;
--- 12,19 ----
  #if defined(HAVE_WCHAR_H)
  # include <wchar.h>       // for towupper() and towlower()
  #endif
  
+ static int win_nolbr_chartabsize(chartabsize_T *cts, int *headp);
  static unsigned nr2hex(unsigned c);
  
  static int    chartab_initialized = FALSE;
***************
*** 737,744 ****
  #endif
  
  /*
!  * Return the number of characters the string 's' will take on the screen,
   * taking into account the size of a tab.
   */
      int
  linetabsize(char_u *s)
--- 737,745 ----
  #endif
  
  /*
!  * 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,778 ****
  }
  
  /*
!  * Like linetabsize(), but starting 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
  
!     while (*s != NUL)
!       col += lbr_chartabsize_adv(line, &s, col);
!     return (int)col;
  }
  
  /*
   * Like linetabsize(), but for a given window instead of the current one.
   */
      int
! win_linetabsize(win_T *wp, char_u *line, colnr_T len)
  {
!     colnr_T   col = 0;
!     char_u    *s;
  
!     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;
  }
  
  /*
--- 748,781 ----
  }
  
  /*
!  * Like linetabsize(), but "s" starts at column "startcol".
   */
      int
  linetabsize_col(int startcol, char_u *s)
  {
!     chartabsize_T cts;
  
!     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, linenr_T lnum, char_u *line, colnr_T len)
  {
!     chartabsize_T cts;
  
!     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,917 ****
  }
  
  /*
!  * like chartabsize(), but also check for line breaks on the screen
   */
      int
! lbr_chartabsize(
!     char_u            *line UNUSED, // start of the line
!     unsigned char     *s,
!     colnr_T           col)
  {
! #ifdef FEAT_LINEBREAK
!     if (!curwin->w_p_lbr && *get_showbreak_value(curwin) == NUL
!                                                          && !curwin->w_p_bri)
      {
  #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_lbr_chartabsize(curwin, line == NULL ? s : line, s, col, NULL);
  #endif
  }
  
--- 896,996 ----
  }
  
  /*
!  * 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(chartabsize_T *cts)
  {
! #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(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(cts, NULL);
  #endif
  }
  
***************
*** 919,937 ****
   * Call lbr_chartabsize() and advance the pointer.
   */
      int
! lbr_chartabsize_adv(
!     char_u    *line, // start of the line
!     char_u    **s,
!     colnr_T   col)
  {
      int               retval;
  
!     retval = lbr_chartabsize(line, *s, col);
!     MB_PTR_ADV(*s);
      return retval;
  }
  
  /*
   * This function is used very often, keep it fast!!!!
   *
   * If "headp" not NULL, set *headp to the size of what we for 'showbreak'
--- 998,1016 ----
   * Call lbr_chartabsize() and advance the pointer.
   */
      int
! lbr_chartabsize_adv(chartabsize_T *cts)
  {
      int               retval;
  
!     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,956 ****
   */
      int
  win_lbr_chartabsize(
!     win_T     *wp,
!     char_u    *line UNUSED, // start of the line
!     char_u    *s,
!     colnr_T   col,
!     int               *headp UNUSED)
  {
  #ifdef FEAT_LINEBREAK
      int               c;
      int               size;
      colnr_T   col2;
!     colnr_T   col_adj = 0; // col + screen size of tab
      colnr_T   colmax;
      int               added;
      int               mb_added = 0;
--- 1019,1036 ----
   */
      int
  win_lbr_chartabsize(
!       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; // vcol + screen size of tab
      colnr_T   colmax;
      int               added;
      int               mb_added = 0;
***************
*** 959,981 ****
      int               tab_corr = (*s == TAB);
      int               n;
      char_u    *sbr;
  
      /*
!      * No 'linebreak', 'showbreak' and 'breakindent': return quickly.
       */
!     if (!wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL)
  #endif
      {
        if (wp->w_p_wrap)
!           return win_nolbr_chartabsize(wp, s, col, headp);
!       RET_WIN_BUF_CHARTABSIZE(wp, wp->w_buffer, s, col)
      }
  
! #ifdef FEAT_LINEBREAK
      /*
!      * First get normal size, without 'linebreak'
       */
!     size = win_chartabsize(wp, s, col);
      c = *s;
      if (tab_corr)
        col_adj = size - 1;
--- 1039,1104 ----
      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', 'breakindent' and text properties that
!      * insert text: return quickly.
       */
!     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(cts, headp);
!       RET_WIN_BUF_CHARTABSIZE(wp, wp->w_buffer, s, vcol)
      }
  
! #if defined(FEAT_LINEBREAK) || defined(FEAT_PROP_POPUP)
      /*
!      * First get the normal size, without 'linebreak' or text properties
       */
!     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,1008 ****
         * non-blank after a blank.
         */
        numberextra = win_col_off(wp);
!       col2 = col;
        colmax = (colnr_T)(wp->w_width - numberextra - col_adj);
!       if (col >= colmax)
        {
            colmax += col_adj;
            n = colmax +  win_col_off2(wp);
            if (n > 0)
!               colmax += (((col - colmax) / n) + 1) * n - col_adj;
        }
  
        for (;;)
--- 1118,1131 ----
         * non-blank after a blank.
         */
        numberextra = win_col_off(wp);
!       col2 = vcol;
        colmax = (colnr_T)(wp->w_width - numberextra - col_adj);
!       if (vcol >= colmax)
        {
            colmax += col_adj;
            n = colmax +  win_col_off2(wp);
            if (n > 0)
!               colmax += (((vcol - colmax) / n) + 1) * n - col_adj;
        }
  
        for (;;)
***************
*** 1013,1031 ****
            if (!(c != NUL
                    && (VIM_ISBREAK(c)
                        || (!VIM_ISBREAK(c)
!                           && (col2 == col || !VIM_ISBREAK((int)*ps))))))
                break;
  
            col2 += win_chartabsize(wp, s, col2);
            if (col2 >= colmax)         // doesn't fit
            {
!               size = colmax - col + col_adj;
                break;
            }
        }
      }
      else if (has_mbyte && size == 2 && MB_BYTE2LEN(*s) > 1
!                                   && wp->w_p_wrap && in_win_border(wp, col))
      {
        ++size;         // Count the ">" in the last column.
        mb_added = 1;
--- 1136,1154 ----
            if (!(c != NUL
                    && (VIM_ISBREAK(c)
                        || (!VIM_ISBREAK(c)
!                              && (col2 == vcol || !VIM_ISBREAK((int)*ps))))))
                break;
  
            col2 += win_chartabsize(wp, s, col2);
            if (col2 >= colmax)         // doesn't fit
            {
!               size = colmax - vcol + col_adj;
                break;
            }
        }
      }
      else if (has_mbyte && size == 2 && MB_BYTE2LEN(*s) > 1
!                                  && wp->w_p_wrap && in_win_border(wp, vcol))
      {
        ++size;         // Count the ">" in the last column.
        mb_added = 1;
***************
*** 1039,1071 ****
       */
      added = 0;
      sbr = c == NUL ? empty_option : get_showbreak_value(wp);
!     if ((*sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && col != 0)
      {
        colnr_T sbrlen = 0;
        int     numberwidth = win_col_off(wp);
  
        numberextra = numberwidth;
!       col += numberextra + mb_added;
!       if (col >= (colnr_T)wp->w_width)
        {
!           col -= wp->w_width;
            numberextra = wp->w_width - (numberextra - win_col_off2(wp));
!           if (col >= numberextra && numberextra > 0)
!               col %= numberextra;
            if (*sbr != NUL)
            {
                sbrlen = (colnr_T)MB_CHARLEN(sbr);
!               if (col >= sbrlen)
!                   col -= sbrlen;
            }
!           if (col >= numberextra && numberextra > 0)
!               col = col % numberextra;
!           else if (col > 0 && numberextra > 0)
!               col += numberwidth - win_col_off2(wp);
  
            numberwidth -= win_col_off2(wp);
        }
!       if (col == 0 || col + size + sbrlen > (colnr_T)wp->w_width)
        {
            added = 0;
            if (*sbr != NUL)
--- 1162,1194 ----
       */
      added = 0;
      sbr = c == NUL ? empty_option : get_showbreak_value(wp);
!     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;
!       vcol += numberextra + mb_added;
!       if (vcol >= (colnr_T)wp->w_width)
        {
!           vcol -= wp->w_width;
            numberextra = wp->w_width - (numberextra - win_col_off2(wp));
!           if (vcol >= numberextra && numberextra > 0)
!               vcol %= numberextra;
            if (*sbr != NUL)
            {
                sbrlen = (colnr_T)MB_CHARLEN(sbr);
!               if (vcol >= sbrlen)
!                   vcol -= sbrlen;
            }
!           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 (vcol == 0 || vcol + size + sbrlen > (colnr_T)wp->w_width)
        {
            added = 0;
            if (*sbr != NUL)
***************
*** 1074,1081 ****
                {
                    // 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;
  
                    if (width <= 0)
                        width = (colnr_T)1;
--- 1197,1204 ----
                {
                    // calculate effective window width
                    int width = (colnr_T)wp->w_width - sbrlen - numberwidth;
!                   int prev_width = vcol
!                              ? ((colnr_T)wp->w_width - (sbrlen + vcol)) : 0;
  
                    if (width <= 0)
                        width = (colnr_T)1;
***************
*** 1091,1118 ****
                added += get_breakindent_win(wp, line);
  
            size += added;
!           if (col != 0)
                added = 0;
        }
      }
      if (headp != NULL)
        *headp = added + mb_added;
      return size;
  #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.
   */
      static int
  win_nolbr_chartabsize(
!     win_T     *wp,
!     char_u    *s,
!     colnr_T   col,
!     int               *headp)
  {
      int               n;
  
      if (*s == TAB && (!wp->w_p_list || wp->w_lcs_chars.tab1))
--- 1214,1245 ----
                added += get_breakindent_win(wp, line);
  
            size += added;
!           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, '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(
!       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,1192 ****
--- 1314,1320 ----
  #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,1224 ****
            posptr -= (*mb_head_off)(line, posptr);
      }
  
      /*
       * This function is used very often, do some speed optimizations.
       * When 'list', 'linebreak', 'showbreak' and 'breakindent' are not set
!      * 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
         )
      {
        for (;;)
--- 1337,1357 ----
            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
!      * 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,1302 ****
      {
        for (;;)
        {
!           // A tab gets expanded, depending on the current column
            head = 0;
!           incr = win_lbr_chartabsize(wp, line, ptr, vcol, &head);
            // make sure we don't go past the end of the line
!           if (*ptr == NUL)
            {
                incr = 1;       // NUL at end of line only takes one column
                break;
            }
  
!           if (posptr != NULL && ptr >= posptr) // character at pos->col
                break;
  
!           vcol += incr;
!           MB_PTR_ADV(ptr);
        }
      }
      if (start != NULL)
        *start = vcol + head;
      if (end != NULL)
        *end = vcol + incr - 1;
      if (cursor != NULL)
      {
        if (*ptr == TAB
                && (State & MODE_NORMAL)
                && !wp->w_p_list
--- 1407,1445 ----
      {
        for (;;)
        {
!           // A tab gets expanded, depending on the current column.
!           // Other things also take up space.
            head = 0;
!           incr = win_lbr_chartabsize(&cts, &head);
            // make sure we don't go past the end of the line
!           if (*cts.cts_ptr == NUL)
            {
                incr = 1;       // NUL at end of line only takes one column
                break;
            }
  
!           if (posptr != NULL && cts.cts_ptr >= posptr)
!               // character at pos->col
                break;
  
!           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
*** ../vim-9.0.0066/src/drawline.c      2022-07-09 04:56:12.522528981 +0100
--- src/drawline.c      2022-07-25 16:10:32.731049859 +0100
***************
*** 326,331 ****
--- 326,332 ----
      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,830 ****
        v = wp->w_leftcol;
      if (v > 0 && !number_only)
      {
!       char_u  *prev_ptr = ptr;
! 
!       while (vcol < v && *ptr != NUL)
!       {
!           c = win_lbr_chartabsize(wp, line, ptr, (colnr_T)vcol, NULL);
!           vcol += c;
!           prev_ptr = ptr;
!           MB_PTR_ADV(ptr);
!       }
  
        // When:
        // - 'cuc' is set, or
--- 817,837 ----
        v = wp->w_leftcol;
      if (v > 0 && !number_only)
      {
!       char_u          *prev_ptr = ptr;
!       chartabsize_T   cts;
!       int             charsize;
! 
!       init_chartabsize_arg(&cts, wp, lnum, vcol, line, ptr);
!       while (cts.cts_vcol < v && *cts.cts_ptr != NUL)
!       {
!           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,854 ****
        // that character but skip the first few screen characters.
        if (vcol > v)
        {
!           vcol -= c;
            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)
               n_skip = v - vcol;
        }
  
--- 851,861 ----
        // that character but skip the first few screen characters.
        if (vcol > v)
        {
!           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) >= charsize || *ptr == TAB) && col == 0)
               n_skip = v - vcol;
        }
  
***************
*** 1476,1483 ****
--- 1483,1494 ----
                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,1505 ****
                        proptype_T  *pt = text_prop_type_by_id(
                                        wp->w_buffer, text_props[tpi].tp_type);
  
!                       if (pt != NULL && pt->pt_hl_id > 0)
                        {
!                           int pt_attr = syn_id2attr(pt->pt_hl_id);
! 
                            text_prop_type = pt;
                            text_prop_attr =
!                                     hl_combine_attr(text_prop_attr, pt_attr);
                            text_prop_combine = pt->pt_flags & PT_FLAG_COMBINE;
                        }
                    }
                }
            }
--- 1502,1544 ----
                        proptype_T  *pt = text_prop_type_by_id(
                                        wp->w_buffer, text_props[tpi].tp_type);
  
!                       if (pt != NULL && pt->pt_hl_id > 0
!                                         && text_props[tpi].tp_id != -MAXCOL)
                        {
!                           used_attr = syn_id2attr(pt->pt_hl_id);
                            text_prop_type = pt;
                            text_prop_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;
                    }
                }
            }
***************
*** 2025,2034 ****
                    int     mb_off = has_mbyte ? (*mb_head_off)(line, ptr - 1)
                                                                           : 0;
                    char_u  *p = ptr - (mb_off + 1);
  
!                   // TODO: is passing p for start of the line OK?
!                   n_extra = win_lbr_chartabsize(wp, line, p, (colnr_T)vcol,
!                                                                   NULL) - 1;
  
                    // We have just drawn the showbreak value, no need to add
                    // space for it again.
--- 2064,2073 ----
                    int     mb_off = has_mbyte ? (*mb_head_off)(line, ptr - 1)
                                                                           : 0;
                    char_u  *p = ptr - (mb_off + 1);
+                   chartabsize_T cts;
  
!                   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,2074 ****
--- 2108,2114 ----
                        if (!wp->w_p_list)
                            c = ' ';
                    }
+                   clear_chartabsize_arg(&cts);
                }
  #endif
  
*** ../vim-9.0.0066/src/edit.c  2022-06-30 22:13:56.204846337 +0100
--- src/edit.c  2022-07-25 16:17:12.986522771 +0100
***************
*** 4905,4910 ****
--- 4905,4912 ----
        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,4961 ****
        getvcol(curwin, &fpos, &vcol, NULL, NULL);
        getvcol(curwin, cursor, &want_vcol, NULL, NULL);
  
        // 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)
                break;
            if (*ptr != TAB)
            {
--- 4952,4965 ----
        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(&cts);
!           if (cts.cts_vcol + i > want_vcol)
                break;
            if (*ptr != TAB)
            {
***************
*** 4970,4990 ****
            }
            ++fpos.col;
            ++ptr;
!           vcol += i;
        }
  
        if (change_col >= 0)
        {
!           int repl_off = 0;
!           char_u *line = ptr;
  
            // Skip over the spaces we need.
!           while (vcol < want_vcol && *ptr == ' ')
            {
!               vcol += lbr_chartabsize(line, ptr, vcol);
!               ++ptr;
                ++repl_off;
            }
            if (vcol > want_vcol)
            {
                // Must have a char with 'showbreak' just before it.
--- 4974,5000 ----
            }
            ++fpos.col;
            ++ptr;
!           cts.cts_vcol += i;
        }
+       vcol = cts.cts_vcol;
+       clear_chartabsize_arg(&cts);
  
        if (change_col >= 0)
        {
!           int             repl_off = 0;
  
            // Skip over the spaces we need.
!           init_chartabsize_arg(&cts, curwin, 0, vcol, ptr, ptr);
!           while (cts.cts_vcol < want_vcol && *cts.cts_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,5229 ****
      int
  ins_copychar(linenr_T lnum)
  {
!     int           c;
!     int           temp;
!     char_u  *ptr, *prev_ptr;
!     char_u  *line;
  
      if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count)
      {
--- 5230,5239 ----
      int
  ins_copychar(linenr_T lnum)
  {
!     int                   c;
!     char_u        *ptr, *prev_ptr;
!     char_u        *line;
!     chartabsize_T   cts;
  
      if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count)
      {
***************
*** 5233,5248 ****
  
      // 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)
      {
!       prev_ptr = ptr;
!       temp += lbr_chartabsize_adv(line, &ptr, (colnr_T)temp);
      }
!     if ((colnr_T)temp > curwin->w_virtcol)
        ptr = prev_ptr;
  
      c = (*mb_ptr2char)(ptr);
      if (c == NUL)
--- 5243,5261 ----
  
      // try to advance to the cursor column
      validate_virtcol();
!     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 = cts.cts_ptr;
!       cts.cts_vcol += lbr_chartabsize_adv(&cts);
      }
!     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)
*** ../vim-9.0.0066/src/errors.h        2022-07-24 20:07:57.656416981 +0100
--- src/errors.h        2022-07-25 15:42:47.107100992 +0100
***************
*** 3310,3312 ****
--- 3310,3316 ----
  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
*** ../vim-9.0.0066/src/evalfunc.c      2022-07-23 09:52:00.333814262 +0100
--- src/evalfunc.c      2022-07-25 15:42:47.107100992 +0100
***************
*** 2218,2224 ****
      {"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)},
      {"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,
--- 2218,2224 ----
      {"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_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,
*** ../vim-9.0.0066/src/getchar.c       2022-07-23 06:24:56.405106035 +0100
--- src/getchar.c       2022-07-25 16:51:55.042379009 +0100
***************
*** 3210,3216 ****
                        && (c = inchar(typebuf.tb_buf + typebuf.tb_off
                                               + typebuf.tb_len, 3, 25L)) == 0)
                {
!                   colnr_T     col = 0, vcol;
                    char_u      *ptr;
  
                    if (mode_displayed)
--- 3210,3216 ----
                        && (c = inchar(typebuf.tb_buf + typebuf.tb_off
                                               + typebuf.tb_len, 3, 25L)) == 0)
                {
!                   colnr_T     col = 0;
                    char_u      *ptr;
  
                    if (mode_displayed)
***************
*** 3242,3265 ****
                        {
                            if (did_ai)
                            {
                                /*
                                 * We are expecting to truncate the trailing
                                 * white-space, so find the last non-white
                                 * character -- webb
                                 */
!                               col = vcol = curwin->w_wcol = 0;
                                ptr = ml_get_curline();
!                               while (col < curwin->w_cursor.col)
                                {
!                                   if (!VIM_ISWHITE(ptr[col]))
!                                       curwin->w_wcol = vcol;
!                                   vcol += lbr_chartabsize(ptr, ptr + col,
!                                                              vcol);
                                    if (has_mbyte)
!                                       col += (*mb_ptr2len)(ptr + col);
                                    else
!                                       ++col;
                                }
                                curwin->w_wrow = curwin->w_cline_row
                                           + curwin->w_wcol / curwin->w_width;
                                curwin->w_wcol %= curwin->w_width;
--- 3242,3271 ----
                        {
                            if (did_ai)
                            {
+                               chartabsize_T cts;
+ 
                                /*
                                 * We are expecting to truncate the trailing
                                 * white-space, so find the last non-white
                                 * character -- webb
                                 */
!                               curwin->w_wcol = 0;
                                ptr = ml_get_curline();
!                               init_chartabsize_arg(&cts, curwin,
!                                         curwin->w_cursor.lnum, 0, ptr, ptr);
!                               while (cts.cts_ptr < ptr + curwin->w_cursor.col)
                                {
!                                   if (!VIM_ISWHITE(*cts.cts_ptr))
!                                       curwin->w_wcol = cts.cts_vcol;
!                                   cts.cts_vcol += lbr_chartabsize(&cts);
                                    if (has_mbyte)
!                                       cts.cts_ptr +=
!                                                  (*mb_ptr2len)(cts.cts_ptr);
                                    else
!                                       ++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;
*** ../vim-9.0.0066/src/indent.c        2022-07-01 13:15:31.556075437 +0100
--- src/indent.c        2022-07-25 17:49:44.798632658 +0100
***************
*** 1350,1375 ****
        new_cursor_col = curwin->w_cursor.col;
      else
      {
        // 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;
        ptr = ml_get_curline();
!       while (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)
                break;
!           vcol += lbr_chartabsize(ptr, ptr + new_cursor_col, (colnr_T)vcol);
        }
        vcol = last_vcol;
  
        // May need to insert spaces to be able to position the cursor on
        // the right screen column.
--- 1350,1377 ----
        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.
!       last_vcol = 0;
        ptr = ml_get_curline();
!       init_chartabsize_arg(&cts, curwin, 0, 0, ptr, ptr);
!       while (cts.cts_vcol <= (int)curwin->w_virtcol)
        {
!           last_vcol = cts.cts_vcol;
!           if (cts.cts_vcol > 0)
!               MB_PTR_ADV(cts.cts_ptr);
!           if (*cts.cts_ptr == NUL)
                break;
!           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,2077 ****
                amount = 2;
            else
            {
!               char_u *line = that;
  
!               amount = 0;
!               while (*that && col)
                {
!                   amount += lbr_chartabsize_adv(line, &that, (colnr_T)amount);
                    col--;
                }
  
                // Some keywords require "body" indenting rules (the
                // non-standard-lisp ones are Scheme special forms):
--- 2066,2083 ----
                amount = 2;
            else
            {
!               char_u          *line = that;
!               chartabsize_T   cts;
  
!               init_chartabsize_arg(&cts, curwin, pos->lnum, 0, line, line);
!               while (*cts.cts_ptr != NUL && col > 0)
                {
!                   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,2101 ****
                    }
                    firsttry = amount;
  
!                   while (VIM_ISWHITE(*that))
                    {
!                       amount += lbr_chartabsize(line, that, (colnr_T)amount);
!                       ++that;
                    }
  
                    if (*that && *that != ';') // not a comment line
                    {
--- 2097,2112 ----
                    }
                    firsttry = amount;
  
!                   init_chartabsize_arg(&cts, curwin, (colnr_T)(that - line),
!                                                          amount, line, that);
!                   while (VIM_ISWHITE(*cts.cts_ptr))
                    {
!                       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,2148 ****
                        parencount = 0;
                        quotecount = 0;
  
                        if (vi_lisp
                                || (*that != '"'
                                    && *that != '\''
                                    && *that != '#'
                                    && (*that < '0' || *that > '9')))
                        {
!                           while (*that
!                                   && (!VIM_ISWHITE(*that)
                                        || quotecount
                                        || parencount)
!                                   && (!((*that == '(' || *that == '[')
                                            && !quotecount
                                            && !parencount
                                            && vi_lisp)))
                            {
!                               if (*that == '"')
                                    quotecount = !quotecount;
!                               if ((*that == '(' || *that == '[')
                                                               && !quotecount)
                                    ++parencount;
!                               if ((*that == ')' || *that == ']')
                                                               && !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);
                            }
                        }
!                       while (VIM_ISWHITE(*that))
                        {
!                           amount += lbr_chartabsize(
!                                                line, that, (colnr_T)amount);
!                           that++;
                        }
                        if (!*that || *that == ';')
                            amount = firsttry;
                    }
--- 2118,2164 ----
                        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 (*cts.cts_ptr
!                                   && (!VIM_ISWHITE(*cts.cts_ptr)
                                        || quotecount
                                        || parencount)
!                                   && (!((*cts.cts_ptr == '('
!                                                       || *cts.cts_ptr == '[')
                                            && !quotecount
                                            && !parencount
                                            && vi_lisp)))
                            {
!                               if (*cts.cts_ptr == '"')
                                    quotecount = !quotecount;
!                               if ((*cts.cts_ptr == '(' || *cts.cts_ptr == '[')
                                                               && !quotecount)
                                    ++parencount;
!                               if ((*cts.cts_ptr == ')' || *cts.cts_ptr == ']')
                                                               && !quotecount)
                                    --parencount;
!                               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(*cts.cts_ptr))
                        {
!                           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;
                    }
*** ../vim-9.0.0066/src/misc1.c 2022-07-02 17:36:27.332515941 +0100
--- src/misc1.c 2022-07-25 16:31:28.675129729 +0100
***************
*** 397,403 ****
      s = ml_get_buf(wp->w_buffer, lnum, FALSE);
      if (*s == NUL)            // empty line
        return 1;
!     col = win_linetabsize(wp, s, (colnr_T)MAXCOL);
  
      /*
       * If list mode is on, then the '$' at the end of the line may take up one
--- 397,403 ----
      s = ml_get_buf(wp->w_buffer, lnum, FALSE);
      if (*s == NUL)            // empty line
        return 1;
!     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,436 ****
  plines_win_col(win_T *wp, linenr_T lnum, long column)
  {
      long      col;
-     char_u    *s;
      int               lines = 0;
      int               width;
      char_u    *line;
  
  #ifdef FEAT_DIFF
      // Check for filler lines above this buffer line.  When folded the result
--- 427,436 ----
  plines_win_col(win_T *wp, linenr_T lnum, long column)
  {
      long      col;
      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,468 ****
      if (wp->w_width == 0)
        return lines + 1;
  
!     line = s = ml_get_buf(wp->w_buffer, lnum, FALSE);
  
!     col = 0;
!     while (*s != NUL && --column >= 0)
      {
!       col += win_lbr_chartabsize(wp, line, s, (colnr_T)col, NULL);
!       MB_PTR_ADV(s);
      }
  
      /*
!      * 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 (*s == 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;
  
      /*
       * Add column offset for 'number', 'relativenumber', 'foldcolumn', etc.
--- 444,470 ----
      if (wp->w_width == 0)
        return lines + 1;
  
!     line = ml_get_buf(wp->w_buffer, lnum, FALSE);
  
!     init_chartabsize_arg(&cts, wp, lnum, 0, line, line);
!     while (*cts.cts_ptr != NUL && --column >= 0)
      {
!       cts.cts_vcol += win_lbr_chartabsize(&cts, NULL);
!       MB_PTR_ADV(cts.cts_ptr);
      }
  
      /*
!      * 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.
       */
!     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(&cts, NULL) - 1;
!     clear_chartabsize_arg(&cts);
  
      /*
       * Add column offset for 'number', 'relativenumber', 'foldcolumn', etc.
*** ../vim-9.0.0066/src/misc2.c 2022-05-16 19:32:27.000000000 +0100
--- src/misc2.c 2022-07-25 16:52:25.602379723 +0100
***************
*** 128,134 ****
  {
      colnr_T   wcol = wcol_arg;
      int               idx;
-     char_u    *ptr;
      char_u    *line;
      colnr_T   col = 0;
      int               csize = 0;
--- 128,133 ----
***************
*** 158,163 ****
--- 157,163 ----
      else
      {
        int width = curwin->w_width - win_col_off(curwin);
+       chartabsize_T cts;
  
        if (finetune
                && curwin->w_p_wrap
***************
*** 180,198 ****
            }
        }
  
!       ptr = line;
!       while (col <= wcol && *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);
  #else
!           csize = lbr_chartabsize_adv(line, &ptr, col);
  #endif
!           col += csize;
        }
!       idx = (int)(ptr - line);
        /*
         * Handle all the special cases.  The virtual_active() check
         * is needed to ensure that a virtual position off the end of
--- 180,201 ----
            }
        }
  
!       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(&cts, &head);
!           MB_PTR_ADV(cts.cts_ptr);
  #else
!           csize = lbr_chartabsize_adv(&cts);
  #endif
!           cts.cts_vcol += csize;
        }
!       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
*** ../vim-9.0.0066/src/mouse.c 2022-07-09 04:56:12.522528981 +0100
--- src/mouse.c 2022-07-25 15:42:47.107100992 +0100
***************
*** 3101,3118 ****
      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;
  
!     line = ptr = ml_get_buf(wp->w_buffer, lnum, FALSE);
!     while (count < vcol && *ptr != NUL)
      {
!       count += win_lbr_chartabsize(wp, line, ptr, count, NULL);
!       MB_PTR_ADV(ptr);
      }
!     return (int)(ptr - line);
  }
  #endif
  
--- 3101,3120 ----
      int
  vcol2col(win_T *wp, linenr_T lnum, int vcol)
  {
!     char_u        *line;
!     chartabsize_T   cts;
  
!     // 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)
      {
!       cts.cts_vcol += win_lbr_chartabsize(&cts, NULL);
!       MB_PTR_ADV(cts.cts_ptr);
      }
!     clear_chartabsize_arg(&cts);
! 
!     return (int)(cts.cts_ptr - line);
  }
  #endif
  
*** ../vim-9.0.0066/src/ops.c   2022-06-30 22:13:56.204846337 +0100
--- src/ops.c   2022-07-25 17:25:27.982274054 +0100
***************
*** 307,313 ****
  
      if (!left)
      {
!       int     tabs = 0, spaces = 0;
  
        /*
         *  1. Get start vcol
--- 307,314 ----
  
      if (!left)
      {
!       int             tabs = 0, spaces = 0;
!       chartabsize_T   cts;
  
        /*
         *  1. Get start vcol
***************
*** 332,344 ****
            else
                ++bd.textstart;
        }
!       for ( ; VIM_ISWHITE(*bd.textstart); )
        {
!           // TODO: is passing bd.textstart for start of the line OK?
!           incr = lbr_chartabsize_adv(bd.textstart, &bd.textstart, 
bd.start_vcol);
            total += incr;
!           bd.start_vcol += incr;
        }
        // OK, now total=all the VWS reqd, and textstart points at the 1st
        // non-ws char in the block.
  #ifdef FEAT_VARTABS
--- 333,352 ----
            else
                ++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); )
        {
!           incr = lbr_chartabsize_adv(&cts);
            total += 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,386 ****
--- 389,395 ----
        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,409 ****
        // The character's column is in "bd.start_vcol".
        non_white_col = bd.start_vcol;
  
!       while (VIM_ISWHITE(*non_white))
!       {
!           incr = lbr_chartabsize_adv(bd.textstart, &non_white, non_white_col);
!           non_white_col += incr;
!       }
  
        block_space_width = non_white_col - oap->start_vcol;
        // We will shift by "total" or "block_space_width", whichever is less.
--- 408,423 ----
        // The character's column is in "bd.start_vcol".
        non_white_col = bd.start_vcol;
  
!       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(&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,440 ****
        // column number.
        if (bd.startspaces)
            verbatim_copy_width -= bd.start_char_vcols;
!       while (verbatim_copy_width < 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)
                break;
!           verbatim_copy_width += incr;
!           MB_PTR_ADV(verbatim_copy_end);
        }
  
        // 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
--- 437,455 ----
        // column number.
        if (bd.startspaces)
            verbatim_copy_width -= bd.start_char_vcols;
!       init_chartabsize_arg(&cts, curwin, 0, verbatim_copy_width,
!                                            bd.textstart, verbatim_copy_end);
!       while (cts.cts_vcol < destination_col)
        {
!           incr = lbr_chartabsize(&cts);
!           if (cts.cts_vcol + incr > destination_col)
                break;
!           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,710 ****
         * 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)
--- 718,723 ----
***************
*** 2213,2218 ****
--- 2226,2232 ----
      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,2245 ****
      bdp->start_char_vcols = 0;
  
      line = ml_get(lnum);
-     pstart = line;
      prev_pstart = line;
!     while (bdp->start_vcol < oap->start_vcol && *pstart)
      {
        // 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))
        {
            bdp->pre_whitesp += incr;
            bdp->pre_whitesp_c++;
--- 2246,2259 ----
      bdp->start_char_vcols = 0;
  
      line = ml_get(lnum);
      prev_pstart = line;
!     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(&cts);
!       cts.cts_vcol += incr;
!       if (VIM_ISWHITE(*cts.cts_ptr))
        {
            bdp->pre_whitesp += incr;
            bdp->pre_whitesp_c++;
***************
*** 2249,2257 ****
            bdp->pre_whitesp = 0;
            bdp->pre_whitesp_c = 0;
        }
!       prev_pstart = pstart;
!       MB_PTR_ADV(pstart);
      }
      bdp->start_char_vcols = incr;
      if (bdp->start_vcol < oap->start_vcol)    // line too short
      {
--- 2263,2275 ----
            bdp->pre_whitesp = 0;
            bdp->pre_whitesp_c = 0;
        }
!       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,2308 ****
        }
        else
        {
            prev_pend = pend;
!           while (bdp->end_vcol <= oap->end_vcol && *pend != 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;
!           }
            if (bdp->end_vcol <= oap->end_vcol
                    && (!is_del
                        || oap->op_type == OP_APPEND
--- 2313,2332 ----
        }
        else
        {
+           init_chartabsize_arg(&cts, curwin, lnum, bdp->end_vcol,
+                                                                 line, pend);
            prev_pend = pend;
!           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 = 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
*** ../vim-9.0.0066/src/popupwin.c      2022-07-23 09:52:00.337814264 +0100
--- src/popupwin.c      2022-07-25 15:42:47.111100981 +0100
***************
*** 1371,1377 ****
        // "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),
                                                              (colnr_T)MAXCOL);
        wp->w_width = w_width;
  
--- 1371,1377 ----
        // "margin_width" is added to "len" where it matters.
        if (wp->w_width < maxwidth)
            wp->w_width = maxwidth;
!       len = win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum, FALSE),
                                                              (colnr_T)MAXCOL);
        wp->w_width = w_width;
  
*** ../vim-9.0.0066/src/proto/charset.pro       2022-06-27 23:14:58.000000000 
+0100
--- src/proto/charset.pro       2022-07-25 16:47:56.031024543 +0100
***************
*** 17,23 ****
  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 vim_isIDc(int c);
  int vim_isNormalIDc(int c);
  int vim_iswordc(int c);
--- 17,23 ----
  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, 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,36 ****
  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 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);
--- 28,38 ----
  int vim_isfilec_or_wc(int c);
  int vim_isprintc(int c);
  int vim_isprintc_strict(int c);
! 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);
*** ../vim-9.0.0066/src/proto/textprop.pro      2022-06-27 23:15:26.000000000 
+0100
--- src/proto/textprop.pro      2022-07-25 16:48:05.562990867 +0100
***************
*** 2,8 ****
  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 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);
--- 2,8 ----
  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);
! 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);
*** ../vim-9.0.0066/src/regexp.c        2022-07-07 22:20:28.441941352 +0100
--- src/regexp.c        2022-07-25 15:42:47.111100981 +0100
***************
*** 1303,1309 ****
        rex.line = reg_getline(rex.lnum);
        rex.input = rex.line + col;
  
!       cols = win_linetabsize(wp, rex.line, col);
        if (cols < start || cols > end - (*p_sel == 'e'))
            return FALSE;
      }
--- 1303,1309 ----
        rex.line = reg_getline(rex.lnum);
        rex.input = 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;
      }
*** ../vim-9.0.0066/src/regexp_bt.c     2022-06-20 11:20:30.000000000 +0100
--- src/regexp_bt.c     2022-07-25 15:48:54.537761933 +0100
***************
*** 3441,3447 ****
          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))
                status = RA_NOMATCH;
            break;
  
--- 3441,3449 ----
          case RE_VCOL:
            if (!re_num_cmp((long_u)win_linetabsize(
                            rex.reg_win == NULL ? curwin : rex.reg_win,
!                           rex.reg_firstlnum + rex.lnum,
!                           rex.line,
!                           (colnr_T)(rex.input - rex.line)) + 1, scan))
                status = RA_NOMATCH;
            break;
  
*** ../vim-9.0.0066/src/regexp_nfa.c    2022-06-20 11:20:50.000000000 +0100
--- src/regexp_nfa.c    2022-07-25 15:49:04.693808384 +0100
***************
*** 6775,6781 ****
                    }
                    if (!result)
                        result = nfa_re_num_cmp(t->state->val, op,
!                               (long_u)win_linetabsize(wp, rex.line, col) + 1);
                    if (result)
                    {
                        add_here = TRUE;
--- 6775,6783 ----
                    }
                    if (!result)
                        result = nfa_re_num_cmp(t->state->val, op,
!                               (long_u)win_linetabsize(wp,
!                                               rex.reg_firstlnum + rex.lnum,
!                                               rex.line, col) + 1);
                    if (result)
                    {
                        add_here = TRUE;
*** ../vim-9.0.0066/src/register.c      2022-06-30 12:30:13.823485781 +0100
--- src/register.c      2022-07-25 17:06:18.621161796 +0100
***************
*** 1820,1827 ****
        bd.textcol = 0;
        for (i = 0; i < y_size; ++i)
        {
!           int spaces = 0;
!           char shortline;
  
            bd.startspaces = 0;
            bd.endspaces = 0;
--- 1820,1828 ----
        bd.textcol = 0;
        for (i = 0; i < y_size; ++i)
        {
!           int             spaces = 0;
!           char            shortline;
!           chartabsize_T   cts;
  
            bd.startspaces = 0;
            bd.endspaces = 0;
***************
*** 1839,1851 ****
            // 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; )
            {
                // Count a tab for what it's worth (if list mode not on)
!               incr = lbr_chartabsize_adv(oldp, &ptr, vcol);
!               vcol += incr;
            }
            bd.textcol = (colnr_T)(ptr - oldp);
  
            shortline = (vcol < col) || (vcol == col && !*ptr) ;
  
--- 1840,1858 ----
            // get the old line and advance to the position to insert at
            oldp = ml_get_curline();
            oldlen = (int)STRLEN(oldp);
!           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(&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,1883 ****
                // calculate number of spaces required to fill right side of
                // block
                spaces = y_width + 1;
                for (j = 0; j < yanklen; j++)
!                   spaces -= lbr_chartabsize(NULL, &y_array[i][j], 0);
                if (spaces < 0)
                    spaces = 0;
            }
--- 1883,1897 ----
                // 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(&cts);
!                   ++cts.cts_ptr;
!                   cts.cts_vcol = 0;
!               }
!               clear_chartabsize_arg(&cts);
                if (spaces < 0)
                    spaces = 0;
            }
*** ../vim-9.0.0066/src/structs.h       2022-07-04 17:34:06.382292138 +0100
--- src/structs.h       2022-07-25 15:42:47.111100981 +0100
***************
*** 806,813 ****
      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
  
  /*
   * Structure defining a property type.
--- 806,814 ----
      int               tp_flags;       // TP_FLAG_ values
  } textprop_T;
  
! #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,3079 ****
--- 3075,3081 ----
  #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,4562 ****
--- 4562,4579 ----
      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;
*** ../vim-9.0.0066/src/textprop.c      2022-07-23 09:52:00.341814264 +0100
--- src/textprop.c      2022-07-25 16:53:22.590367803 +0100
***************
*** 150,156 ****
   * prop_add({lnum}, {col}, {props})
   */
      void
! f_prop_add(typval_T *argvars, typval_T *rettv UNUSED)
  {
      linenr_T  start_lnum;
      colnr_T   start_col;
--- 150,156 ----
   * prop_add({lnum}, {col}, {props})
   */
      void
! f_prop_add(typval_T *argvars, typval_T *rettv)
  {
      linenr_T  start_lnum;
      colnr_T   start_col;
***************
*** 174,193 ****
        return;
      }
  
!     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'.
   */
      static int
  prop_add_one(
        buf_T           *buf,
        char_u          *type_name,
        int             id,
        linenr_T        start_lnum,
        linenr_T        end_lnum,
        colnr_T         start_col,
--- 174,195 ----
        return;
      }
  
!     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".
!  * 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,227 ****
      char_u    *newtext;
      int               i;
      textprop_T        tmp_prop;
  
      type = lookup_prop_type(type_name, buf);
      if (type == NULL)
!       return FAIL;
  
      if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count)
      {
        semsg(_(e_invalid_line_number_nr), (long)start_lnum);
!       return FAIL;
      }
      if (end_lnum < start_lnum || end_lnum > buf->b_ml.ml_line_count)
      {
        semsg(_(e_invalid_line_number_nr), (long)end_lnum);
!       return FAIL;
      }
  
      if (buf->b_ml.ml_mfp == NULL)
      {
        emsg(_(e_cannot_add_text_property_to_unloaded_buffer));
!       return FAIL;
      }
  
      for (lnum = start_lnum; lnum <= end_lnum; ++lnum)
--- 204,246 ----
      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)
!       goto theend;
  
      if (start_lnum < 1 || start_lnum > buf->b_ml.ml_line_count)
      {
        semsg(_(e_invalid_line_number_nr), (long)start_lnum);
!       goto theend;
      }
      if (end_lnum < start_lnum || end_lnum > buf->b_ml.ml_line_count)
      {
        semsg(_(e_invalid_line_number_nr), (long)end_lnum);
!       goto theend;
      }
  
      if (buf->b_ml.ml_mfp == NULL)
      {
        emsg(_(e_cannot_add_text_property_to_unloaded_buffer));
!       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,246 ****
        if (col - 1 > (colnr_T)textlen)
        {
            semsg(_(e_invalid_column_number_nr), (long)start_col);
!           return FAIL;
        }
  
        if (lnum == end_lnum)
--- 259,265 ----
        if (col - 1 > (colnr_T)textlen)
        {
            semsg(_(e_invalid_column_number_nr), (long)start_col);
!           goto theend;
        }
  
        if (lnum == end_lnum)
***************
*** 255,261 ****
        // 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;
        // Copy the text, including terminating NUL.
        mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen);
  
--- 274,280 ----
        // Allocate the new line with space for the new property.
        newtext = alloc(buf->b_ml.ml_line_len + sizeof(textprop_T));
        if (newtext == NULL)
!           goto theend;
        // Copy the text, including terminating NUL.
        mch_memmove(newtext, buf->b_ml.ml_line_ptr, textlen);
  
***************
*** 295,301 ****
      }
  
      changed_lines_buf(buf, start_lnum, end_lnum + 1, 0);
!     return OK;
  }
  
  /*
--- 314,324 ----
      }
  
      changed_lines_buf(buf, start_lnum, end_lnum + 1, 0);
!     res = OK;
! 
! theend:
!     vim_free(text);
!     return res;
  }
  
  /*
***************
*** 367,373 ****
            emsg(_(e_invalid_argument));
            return;
        }
!       if (prop_add_one(buf, type_name, id, start_lnum, end_lnum,
                                                start_col, end_col) == FAIL)
            return;
      }
--- 390,396 ----
            emsg(_(e_invalid_argument));
            return;
        }
!       if (prop_add_one(buf, type_name, id, NULL, start_lnum, end_lnum,
                                                start_col, end_col) == FAIL)
            return;
      }
***************
*** 376,386 ****
  }
  
  /*
   * Shared between prop_add() and popup_create().
   * "dict_arg" is the function argument of a dict containing "bufnr".
   * it is NULL for popup_create().
   */
!     void
  prop_add_common(
        linenr_T    start_lnum,
        colnr_T     start_col,
--- 399,420 ----
  }
  
  /*
+  * 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.
   */
!     int
  prop_add_common(
        linenr_T    start_lnum,
        colnr_T     start_col,
***************
*** 393,403 ****
      char_u    *type_name;
      buf_T     *buf = default_buf;
      int               id = 0;
  
      if (dict == NULL || !dict_has_key(dict, "type"))
      {
        emsg(_(e_missing_property_type_name));
!       return;
      }
      type_name = dict_get_string(dict, "type", FALSE);
  
--- 427,438 ----
      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));
!       goto theend;
      }
      type_name = dict_get_string(dict, "type", FALSE);
  
***************
*** 407,413 ****
        if (end_lnum < start_lnum)
        {
            semsg(_(e_invalid_value_for_argument_str), "end_lnum");
!           return;
        }
      }
      else
--- 442,448 ----
        if (end_lnum < start_lnum)
        {
            semsg(_(e_invalid_value_for_argument_str), "end_lnum");
!           goto theend;
        }
      }
      else
***************
*** 420,426 ****
        if (length < 0 || end_lnum > start_lnum)
        {
            semsg(_(e_invalid_value_for_argument_str), "length");
!           return;
        }
        end_col = start_col + length;
      }
--- 455,461 ----
        if (length < 0 || end_lnum > start_lnum)
        {
            semsg(_(e_invalid_value_for_argument_str), "length");
!           goto theend;
        }
        end_col = start_col + length;
      }
***************
*** 430,436 ****
        if (end_col <= 0)
        {
            semsg(_(e_invalid_value_for_argument_str), "end_col");
!           return;
        }
      }
      else if (start_lnum == end_lnum)
--- 465,471 ----
        if (end_col <= 0)
        {
            semsg(_(e_invalid_value_for_argument_str), "end_col");
!           goto theend;
        }
      }
      else if (start_lnum == end_lnum)
***************
*** 441,457 ****
      if (dict_has_key(dict, "id"))
        id = dict_get_number(dict, "id");
  
      if (dict_arg != NULL && get_bufnr_from_arg(dict_arg, &buf) == FAIL)
!       return;
  
      // 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);
  
      redraw_buf_later(buf, VALID);
  }
  
  /*
--- 476,515 ----
      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)
!       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, text,
!                                   start_lnum, end_lnum, start_col, end_col);
!     text = NULL;
  
      redraw_buf_later(buf, VALID);
+ 
+ theend:
+     vim_free(text);
+     return id;
  }
  
  /*
***************
*** 954,962 ****
        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)))
        {
            dict_T *d = dict_alloc();
  
--- 1012,1020 ----
        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)))
        {
            dict_T *d = dict_alloc();
  
*** ../vim-9.0.0066/src/testdir/test_textprop.vim       2022-05-24 
21:22:09.000000000 +0100
--- src/testdir/test_textprop.vim       2022-07-25 18:09:17.168061181 +0100
***************
*** 2187,2190 ****
--- 2187,2213 ----
    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
*** ../vim-9.0.0066/src/testdir/dumps/Test_prop_inserts_text.dump       
2022-07-25 18:11:47.815151136 +0100
--- src/testdir/dumps/Test_prop_inserts_text.dump       2022-07-25 
18:06:47.773105469 +0100
***************
*** 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+0&#5fd7ff255|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| 
*** ../vim-9.0.0066/src/version.c       2022-07-25 12:28:05.844483996 +0100
--- src/version.c       2022-07-25 18:05:59.213482921 +0100
***************
*** 737,738 ****
--- 737,740 ----
  {   /* Add new patch number below this line */
+ /**/
+     67,
  /**/

-- 
"A clear conscience is usually the sign of a bad memory."
                             -- Steven Wright

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/20220725171421.454C21C065D%40moolenaar.net.

Raspunde prin e-mail lui