patch 9.1.0394: Cannot get a list of positions describing a region

Problem:  Cannot get a list of positions describing a region
          (Justin M. Keyes, after v9.1.0120)
Solution: Add the getregionpos() function
          (Shougo Matsushita)

fixes: #14609
closes: #14617

Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
Signed-off-by: Shougo Matsushita <Shougo.Matsu@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 7c7a202..cca7e2c 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -73,6 +73,7 @@
 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_getregionpos(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);
@@ -2136,6 +2137,8 @@
 			ret_dict_any,	    f_getreginfo},
     {"getregion",	2, 3, FEARG_1,	    arg3_list_list_dict,
 			ret_list_string,    f_getregion},
+    {"getregionpos",   2, 3, FEARG_1,      arg3_list_list_dict,
+			ret_list_string,    f_getregionpos},
     {"getregtype",	0, 1, FEARG_1,	    arg1_string,
 			ret_string,	    f_getregtype},
     {"getscriptinfo",	0, 1, 0,	    arg1_dict_any,
@@ -5481,40 +5484,35 @@
     return ret;
 }
 
-/*
- * "getregion()" function
- */
-    static void
-f_getregion(typval_T *argvars, typval_T *rettv)
+    static int
+getregionpos(
+    typval_T	*argvars,
+    typval_T	*rettv,
+    pos_T	*p1, pos_T *p2,
+    int		*inclusive,
+    int		*region_type,
+    oparg_T	*oa,
+    int		*fnum)
 {
-    linenr_T		lnum;
-    oparg_T		oa;
-    struct block_def	bd;
-    char_u		*akt = NULL;
-    int			inclusive = TRUE;
-    int			fnum1 = -1, fnum2 = -1;
-    pos_T		p1, p2;
-    char_u		*type;
-    buf_T		*save_curbuf;
-    buf_T		*findbuf;
-    char_u		default_type[] = "v";
-    int			save_virtual;
-    int			l;
-    int			region_type = -1;
-    int			is_select_exclusive;
+    int		fnum1 = -1, fnum2 = -1;
+    char_u	*type;
+    buf_T	*findbuf;
+    char_u	default_type[] = "v";
+    int		is_select_exclusive;
+    int		l;
 
     if (rettv_list_alloc(rettv) == FAIL)
-	return;
+	return FAIL;
 
     if (check_for_list_arg(argvars, 0) == FAIL
 	    || check_for_list_arg(argvars, 1) == FAIL
 	    || check_for_opt_dict_arg(argvars, 2) == FAIL)
-	return;
+	return FAIL;
 
-    if (list2fpos(&argvars[0], &p1, &fnum1, NULL, FALSE) != OK
-	    || list2fpos(&argvars[1], &p2, &fnum2, NULL, FALSE) != OK
+    if (list2fpos(&argvars[0], p1, &fnum1, NULL, FALSE) != OK
+	    || list2fpos(&argvars[1], p2, &fnum2, NULL, FALSE) != OK
 	    || fnum1 != fnum2)
-	return;
+	return FAIL;
 
     if (argvars[2].v_type == VAR_DICT)
     {
@@ -5532,125 +5530,152 @@
     }
 
     if (type[0] == 'v' && type[1] == NUL)
-	region_type = MCHAR;
+	*region_type = MCHAR;
     else if (type[0] == 'V' && type[1] == NUL)
-	region_type = MLINE;
+	*region_type = MLINE;
     else if (type[0] == Ctrl_V && type[1] == NUL)
-	region_type = MBLOCK;
+	*region_type = MBLOCK;
     else
     {
 	semsg(_(e_invalid_value_for_argument_str_str), "type", type);
-	return;
+	return FAIL;
     }
 
     findbuf = fnum1 != 0 ? buflist_findnr(fnum1) : curbuf;
+    *fnum = fnum1 != 0 ? fnum1 : curbuf->b_fnum;
     if (findbuf == NULL || findbuf->b_ml.ml_mfp == NULL)
     {
 	emsg(_(e_buffer_is_not_loaded));
-	return;
+	return FAIL;
     }
 
-    if (p1.lnum < 1 || p1.lnum > findbuf->b_ml.ml_line_count)
+    if (p1->lnum < 1 || p1->lnum > findbuf->b_ml.ml_line_count)
     {
-	semsg(_(e_invalid_line_number_nr), p1.lnum);
-	return;
+	semsg(_(e_invalid_line_number_nr), p1->lnum);
+	return FAIL;
     }
-    if (p1.col == MAXCOL)
-	p1.col = ml_get_buf_len(findbuf, p1.lnum) + 1;
-    else if (p1.col < 1 || p1.col > ml_get_buf_len(findbuf, p1.lnum) + 1)
+    if (p1->col == MAXCOL)
+	p1->col = ml_get_buf_len(findbuf, p1->lnum) + 1;
+    else if (p1->col < 1 || p1->col > ml_get_buf_len(findbuf, p1->lnum) + 1)
     {
-	semsg(_(e_invalid_column_number_nr), p1.col);
-	return;
+	semsg(_(e_invalid_column_number_nr), p1->col);
+	return FAIL;
     }
 
-    if (p2.lnum < 1 || p2.lnum > findbuf->b_ml.ml_line_count)
+    if (p2->lnum < 1 || p2->lnum > findbuf->b_ml.ml_line_count)
     {
-	semsg(_(e_invalid_line_number_nr), p2.lnum);
-	return;
+	semsg(_(e_invalid_line_number_nr), p2->lnum);
+	return FAIL;
     }
-    if (p2.col == MAXCOL)
-	p2.col = ml_get_buf_len(findbuf, p2.lnum) + 1;
-    else if (p2.col < 1 || p2.col > ml_get_buf_len(findbuf, p2.lnum) + 1)
+    if (p2->col == MAXCOL)
+	p2->col = ml_get_buf_len(findbuf, p2->lnum) + 1;
+    else if (p2->col < 1 || p2->col > ml_get_buf_len(findbuf, p2->lnum) + 1)
     {
-	semsg(_(e_invalid_column_number_nr), p2.col);
-	return;
+	semsg(_(e_invalid_column_number_nr), p2->col);
+	return FAIL;
     }
 
-    save_curbuf = curbuf;
     curbuf = findbuf;
     curwin->w_buffer = curbuf;
-    save_virtual = virtual_op;
     virtual_op = virtual_active();
 
-    // NOTE: Adjust is needed.
-    p1.col--;
-    p2.col--;
+    // NOTE: Adjustment is needed.
+    p1->col--;
+    p2->col--;
 
-    if (!LT_POS(p1, p2))
+    if (!LT_POS(*p1, *p2))
     {
 	// swap position
 	pos_T p;
 
-	p = p1;
-	p1 = p2;
-	p2 = p;
+	p = *p1;
+	*p1 = *p2;
+	*p2 = p;
     }
 
-    if (region_type == MCHAR)
+    if (*region_type == MCHAR)
     {
 	// handle 'selection' == "exclusive"
-	if (is_select_exclusive && !EQUAL_POS(p1, p2))
+	if (is_select_exclusive && !EQUAL_POS(*p1, *p2))
 	{
-	    if (p2.coladd > 0)
-		p2.coladd--;
-	    else if (p2.col > 0)
+	    if (p2->coladd > 0)
+		p2->coladd--;
+	    else if (p2->col > 0)
 	    {
-		p2.col--;
+		p2->col--;
 
-		mb_adjustpos(curbuf, &p2);
+		mb_adjustpos(curbuf, p2);
 	    }
-	    else if (p2.lnum > 1)
+	    else if (p2->lnum > 1)
 	    {
-		p2.lnum--;
-		p2.col = ml_get_len(p2.lnum);
-		if (p2.col > 0)
+		p2->lnum--;
+		p2->col = ml_get_len(p2->lnum);
+		if (p2->col > 0)
 		{
-		    p2.col--;
+		    p2->col--;
 
-		    mb_adjustpos(curbuf, &p2);
+		    mb_adjustpos(curbuf, p2);
 		}
 	    }
 	}
 	// if fp2 is on NUL (empty line) inclusive becomes false
-	if (*ml_get_pos(&p2) == NUL && !virtual_op)
-	    inclusive = FALSE;
+	if (*ml_get_pos(p2) == NUL && !virtual_op)
+	    *inclusive = FALSE;
     }
-    else if (region_type == MBLOCK)
+    else if (*region_type == MBLOCK)
     {
 	colnr_T sc1, ec1, sc2, ec2;
 
-	getvvcol(curwin, &p1, &sc1, NULL, &ec1);
-	getvvcol(curwin, &p2, &sc2, NULL, &ec2);
-	oa.motion_type = MBLOCK;
-	oa.inclusive = TRUE;
-	oa.op_type = OP_NOP;
-	oa.start = p1;
-	oa.end = p2;
-	oa.start_vcol = MIN(sc1, sc2);
+	getvvcol(curwin, p1, &sc1, NULL, &ec1);
+	getvvcol(curwin, p2, &sc2, NULL, &ec2);
+	oa->motion_type = MBLOCK;
+	oa->inclusive = TRUE;
+	oa->op_type = OP_NOP;
+	oa->start = *p1;
+	oa->end = *p2;
+	oa->start_vcol = MIN(sc1, sc2);
 	if (is_select_exclusive && ec1 < sc2 && 0 < sc2 && ec2 > ec1)
-	    oa.end_vcol = sc2 - 1;
+	    oa->end_vcol = sc2 - 1;
 	else
-	    oa.end_vcol = MAX(ec1, ec2);
+	    oa->end_vcol = MAX(ec1, ec2);
     }
 
     // Include the trailing byte of a multi-byte char.
-    l = utfc_ptr2len((char_u *)ml_get_pos(&p2));
+    l = mb_ptr2len((char_u *)ml_get_pos(p2));
     if (l > 1)
-	p2.col += l - 1;
+	p2->col += l - 1;
+
+    return OK;
+}
+
+/*
+ * "getregion()" function
+ */
+    static void
+f_getregion(typval_T *argvars, typval_T *rettv)
+{
+    pos_T		p1, p2;
+    int			inclusive = TRUE;
+    int			region_type = -1;
+    oparg_T		oa;
+    int			fnum;
+
+    buf_T		*save_curbuf;
+    int			save_virtual;
+    char_u		*akt = NULL;
+    linenr_T		lnum;
+
+    save_curbuf = curbuf;
+    save_virtual = virtual_op;
+
+    if (getregionpos(argvars, rettv,
+		&p1, &p2, &inclusive, &region_type, &oa, &fnum) == FAIL)
+	return;
 
     for (lnum = p1.lnum; lnum <= p2.lnum; lnum++)
     {
 	int ret = 0;
+	struct block_def	bd;
 
 	if (region_type == MLINE)
 	    akt = vim_strsave(ml_get(lnum));
@@ -5681,6 +5706,127 @@
 	}
     }
 
+    // getregionpos() breaks curbuf and virtual_op
+    curbuf = save_curbuf;
+    curwin->w_buffer = curbuf;
+    virtual_op = save_virtual;
+}
+
+    static void
+add_regionpos_range(
+    typval_T	*rettv,
+    int		bufnr,
+    int		lnum1,
+    int		col1,
+    int		coladd1,
+    int		lnum2,
+    int		col2,
+    int		coladd2)
+{
+    list_T	*l1, *l2, *l3;
+    buf_T	*findbuf;
+    int		max_col1, max_col2;
+
+    l1 = list_alloc();
+    if (l1 == NULL)
+	return;
+
+    if (list_append_list(rettv->vval.v_list, l1) == FAIL)
+    {
+	vim_free(l1);
+	return;
+    }
+
+    l2 = list_alloc();
+    if (l2 == NULL)
+	return;
+
+    if (list_append_list(l1, l2) == FAIL)
+    {
+	vim_free(l2);
+	return;
+    }
+
+    l3 = list_alloc();
+    if (l3 == NULL)
+	return;
+
+    if (list_append_list(l1, l3) == FAIL)
+    {
+	vim_free(l3);
+	return;
+    }
+
+    findbuf = bufnr != 0 ? buflist_findnr(bufnr) : curbuf;
+
+    max_col1 = ml_get_buf_len(findbuf, lnum1);
+    list_append_number(l2, bufnr);
+    list_append_number(l2, lnum1);
+    list_append_number(l2, col1 > max_col1 ? max_col1 : col1);
+    list_append_number(l2, coladd1);
+
+    max_col2 = ml_get_buf_len(findbuf, lnum2);
+    list_append_number(l3, bufnr);
+    list_append_number(l3, lnum2);
+    list_append_number(l3, col2 > max_col2 ? max_col2 : col2);
+    list_append_number(l3, coladd2);
+}
+
+/*
+ * "getregionpos()" function
+ */
+    static void
+f_getregionpos(typval_T *argvars, typval_T *rettv)
+{
+    pos_T	p1, p2;
+    int		inclusive = TRUE;
+    int		region_type = -1;
+    oparg_T	oa;
+    int		fnum;
+    int		lnum;
+
+    buf_T	*save_curbuf;
+    int		save_virtual;
+
+    save_curbuf = curbuf;
+    save_virtual = virtual_op;
+
+    if (getregionpos(argvars, rettv,
+		&p1, &p2, &inclusive, &region_type, &oa, &fnum) == FAIL)
+	return;
+
+    for (lnum = p1.lnum; lnum <= p2.lnum; lnum++)
+    {
+	struct block_def	bd;
+	int			start_col, end_col;
+
+	if (region_type == MLINE)
+	{
+	    start_col = 1;
+	    end_col = MAXCOL;
+	}
+	else if (region_type == MBLOCK)
+	{
+	    block_prep(&oa, &bd, lnum, FALSE);
+	    start_col = bd.start_vcol + 1;
+	    end_col = bd.end_vcol;
+	}
+	else if (p1.lnum < lnum && lnum < p2.lnum)
+	{
+	    start_col = 1;
+	    end_col = MAXCOL;
+	}
+	else
+	{
+	    start_col = p1.lnum == lnum ? p1.col + 1 : 1;
+	    end_col = p2.lnum == lnum ? p2.col + 1 : MAXCOL;
+	}
+
+	add_regionpos_range(rettv, fnum, lnum, start_col,
+		p1.coladd, lnum, end_col, p2.coladd);
+    }
+
+    // getregionpos() may change curbuf and virtual_op
     curbuf = save_curbuf;
     curwin->w_buffer = curbuf;
     virtual_op = save_virtual;
diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim
index 83153ad..7a83018 100644
--- a/src/testdir/test_vim9_builtin.vim
+++ b/src/testdir/test_vim9_builtin.vim
@@ -5181,10 +5181,26 @@
 
 def Test_getregion()
   assert_equal(['x'], getregion(getpos('.'), getpos('.'))->map((_, _) => 'x'))
+  assert_equal(['x'], getregionpos(getpos('.'), getpos('.'))->map((_, _) => 'x'))
 
-  v9.CheckSourceDefAndScriptFailure(['getregion(10, getpos("."))'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1211: List required for argument 1'])
-  assert_equal([''], getregion(getpos('.'), getpos('.')))
+  v9.CheckSourceDefAndScriptFailure(
+      ['getregion(10, getpos("."))'],
+      ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1211: List required for argument 1']
+  )
+  v9.CheckSourceDefAndScriptFailure(
+      ['getregionpos(10, getpos("."))'],
+      ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1211: List required for argument 1']
+  )
+  assert_equal(
+      [''],
+      getregion(getpos('.'), getpos('.'))
+  )
+  assert_equal(
+      [[[bufnr('%'), 1, 0, 0], [bufnr('%'), 1, 0, 0]]],
+      getregionpos(getpos('.'), getpos('.'))
+  )
   v9.CheckSourceDefExecFailure(['getregion(getpos("a"), getpos("."))'], 'E1209:')
+  v9.CheckSourceDefExecFailure(['getregionpos(getpos("a"), getpos("."))'], 'E1209:')
 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 dff6150..9b8eed4 100644
--- a/src/testdir/test_visual.vim
+++ b/src/testdir/test_visual.vim
@@ -1642,16 +1642,44 @@
     call feedkeys("\<ESC>vjl", 'tx')
     call assert_equal(['one', 'tw'],
           \ 'v'->getpos()->getregion(getpos('.')))
+    call assert_equal([
+          \   [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]],
+          \   [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 2, 0]]
+          \ ],
+          \ 'v'->getpos()->getregionpos(getpos('.')))
     call assert_equal(['one', 'tw'],
           \ '.'->getpos()->getregion(getpos('v')))
+    call assert_equal([
+          \   [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]],
+          \   [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 2, 0]]
+          \ ],
+          \ '.'->getpos()->getregionpos(getpos('v')))
     call assert_equal(['o'],
           \ 'v'->getpos()->getregion(getpos('v')))
+    call assert_equal([
+          \   [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 1, 0]],
+          \ ],
+          \ 'v'->getpos()->getregionpos(getpos('v')))
     call assert_equal(['w'],
           \ '.'->getpos()->getregion(getpos('.'), {'type': 'v' }))
+    call assert_equal([
+          \   [[bufnr('%'), 2, 2, 0], [bufnr('%'), 2, 2, 0]],
+          \ ],
+          \ '.'->getpos()->getregionpos(getpos('.'), {'type': 'v' }))
     call assert_equal(['one', 'two'],
           \ getpos('.')->getregion(getpos('v'), {'type': 'V' }))
+    call assert_equal([
+          \   [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]],
+          \   [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 3, 0]],
+          \ ],
+          \ getpos('.')->getregionpos(getpos('v'), {'type': 'V' }))
     call assert_equal(['on', 'tw'],
           \ getpos('.')->getregion(getpos('v'), {'type': "\<C-v>" }))
+    call assert_equal([
+          \   [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 2, 0]],
+          \   [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 2, 0]],
+          \ ],
+          \ getpos('.')->getregionpos(getpos('v'), {'type': "\<C-v>" }))
 
     #" Line visual mode
     call cursor(1, 1)
@@ -1746,9 +1774,13 @@
     call assert_fails("call getregion(1, 2)", 'E1211:')
     call assert_fails("call getregion(getpos('.'), {})", 'E1211:')
     call assert_fails(':echo "."->getpos()->getregion("$", [])', 'E1211:')
+    call assert_fails("call getregionpos(1, 2)", 'E1211:')
+    call assert_fails("call getregionpos(getpos('.'), {})", 'E1211:')
+    call assert_fails(':echo "."->getpos()->getregionpos("$", [])', 'E1211:')
 
     #" using invalid value for "type"
     call assert_fails("call getregion(getpos('.'), getpos('.'), {'type': '' })", 'E475:')
+    call assert_fails("call getregionpos(getpos('.'), getpos('.'), {'type': '' })", 'E475:')
 
     #" using a mark from another buffer to current buffer
     new
@@ -1759,13 +1791,20 @@
     call assert_equal([g:buf, 10, 1, 0], getpos("'A"))
     call assert_equal([], getregion(getpos('.'), getpos("'A"), {'type': 'v' }))
     call assert_equal([], getregion(getpos("'A"), getpos('.'), {'type': 'v' }))
+    call assert_equal([], getregionpos(getpos('.'), getpos("'A"), {'type': 'v' }))
+    call assert_equal([], getregionpos(getpos("'A"), getpos('.'), {'type': 'v' }))
 
     #" using two marks from another buffer
     wincmd p
     normal! GmB
     wincmd p
     call assert_equal([g:buf, 10, 1, 0], getpos("'B"))
-    call assert_equal(['9'], getregion(getpos("'B"), getpos("'A"), {'type': 'v' }))
+    call assert_equal(['9'],
+          \ getregion(getpos("'B"), getpos("'A"), {'type': 'v' }))
+    call assert_equal([
+          \   [[g:buf, 10, 1, 0], [g:buf, 10, 1, 0]],
+          \ ],
+          \ getregionpos(getpos("'B"), getpos("'A"), {'type': 'v' }))
 
     #" using two positions from another buffer
     for type in ['v', 'V', "\<C-V>"]
@@ -1788,6 +1827,8 @@
     call assert_fails('call getregion([g:buf, 10, 0, 0], [g:buf, 1, 1, 0])', 'E964:')
     call assert_fails('call getregion([g:buf, 1, 1, 0], [g:buf, 10, 3, 0])', 'E964:')
     call assert_fails('call getregion([g:buf, 10, 3, 0], [g:buf, 1, 1, 0])', 'E964:')
+    call assert_fails('call getregion([g:buf, 1, 0, 0], [g:buf, 1, 1, 0])', 'E964:')
+    call assert_fails('call getregion([g:buf, 1, 1, 0], [g:buf, 1, 0, 0])', 'E964:')
 
     #" using invalid buffer
     call assert_fails('call getregion([10000, 10, 1, 0], [10000, 10, 1, 0])', 'E681:')
@@ -1819,6 +1860,12 @@
     call feedkeys("\<Esc>\<C-v>jj", 'xt')
     call assert_equal(['e', ' ', '5'],
           \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" }))
+    call assert_equal([
+          \   [[bufnr('%'), 1, 5, 0], [bufnr('%'), 1, 13, 0]],
+          \   [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 22, 0]],
+          \   [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 5, 0]],
+          \ ],
+          \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' }))
     call cursor(1, 1)
     call feedkeys("\<Esc>vj", 'xt')
     call assert_equal(['abcdefghijk«', "\U0001f1e6"],
@@ -1831,7 +1878,7 @@
     call setpos("'c", [0, 2, 0, 0])
     call cursor(1, 1)
     call assert_equal(['ghijk', '🇨«ðŸ‡©'],
-          \ getregion(getpos("'a"), getpos("'b"), {'type': "\<c-v>" }))
+          \ getregion(getpos("'a"), getpos("'b"), {'type': "\<C-v>" }))
     call assert_equal(['k«', '🇦«ðŸ‡§«ðŸ‡¨'],
           \ getregion(getpos("'a"), getpos("'b"), {'type': 'v' }))
     call assert_equal(['k«'],
@@ -1958,4 +2005,30 @@
   bwipe!
 endfunc
 
+func Test_getregion_maxcol()
+  new
+  autocmd TextYankPost *
+        \ : if v:event.operator ==? 'y'
+        \ | call assert_equal([
+        \                       [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 4, 0]],
+        \                     ],
+        \                     getregionpos(getpos("'["), getpos("']"),
+        \                                  #{ mode: visualmode() }))
+        \ | call assert_equal(['abcd'],
+        \                     getregion(getpos("'["), getpos("']"),
+        \                               #{ mode: visualmode() }))
+        \ | call assert_equal([
+        \                       [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 4, 0]],
+        \                     ],
+        \                     getregionpos(getpos("']"), getpos("'["),
+        \                                  #{ mode: visualmode() }))
+        \ | call assert_equal(['abcd'],
+        \                     getregion(getpos("']"), getpos("'["),
+        \                               #{ mode: visualmode() }))
+        \ | endif
+  call setline(1, ['abcd', 'efghij'])
+  normal yy
+  bwipe!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 5312bfe..1281970 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    394,
+/**/
     393,
 /**/
     392,