patch 9.1.0120: hard to get visual region using Vim script

Commit: 
https://github.com/vim/vim/commit/3f905ab3c4f66562f4a224bf00f49d98a0b0da91
Author: Shougo Matsushita <[email protected]>
Date:   Wed Feb 21 00:02:45 2024 +0100

    patch 9.1.0120: hard to get visual region using Vim script
    
    Problem:  hard to get visual region using Vim script
    Solution: Add getregion() Vim script function
              (Shougo Matsushita, Jakub Łuczyński)
    
    closes: #13998
    closes: #11579
    
    Co-authored-by: =?UTF-8?q?Jakub=20=C5=81uczy=C5=84ski?= <[email protected]>
    Co-authored-by: Shougo Matsushita <[email protected]>
    Signed-off-by: Shougo Matsushita <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 0cddeffa7..93b300018 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -263,6 +263,8 @@ getqflist({what})           Dict    get specific quickfix 
list properties
 getreg([{regname} [, 1 [, {list}]]])
                                String or List   contents of a register
 getreginfo([{regname}])                Dict    information about a register
+getregion({pos1}, {pos2}, {type})
+                               List    get the text from {pos1} to {pos2}
 getregtype([{regname}])                String  type of a register
 getscriptinfo([{opts}])                List    list of sourced scripts
 gettabinfo([{expr}])           List    list of tab pages
@@ -4266,6 +4268,43 @@ getreginfo([{regname}])                                  
*getreginfo()*
                Can also be used as a |method|: >
                        GetRegname()->getreginfo()
 
+getregion({pos1}, {pos2}, {type})                      *getregion()*
+               Returns the list of strings from {pos1} to {pos2} as if it's
+               selected in visual mode of {type}.
+               For possible values of {pos1} and {pos2} see |line()|.
+               {type} is the selection type:
+                       "v" for |characterwise| mode
+                       "V" for |linewise| mode
+                       "<CTRL-V>" for |blockwise-visual| mode
+               You can get the last selection type by |visualmode()|.
+               If Visual mode is active, use |mode()| to get the Visual mode
+               (e.g., in a |:vmap|).
+               This function uses the line and column number from the
+               specified position.
+               It is useful to get text starting and ending in different
+               columns, such as |characterwise-visual| selection.
+
+               Note that:
+               - Order of {pos1} and {pos2} doesn't matter, it will always
+                 return content from the upper left position to the lower
+                 right position.
+               - If 'virtualedit' is enabled and selection is past the end of
+                 line, resulting lines are filled with blanks.
+               - If the selection starts or ends in the middle of a multibyte
+                 character, it is not included but its selected part is
+                 substituted with spaces.
+               - If {pos1} or {pos2} equals "v" (see |line()|) and it is not in
+                 |visual-mode|, an empty list is returned.
+               - If {pos1}, {pos2} or {type} is an invalid string, an empty
+                 list is returned.
+
+               Examples: >
+                       :xnoremap <CR>
+                       \ <Cmd>echo getregion('v', '.', mode())<CR>
+<
+               Can also be used as a |method|: >
+                       '.'->getregion("'a', 'v')
+<
 getregtype([{regname}])                                        *getregtype()*
                The result is a String, which is type of register {regname}.
                The value will be one of:
diff --git a/runtime/doc/tags b/runtime/doc/tags
index 330ac8bc3..b7e26ee49 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -7760,6 +7760,7 @@ getqflist()       builtin.txt     /*getqflist()*
 getqflist-examples     quickfix.txt    /*getqflist-examples*
 getreg()       builtin.txt     /*getreg()*
 getreginfo()   builtin.txt     /*getreginfo()*
+getregion()    builtin.txt     /*getregion()*
 getregtype()   builtin.txt     /*getregtype()*
 getscript      pi_getscript.txt        /*getscript*
 getscript-autoinstall  pi_getscript.txt        /*getscript-autoinstall*
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 0ca19beb0..0d832a34c 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -1,4 +1,4 @@
-*usr_41.txt*   For Vim version 9.1.  Last change: 2024 Feb 01
+*usr_41.txt*   For Vim version 9.1.  Last change: 2024 Feb 20
 
                     VIM USER MANUAL - by Bram Moolenaar
 
@@ -929,6 +929,7 @@ Cursor and mark position:           *cursor-functions* 
*mark-functions*
 
 Working with text in the current buffer:               *text-functions*
        getline()               get a line or list of lines from the buffer
+       getregion()             get a region of text from the buffer
        setline()               replace a line in the buffer
        append()                append line or list of lines in the buffer
        indent()                indent of a specific line
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index e8973ce73..29dbfb8c5 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -41560,6 +41560,7 @@ Functions: ~
 |foreach()|            apply function to List items
 |matchbufline()|       all the matches of a pattern in a buffer
 |matchstrlist()|       all the matches of a pattern in a List of strings
+|getregion()|          get a region of text from a buffer
 
 
 Autocommands: ~
diff --git a/src/evalfunc.c b/src/evalfunc.c
index d13eb1c9f..81f747f84 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -63,15 +63,16 @@ static void f_get(typval_T *argvars, typval_T *rettv);
 static void f_getchangelist(typval_T *argvars, typval_T *rettv);
 static void f_getcharpos(typval_T *argvars, typval_T *rettv);
 static void f_getcharsearch(typval_T *argvars, typval_T *rettv);
+static void f_getcurpos(typval_T *argvars, typval_T *rettv);
+static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv);
 static void f_getenv(typval_T *argvars, typval_T *rettv);
 static void f_getfontname(typval_T *argvars, typval_T *rettv);
 static void f_getjumplist(typval_T *argvars, typval_T *rettv);
 static void f_getpid(typval_T *argvars, typval_T *rettv);
-static void f_getcurpos(typval_T *argvars, typval_T *rettv);
-static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv);
 static void f_getpos(typval_T *argvars, typval_T *rettv);
 static void f_getreg(typval_T *argvars, typval_T *rettv);
 static void f_getreginfo(typval_T *argvars, typval_T *rettv);
+static void f_getregion(typval_T *argvars, typval_T *rettv);
 static void f_getregtype(typval_T *argvars, typval_T *rettv);
 static void f_gettagstack(typval_T *argvars, typval_T *rettv);
 static void f_gettext(typval_T *argvars, typval_T *rettv);
@@ -2131,6 +2132,8 @@ static funcentry_T global_functions[] =
                        ret_getreg,         f_getreg},
     {"getreginfo",     0, 1, FEARG_1,      arg1_string,
                        ret_dict_any,       f_getreginfo},
+    {"getregion",      3, 3, FEARG_1,      arg3_string,
+                       ret_list_string,    f_getregion},
     {"getregtype",     0, 1, FEARG_1,      arg1_string,
                        ret_string,         f_getregtype},
     {"getscriptinfo",  0, 1, 0,            arg1_dict_any,
@@ -5452,6 +5455,186 @@ f_getpos(typval_T *argvars, typval_T *rettv)
     getpos_both(argvars, rettv, FALSE, FALSE);
 }
 
+/*
+ * Convert from block_def to string
+ */
+   static char_u *
+block_def2str(struct block_def *bd)
+{
+    char_u *p, *ret;
+    size_t size = bd->startspaces + bd->endspaces + bd->textlen;
+
+    ret = alloc(size + 1);
+    if (ret != NULL)
+    {
+       p = ret;
+       vim_memset(p, ' ', bd->startspaces);
+       p += bd->startspaces;
+       mch_memmove(p, bd->textstart, bd->textlen);
+       p += bd->textlen;
+       vim_memset(p, ' ', bd->endspaces);
+       *(p + bd->endspaces) = NUL;
+    }
+    return ret;
+}
+
+/*
+ * "getregion()" function
+ */
+    static void
+f_getregion(typval_T *argvars, typval_T *rettv)
+{
+    linenr_T           lnum;
+    oparg_T            oap;
+    struct block_def   bd;
+    char_u             *akt = NULL;
+    int                        inclusive = TRUE;
+    int                        fnum = -1;
+    pos_T              p1, p2;
+    pos_T              *fp = NULL;
+    char_u             *pos1, *pos2, *type;
+    int                        save_virtual = -1;
+    int                        l;
+    int                        region_type = -1;
+    int                        is_visual;
+
+    if (rettv_list_alloc(rettv) == FAIL)
+       return;
+
+    if (check_for_string_arg(argvars, 0) == FAIL
+           || check_for_string_arg(argvars, 1) == FAIL
+           || check_for_string_arg(argvars, 2) == FAIL)
+       return;
+
+    // NOTE: var2fpos() returns static pointer.
+    fp = var2fpos(&argvars[0], TRUE, &fnum, FALSE);
+    if (fp == NULL)
+       return;
+    p1 = *fp;
+
+    fp = var2fpos(&argvars[1], TRUE, &fnum, FALSE);
+    if (fp == NULL)
+       return;
+    p2 = *fp;
+
+    pos1 = tv_get_string(&argvars[0]);
+    pos2 = tv_get_string(&argvars[1]);
+    type = tv_get_string(&argvars[2]);
+
+    is_visual = (pos1[0] == 'v' && pos1[1] == NUL)
+       || (pos2[0] == 'v' && pos2[1] == NUL);
+
+    if (is_visual && !VIsual_active)
+       return;
+
+    if (type[0] == 'v' && type[1] == NUL)
+       region_type = MCHAR;
+    else if (type[0] == 'V' && type[1] == NUL)
+       region_type = MLINE;
+    else if (type[0] == Ctrl_V && type[1] == NUL)
+       region_type = MBLOCK;
+    else
+       return;
+
+    save_virtual = virtual_op;
+    virtual_op = virtual_active();
+
+    if (!LT_POS(p1, p2))
+    {
+       // swap position
+       pos_T p;
+
+       p = p1;
+       p1 = p2;
+       p2 = p;
+    }
+
+    if (region_type == MCHAR)
+    {
+       // handle 'selection' == "exclusive"
+       if (*p_sel == 'e' && !EQUAL_POS(p1, p2))
+       {
+           if (p2.coladd > 0)
+               p2.coladd--;
+           else if (p2.col > 0)
+           {
+               p2.col--;
+
+               mb_adjustpos(curbuf, &p2);
+           }
+           else if (p2.lnum > 1)
+           {
+               p2.lnum--;
+               p2.col = (colnr_T)STRLEN(ml_get(p2.lnum));
+               if (p2.col > 0)
+               {
+                   p2.col--;
+
+                   mb_adjustpos(curbuf, &p2);
+               }
+           }
+       }
+       // if fp2 is on NUL (empty line) inclusive becomes false
+       if (*ml_get_pos(&p2) == NUL && !virtual_op)
+           inclusive = FALSE;
+    }
+    else if (region_type == MBLOCK)
+    {
+       colnr_T sc1, ec1, sc2, ec2;
+
+       getvvcol(curwin, &p1, &sc1, NULL, &ec1);
+       getvvcol(curwin, &p2, &sc2, NULL, &ec2);
+       oap.motion_type = OP_NOP;
+       oap.inclusive = TRUE;
+       oap.start = p1;
+       oap.end = p2;
+       oap.start_vcol = MIN(sc1, sc2);
+       if (*p_sel == 'e' && ec1 < sc2 && 0 < sc2 && ec2 > ec1)
+           oap.end_vcol = sc2 - 1;
+       else
+           oap.end_vcol = MAX(ec1, ec2);
+    }
+
+    // Include the trailing byte of a multi-byte char.
+    l = utfc_ptr2len((char_u *)ml_get_pos(&p2));
+    if (l > 1)
+       p2.col += l - 1;
+
+    for (lnum = p1.lnum; lnum <= p2.lnum; lnum++)
+    {
+       int ret = 0;
+
+       if (region_type == MLINE)
+           akt = vim_strsave(ml_get(lnum));
+       else if (region_type == MBLOCK)
+       {
+           block_prep(&oap, &bd, lnum, FALSE);
+           akt = block_def2str(&bd);
+       }
+       else if (p1.lnum < lnum && lnum < p2.lnum)
+           akt = vim_strsave(ml_get(lnum));
+       else
+       {
+           charwise_block_prep(p1, p2, &bd, lnum, inclusive);
+           akt = block_def2str(&bd);
+       }
+
+       if (akt)
+       {
+           ret = list_append_string(rettv->vval.v_list, akt, -1);
+           vim_free(akt);
+       }
+
+       if (akt == NULL || ret == FAIL)
+       {
+           list_free(rettv->vval.v_list);
+           break;
+       }
+    }
+
+    virtual_op = save_virtual;
+}
+
 /*
  * Common between getreg(), getreginfo() and getregtype(): get the register
  * name from the first argument.
diff --git a/src/ops.c b/src/ops.c
index 3fefbc8a4..e66b4b5ee 100644
--- a/src/ops.c
+++ b/src/ops.c
@@ -2414,6 +2414,84 @@ block_prep(
 #endif
 }
 
+/*
+ * Get block text from "start" to "end"
+ */
+    void
+charwise_block_prep(
+    pos_T              start,
+    pos_T              end,
+    struct block_def   *bdp,
+    linenr_T           lnum,
+    int                        inclusive)
+{
+    colnr_T startcol = 0, endcol = MAXCOL;
+    int            is_oneChar = FALSE;
+    colnr_T cs, ce;
+    char_u *p;
+
+    p = ml_get(lnum);
+    bdp->startspaces = 0;
+    bdp->endspaces = 0;
+
+    if (lnum == start.lnum)
+    {
+       startcol = start.col;
+       if (virtual_op)
+       {
+           getvcol(curwin, &start, &cs, NULL, &ce);
+           if (ce != cs && start.coladd > 0)
+           {
+               // Part of a tab selected -- but don't
+               // double-count it.
+               bdp->startspaces = (ce - cs + 1)
+                   - start.coladd;
+               if (bdp->startspaces < 0)
+                   bdp->startspaces = 0;
+               startcol++;
+           }
+       }
+    }
+
+    if (lnum == end.lnum)
+    {
+       endcol = end.col;
+       if (virtual_op)
+       {
+           getvcol(curwin, &end, &cs, NULL, &ce);
+           if (p[endcol] == NUL || (cs + end.coladd < ce
+                       // Don't add space for double-wide
+                       // char; endcol will be on last byte
+                       // of multi-byte char.
+                       && (*mb_head_off)(p, p + endcol) == 0))
+           {
+               if (start.lnum == end.lnum
+                       && start.col == end.col)
+               {
+                   // Special case: inside a single char
+                   is_oneChar = TRUE;
+                   bdp->startspaces = end.coladd
+                       - start.coladd + inclusive;
+                   endcol = startcol;
+               }
+               else
+               {
+                   bdp->endspaces = end.coladd
+                       + inclusive;
+                   endcol -= inclusive;
+               }
+           }
+       }
+    }
+    if (endcol == MAXCOL)
+       endcol = (colnr_T)STRLEN(p);
+    if (startcol > endcol || is_oneChar)
+       bdp->textlen = 0;
+    else
+       bdp->textlen = endcol - startcol + inclusive;
+    bdp->textstart = p + startcol;
+}
+
 /*
  * Handle the add/subtract operator.
  */
diff --git a/src/proto/ops.pro b/src/proto/ops.pro
index 193e89430..bebe63553 100644
--- a/src/proto/ops.pro
+++ b/src/proto/ops.pro
@@ -14,6 +14,7 @@ void adjust_cursor_eol(void);
 char_u *skip_comment(char_u *line, int process, int include_space, int 
*is_comment);
 int do_join(long count, int insert_space, int save_undo, int 
use_formatoptions, int setmark);
 void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, int 
is_del);
+void charwise_block_prep(pos_T start, pos_T end, struct block_def *bdp, 
linenr_T lnum, int inclusive);
 void op_addsub(oparg_T *oap, linenr_T Prenum1, int g_cmd);
 void clear_oparg(oparg_T *oap);
 void cursor_pos_info(dict_T *dict);
diff --git a/src/register.c b/src/register.c
index 3f1506a5d..98015e05b 100644
--- a/src/register.c
+++ b/src/register.c
@@ -1148,7 +1148,6 @@ op_yank(oparg_T *oap, int deleting, int mess)
     int                        yanktype = oap->motion_type;
     long               yanklines = oap->line_count;
     linenr_T           yankendlnum = oap->end.lnum;
-    char_u             *p;
     char_u             *pnew;
     struct block_def   bd;
 #if defined(FEAT_CLIPBOARD) && defined(FEAT_X11)
@@ -1240,70 +1239,7 @@ op_yank(oparg_T *oap, int deleting, int mess)
 
            case MCHAR:
                {
-                   colnr_T startcol = 0, endcol = MAXCOL;
-                   int     is_oneChar = FALSE;
-                   colnr_T cs, ce;
-
-                   p = ml_get(lnum);
-                   bd.startspaces = 0;
-                   bd.endspaces = 0;
-
-                   if (lnum == oap->start.lnum)
-                   {
-                       startcol = oap->start.col;
-                       if (virtual_op)
-                       {
-                           getvcol(curwin, &oap->start, &cs, NULL, &ce);
-                           if (ce != cs && oap->start.coladd > 0)
-                           {
-                               // Part of a tab selected -- but don't
-                               // double-count it.
-                               bd.startspaces = (ce - cs + 1)
-                                                         - oap->start.coladd;
-                               if (bd.startspaces < 0)
-                                   bd.startspaces = 0;
-                               startcol++;
-                           }
-                       }
-                   }
-
-                   if (lnum == oap->end.lnum)
-                   {
-                       endcol = oap->end.col;
-                       if (virtual_op)
-                       {
-                           getvcol(curwin, &oap->end, &cs, NULL, &ce);
-                           if (p[endcol] == NUL || (cs + oap->end.coladd < ce
-                                       // Don't add space for double-wide
-                                       // char; endcol will be on last byte
-                                       // of multi-byte char.
-                                       && (*mb_head_off)(p, p + endcol) == 0))
-                           {
-                               if (oap->start.lnum == oap->end.lnum
-                                           && oap->start.col == oap->end.col)
-                               {
-                                   // Special case: inside a single char
-                                   is_oneChar = TRUE;
-                                   bd.startspaces = oap->end.coladd
-                                        - oap->start.coladd + oap->inclusive;
-                                   endcol = startcol;
-                               }
-                               else
-                               {
-                                   bd.endspaces = oap->end.coladd
-                                                            + oap->inclusive;
-                                   endcol -= oap->inclusive;
-                               }
-                           }
-                       }
-                   }
-                   if (endcol == MAXCOL)
-                       endcol = (colnr_T)STRLEN(p);
-                   if (startcol > endcol || is_oneChar)
-                       bd.textlen = 0;
-                   else
-                       bd.textlen = endcol - startcol + oap->inclusive;
-                   bd.textstart = p + startcol;
+                   charwise_block_prep(oap->start, oap->end, &bd, lnum, 
oap->inclusive);
                    if (yank_copy_line(&bd, y_idx, FALSE) == FAIL)
                        goto fail;
                    break;
diff --git a/src/testdir/test_vim9_builtin.vim 
b/src/testdir/test_vim9_builtin.vim
index aa819811c..e2585d4f6 100644
--- a/src/testdir/test_vim9_builtin.vim
+++ b/src/testdir/test_vim9_builtin.vim
@@ -5197,4 +5197,13 @@ def Test_passing_type_to_builtin()
   v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value')
 enddef
 
+def Test_getregion()
+  assert_equal(['x'], getregion('.', '.', 'v')->map((_, _) => 'x'))
+
+  v9.CheckDefAndScriptFailure(['getregion(10, ".", "v")'], ['E1013: Argument 
1: type mismatch, expected string but got number', 'E1174: String required for 
argument 1'])
+  assert_equal([''], getregion('.', '.', 'v'))
+  v9.CheckDefExecFailure(['getregion("a", ".", "v")'], 'E1209:')
+  v9.CheckDefExecAndScriptFailure(['getregion("", ".", "v")'], 'E1209: Invalid 
value for a line number')
+enddef
+
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/testdir/test_visual.vim b/src/testdir/test_visual.vim
index 17f0fd068..34c572e61 100644
--- a/src/testdir/test_visual.vim
+++ b/src/testdir/test_visual.vim
@@ -1630,4 +1630,159 @@ func Test_visual_substitute_visual()
   bwipe!
 endfunc
 
+func Test_visual_getregion()
+  new
+
+  call setline(1, ['one', 'two', 'three'])
+
+  " Visual mode
+  call cursor(1, 1)
+  call feedkeys("\<ESC>vjl", 'tx')
+  call assert_equal(['one', 'tw'], 'v'->getregion('.', 'v'))
+  call assert_equal(['one', 'tw'], '.'->getregion('v', 'v'))
+  call assert_equal(['o'], 'v'->getregion('v', 'v'))
+  call assert_equal(['w'], '.'->getregion('.', 'v'))
+  call assert_equal(['one', 'two'], '.'->getregion('v', 'V'))
+  call assert_equal(['on', 'tw'], '.'->getregion('v', "\<C-v>"))
+
+  " Line visual mode
+  call cursor(1, 1)
+  call feedkeys("\<ESC>Vl", 'tx')
+  call assert_equal(['one'], getregion('v', '.', 'V'))
+  call assert_equal(['one'], getregion('.', 'v', 'V'))
+  call assert_equal(['one'], getregion('v', 'v', 'V'))
+  call assert_equal(['one'], getregion('.', '.', 'V'))
+  call assert_equal(['on'], '.'->getregion('v', 'v'))
+  call assert_equal(['on'], '.'->getregion('v', "\<C-v>"))
+
+  " Block visual mode
+  call cursor(1, 1)
+  call feedkeys("\<ESC>\<C-v>ll", 'tx')
+  call assert_equal(['one'], getregion('v', '.', "\<C-v>"))
+  call assert_equal(['one'], getregion('.', 'v', "\<C-v>"))
+  call assert_equal(['o'], getregion('v', 'v', "\<C-v>"))
+  call assert_equal(['e'], getregion('.', '.', "\<C-v>"))
+  call assert_equal(['one'], '.'->getregion('v', 'V'))
+  call assert_equal(['one'], '.'->getregion('v', 'v'))
+
+  " Using Marks
+  call setpos("'a", [0, 2, 3, 0])
+  call cursor(1, 1)
+  call assert_equal(['one', 'two'], "'a"->getregion('.', 'v'))
+  call assert_equal(['one', 'two'], "."->getregion("'a", 'v'))
+  call assert_equal(['one', 'two'], "."->getregion("'a", 'V'))
+  call assert_equal(['two'], "'a"->getregion("'a", 'V'))
+  call assert_equal(['one', 'two'], "."->getregion("'a", "\<c-v>"))
+
+  " Multiline with line visual mode
+  call cursor(1, 1)
+  call feedkeys("\<ESC>Vjj", 'tx')
+  call assert_equal(['one', 'two', 'three'], getregion('v', '.', 'V'))
+
+  " Multiline with block visual mode
+  call cursor(1, 1)
+  call feedkeys("\<ESC>\<C-v>jj", 'tx')
+  call assert_equal(['o', 't', 't'], getregion('v', '.', "\<C-v>"))
+
+  call cursor(1, 1)
+  call feedkeys("\<ESC>\<C-v>jj$", 'tx')
+  call assert_equal(['one', 'two', 'three'], getregion('v', '.', "\<C-v>"))
+
+  " 'virtualedit'
+  set virtualedit=all
+  call cursor(1, 1)
+  call feedkeys("\<ESC>\<C-v>10ljj$", 'tx')
+  call assert_equal(['one   ', 'two   ', 'three '],
+        \ getregion('v', '.', "\<C-v>"))
+  set virtualedit&
+
+  " Invalid position
+  call cursor(1, 1)
+  call feedkeys("\<ESC>vjj$", 'tx')
+  call assert_fails("call getregion(1, 2, 'v')", 'E1174:')
+  call assert_fails("call getregion('.', {}, 'v')", 'E1174:')
+  call assert_equal([], getregion('', '.', 'v'))
+  call assert_equal([], getregion('.', '.', ''))
+  call feedkeys("\<ESC>", 'tx')
+  call assert_equal([], getregion('v', '.', 'v'))
+
+  " using an unset mark
+  call assert_equal([], "'z"->getregion(".", 'V'))
+  " using the wrong type
+  call assert_fails(':echo "."->getregion([],"V")', 'E1174:')
+  call assert_fails(':echo "."->getregion("$", {})', 'E1174:')
+  call assert_fails(':echo [0, 1, 1, 0]->getregion("$", "v")', 'E1174:')
+
+
+  bwipe!
+  " Selection in starts or ends in the middle of a multibyte character
+  new
+  call setline(1, [
+        \   "abcdefghijk\u00ab",
+        \   "\U0001f1e6\u00ab\U0001f1e7\u00ab\U0001f1e8\u00ab\U0001f1e9",
+        \   "1234567890"
+        \ ])
+  call cursor(1, 3)
+  call feedkeys("\<Esc>\<C-v>ljj", 'xt')
+  call assert_equal(['cd', "\u00ab ", '34'],
+        \ getregion('v', '.', "\<C-v>"))
+  call cursor(1, 4)
+  call feedkeys("\<Esc>\<C-v>ljj", 'xt')
+  call assert_equal(['de', "\U0001f1e7", '45'],
+        \ getregion('v', '.', "\<C-v>"))
+  call cursor(1, 5)
+  call feedkeys("\<Esc>\<C-v>jj", 'xt')
+  call assert_equal(['e', ' ', '5'], getregion('v', '.', "\<C-v>"))
+  call cursor(1, 1)
+  call feedkeys("\<Esc>vj", 'xt')
+  call assert_equal(['abcdefghijk«', "\U0001f1e6"], getregion('v', '.', "v"))
+  " marks on multibyte chars
+  set selection=exclusive
+  call setpos("'a", [0, 1, 11, 0])
+  call setpos("'b", [0, 2, 16, 0])
+  call setpos("'c", [0, 2, 0, 0])
+  call cursor(1, 1)
+  call assert_equal(['ghijk', '🇨«🇩'], getregion("'a", "'b", "\<c-v>"))
+  call assert_equal(['k«', '🇦«🇧«🇨'], getregion("'a", "'b", "v"))
+  call assert_equal(['k«'], getregion("'a", "'c", "v"))
+
+  bwipe!
+
+  " Exclusive selection
+  new
+  set selection=exclusive
+  call setline(1, ["a  c", "x  z", '', ''])
+  call cursor(1, 1)
+  call feedkeys("\<Esc>v2l", 'xt')
+  call assert_equal(["a        "], getregion('v', '.', 'v'))
+  call cursor(1, 1)
+  call feedkeys("\<Esc>v$G", 'xt')
+  call assert_equal(["a        c", "x  z", ''], getregion('v', '.', 'v'))
+  call cursor(1, 1)
+  call feedkeys("\<Esc>v$j", 'xt')
+  call assert_equal(["a        c", "x  z"], getregion('v', '.', 'v'))
+  call cursor(1, 1)
+  call feedkeys("\<Esc>\<C-v>$j", 'xt')
+  call assert_equal(["a        c", "x  z"], getregion('v', '.', "\<C-v>"))
+  call cursor(1, 1)
+  call feedkeys("\<Esc>\<C-v>$G", 'xt')
+  call assert_equal(["a", "x", '', ''], getregion('v', '.', "\<C-v>"))
+  call cursor(1, 1)
+  call feedkeys("\<Esc>wv2j", 'xt')
+  call assert_equal(["c", "x   z"], getregion('v', '.', 'v'))
+
+  " virtualedit
+  set virtualedit=all
+  call cursor(1, 1)
+  call feedkeys("\<Esc>2lv2lj", 'xt')
+  call assert_equal(['      c', 'x   '], getregion('v', '.', 'v'))
+  call cursor(1, 1)
+  call feedkeys("\<Esc>2l\<C-v>2l2j", 'xt')
+  call assert_equal(['  ', '  ', '  '], getregion('v', '.', "\<C-v>"))
+  set virtualedit&
+  set selection&
+
+  bwipe!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index f3d8fd8d1..d49516551 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    120,
 /**/
     119,
 /**/

-- 
-- 
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/E1rcZKI-009Ib7-QD%40256bit.org.

Raspunde prin e-mail lui