changeset 35125:9322493ebe58 v9.1.0394

patch 9.1.0394: Cannot get a list of positions describing a region Commit: https://github.com/vim/vim/commit/b4757e627e6c83d1c8e5535d4887a82d6a5efdd0 Author: Shougo Matsushita <Shougo.Matsu@gmail.com> Date: Tue May 7 20:49:24 2024 +0200 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>
author Christian Brabandt <cb@256bit.org>
date Tue, 07 May 2024 21:00:04 +0200
parents 4aa752e16fc2
children d9d0c879d865
files runtime/doc/builtin.txt runtime/doc/tags runtime/doc/usr_41.txt runtime/doc/version9.txt src/evalfunc.c src/testdir/test_vim9_builtin.vim src/testdir/test_visual.vim src/version.c
diffstat 8 files changed, 373 insertions(+), 111 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1,4 +1,4 @@
-*builtin.txt*	For Vim version 9.1.  Last change: 2024 May 05
+*builtin.txt*	For Vim version 9.1.  Last change: 2024 May 07
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -265,6 +265,8 @@ getreg([{regname} [, 1 [, {list}]]])
 getreginfo([{regname}])		Dict	information about a register
 getregion({pos1}, {pos2} [, {opts}])
 				List	get the text from {pos1} to {pos2}
+getregionpos({pos1}, {pos2} [, {opts}])
+				List	get a list of positions for a region
 getregtype([{regname}])		String	type of a register
 getscriptinfo([{opts}])		List	list of sourced scripts
 gettabinfo([{expr}])		List	list of tab pages
@@ -4327,6 +4329,26 @@ getregion({pos1}, {pos2} [, {opts}])			*
 		Can also be used as a |method|: >
 			getpos('.')->getregion(getpos("'a"))
 <
+getregionpos({pos1}, {pos2} [, {opts}])            *getregionpos()*
+		Same as |getregion()|, but returns a list of positions
+		describing the buffer text segments bound by {pos1} and
+		{pos2}.
+		The segments are a pair of positions for every line: >
+			[[{start_pos}, {end_pos}], ...]
+<
+		The position is a |List| with four numbers:
+		    [bufnum, lnum, col, off]
+		"bufnum" is the buffer number.
+		"lnum" and "col" are the position in the buffer.  The first
+		column is 1.
+		The "off" number is zero, unless 'virtualedit' is used.  Then
+		it is the offset in screen columns from the start of the
+		character.  E.g., a position within a <Tab> or after the last
+		character.
+
+		Can also be used as a |method|: >
+			getpos('.')->getregionpos(getpos("'a"))
+<
 getregtype([{regname}])					*getregtype()*
 		The result is a String, which is type of register {regname}.
 		The value will be one of:
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -7799,6 +7799,7 @@ getreg()	builtin.txt	/*getreg()*
 getreginfo()	builtin.txt	/*getreginfo()*
 getregion()	builtin.txt	/*getregion()*
 getregion-notes	builtin.txt	/*getregion-notes*
+getregionpos()	builtin.txt	/*getregionpos()*
 getregtype()	builtin.txt	/*getregtype()*
 getscript	pi_getscript.txt	/*getscript*
 getscript-autoinstall	pi_getscript.txt	/*getscript-autoinstall*
--- 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 Apr 26
+*usr_41.txt*	For Vim version 9.1.  Last change: 2024 May 07
 
 		     VIM USER MANUAL - by Bram Moolenaar
 
@@ -930,6 +930,7 @@ Cursor and mark position:		*cursor-funct
 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
+	getregionpos()		get a list of positions for a region
 	setline()		replace a line in the buffer
 	append()		append line or list of lines in the buffer
 	indent()		indent of a specific line
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -41574,6 +41574,7 @@ Functions: ~
 |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
+|getregionpos()|	get a list of positions for a region
 
 
 Autocommands: ~
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -73,6 +73,7 @@ static void f_getpos(typval_T *argvars, 
 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 @@ static funcentry_T global_functions[] =
 			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 @@ block_def2str(struct block_def *bd)
     return ret;
 }
 
-/*
- * "getregion()" function
- */
-    static void
-f_getregion(typval_T *argvars, typval_T *rettv)
-{
-    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;
+    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)
+{
+    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;
-
-    if (list2fpos(&argvars[0], &p1, &fnum1, NULL, FALSE) != OK
-	    || list2fpos(&argvars[1], &p2, &fnum2, NULL, FALSE) != OK
+	return FAIL;
+
+    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 @@ f_getregion(typval_T *argvars, typval_T 
     }
 
     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;
-    }
-
-    if (p1.lnum < 1 || p1.lnum > findbuf->b_ml.ml_line_count)
-    {
-	semsg(_(e_invalid_line_number_nr), p1.lnum);
-	return;
-    }
-    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;
-    }
-
-    if (p2.lnum < 1 || p2.lnum > findbuf->b_ml.ml_line_count)
-    {
-	semsg(_(e_invalid_line_number_nr), p2.lnum);
-	return;
-    }
-    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;
-    }
-
-    save_curbuf = curbuf;
+	return FAIL;
+    }
+
+    if (p1->lnum < 1 || p1->lnum > findbuf->b_ml.ml_line_count)
+    {
+	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)
+    {
+	semsg(_(e_invalid_column_number_nr), p1->col);
+	return FAIL;
+    }
+
+    if (p2->lnum < 1 || p2->lnum > findbuf->b_ml.ml_line_count)
+    {
+	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)
+    {
+	semsg(_(e_invalid_column_number_nr), p2->col);
+	return FAIL;
+    }
+
     curbuf = findbuf;
     curwin->w_buffer = curbuf;
-    save_virtual = virtual_op;
     virtual_op = virtual_active();
 
-    // NOTE: Adjust is needed.
-    p1.col--;
-    p2.col--;
-
-    if (!LT_POS(p1, p2))
+    // NOTE: Adjustment is needed.
+    p1->col--;
+    p2->col--;
+
+    if (!LT_POS(*p1, *p2))
     {
 	// swap position
 	pos_T p;
 
-	p = p1;
-	p1 = p2;
-	p2 = p;
-    }
-
-    if (region_type == MCHAR)
+	p = *p1;
+	*p1 = *p2;
+	*p2 = p;
+    }
+
+    if (*region_type == MCHAR)
     {
 	// handle 'selection' == "exclusive"
-	if (is_select_exclusive && !EQUAL_POS(p1, p2))
-	{
-	    if (p2.coladd > 0)
-		p2.coladd--;
-	    else if (p2.col > 0)
+	if (is_select_exclusive && !EQUAL_POS(*p1, *p2))
+	{
+	    if (p2->coladd > 0)
+		p2->coladd--;
+	    else if (p2->col > 0)
 	    {
-		p2.col--;
-
-		mb_adjustpos(curbuf, &p2);
+		p2->col--;
+
+		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--;
-
-		    mb_adjustpos(curbuf, &p2);
+		    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)
+	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);
-	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 @@ f_getregion(typval_T *argvars, typval_T 
 	}
     }
 
+    // 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;
--- a/src/testdir/test_vim9_builtin.vim
+++ b/src/testdir/test_vim9_builtin.vim
@@ -5181,10 +5181,26 @@ enddef
 
 def Test_getregion()
   assert_equal(['x'], getregion(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('.')))
+  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']
+  )
+  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
--- a/src/testdir/test_visual.vim
+++ b/src/testdir/test_visual.vim
@@ -1642,16 +1642,44 @@ func Test_visual_getregion()
     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 @@ func Test_visual_getregion()
     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 @@ func Test_visual_getregion()
     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 @@ func Test_visual_getregion()
     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 @@ func Test_visual_getregion()
     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 @@ func Test_visual_getregion()
     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 @@ func Test_getregion_invalid_buf()
   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
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    394,
+/**/
     393,
 /**/
     392,