aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2024-02-22 20:32:52 +0800
committerzeertzjq <zeertzjq@outlook.com>2024-02-23 06:39:03 +0800
commit20e4001eeedc80b1f2857fcaca81f7a211a09b40 (patch)
treeaf478c0dc95acf36eaf48d6be9d71539313fd457
parentbb15fa035610bb9765ca16900703804a88faa3bb (diff)
downloadrneovim-20e4001eeedc80b1f2857fcaca81f7a211a09b40.tar.gz
rneovim-20e4001eeedc80b1f2857fcaca81f7a211a09b40.tar.bz2
rneovim-20e4001eeedc80b1f2857fcaca81f7a211a09b40.zip
vim-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: vim/vim#13998 closes: vim/vim#11579 https://github.com/vim/vim/commit/3f905ab3c4f66562f4a224bf00f49d98a0b0da91 Cherry-pick changes from patch 9.1.0122, with :echom instead of :echow. Co-authored-by: Shougo Matsushita <Shougo.Matsu@gmail.com> Co-authored-by: Jakub Łuczyński <doubleloop@o2.pl>
-rw-r--r--runtime/doc/builtin.txt35
-rw-r--r--runtime/doc/usr_41.txt1
-rw-r--r--runtime/lua/vim/_meta/vimfn.lua40
-rw-r--r--src/nvim/eval.lua43
-rw-r--r--src/nvim/eval/funcs.c144
-rw-r--r--src/nvim/ops.c141
-rw-r--r--src/nvim/ops.h19
-rw-r--r--test/old/testdir/test_visual.vim155
8 files changed, 500 insertions, 78 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index c6d75ab985..9c1855806e 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -2918,6 +2918,41 @@ getreginfo([{regname}]) *getreginfo()*
If {regname} is not specified, |v:register| is used.
The returned Dictionary can be passed to |setreg()|.
+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 |charwise| 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 |charwise-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>echom getregion('v', '.', mode())<CR>
+<
+
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/usr_41.txt b/runtime/doc/usr_41.txt
index 2dae9333b6..56750420e9 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -793,6 +793,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/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index 224cecf144..527113c016 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -3525,6 +3525,46 @@ function vim.fn.getreg(regname, list) end
--- @return table
function vim.fn.getreginfo(regname) end
+--- 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 |charwise| 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 |charwise-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>echom getregion('v', '.', mode())<CR>
+--- <
+---
+--- @param pos1 string
+--- @param pos2 string
+--- @param type string
+--- @return string[]
+function vim.fn.getregion(pos1, pos2, type) end
+
--- The result is a String, which is type of register {regname}.
--- The value will be one of:
--- "v" for |charwise| text
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 2630241077..231dd1f9bf 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -4355,6 +4355,49 @@ M.funcs = {
returns = 'table',
signature = 'getreginfo([{regname}])',
},
+ getregion = {
+ args = 3,
+ base = 1,
+ desc = [=[
+ 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 |charwise| 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 |charwise-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>echom getregion('v', '.', mode())<CR>
+ <
+ ]=],
+ name = 'getregion',
+ params = { { 'pos1', 'string' }, { 'pos2', 'string' }, { 'type', 'string' } },
+ returns = 'string[]',
+ signature = 'getregion({pos1}, {pos2}, {type})',
+ },
getregtype = {
args = { 0, 1 },
base = 1,
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 01e666887a..b62ed557e4 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -130,6 +130,7 @@
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/tag.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/version.h"
#include "nvim/vim_defs.h"
@@ -2801,6 +2802,149 @@ static void f_getpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
getpos_both(argvars, rettv, false, false);
}
+/// Convert from block_def to string
+static char *block_def2str(struct block_def *bd)
+{
+ size_t size = (size_t)bd->startspaces + (size_t)bd->endspaces + (size_t)bd->textlen;
+ char *ret = xmalloc(size + 1);
+ char *p = ret;
+ memset(p, ' ', (size_t)bd->startspaces);
+ p += bd->startspaces;
+ memmove(p, bd->textstart, (size_t)bd->textlen);
+ p += bd->textlen;
+ memset(p, ' ', (size_t)bd->endspaces);
+ *(p + bd->endspaces) = NUL;
+ return ret;
+}
+
+/// "getregion()" function
+static void f_getregion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ if (tv_check_for_string_arg(argvars, 0) == FAIL
+ || tv_check_for_string_arg(argvars, 1) == FAIL
+ || tv_check_for_string_arg(argvars, 2) == FAIL) {
+ return;
+ }
+
+ int fnum = -1;
+ // NOTE: var2fpos() returns static pointer.
+ pos_T *fp = var2fpos(&argvars[0], true, &fnum, false);
+ if (fp == NULL) {
+ return;
+ }
+ pos_T p1 = *fp;
+
+ fp = var2fpos(&argvars[1], true, &fnum, false);
+ if (fp == NULL) {
+ return;
+ }
+ pos_T p2 = *fp;
+
+ const char *pos1 = tv_get_string(&argvars[0]);
+ const char *pos2 = tv_get_string(&argvars[1]);
+ const char *type = tv_get_string(&argvars[2]);
+
+ const bool is_visual
+ = (pos1[0] == 'v' && pos1[1] == NUL) || (pos2[0] == 'v' && pos2[1] == NUL);
+
+ if (is_visual && !VIsual_active) {
+ return;
+ }
+
+ MotionType region_type = kMTUnknown;
+ if (type[0] == 'v' && type[1] == NUL) {
+ region_type = kMTCharWise;
+ } else if (type[0] == 'V' && type[1] == NUL) {
+ region_type = kMTLineWise;
+ } else if (type[0] == Ctrl_V && type[1] == NUL) {
+ region_type = kMTBlockWise;
+ } else {
+ return;
+ }
+
+ const TriState save_virtual = virtual_op;
+ virtual_op = virtual_active();
+
+ if (!lt(p1, p2)) {
+ // swap position
+ pos_T p = p1;
+ p1 = p2;
+ p2 = p;
+ }
+
+ oparg_T oap;
+ bool inclusive = true;
+
+ if (region_type == kMTCharWise) {
+ // handle 'selection' == "exclusive"
+ if (*p_sel == 'e' && !equalpos(p1, p2)) {
+ if (p2.coladd > 0) {
+ p2.coladd--;
+ } else if (p2.col > 0) {
+ p2.col--;
+ mark_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--;
+ mark_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 == kMTBlockWise) {
+ colnr_T sc1, ec1, sc2, ec2;
+ getvvcol(curwin, &p1, &sc1, NULL, &ec1);
+ getvvcol(curwin, &p2, &sc2, NULL, &ec2);
+ oap.motion_type = kMTBlockWise;
+ oap.inclusive = true;
+ oap.op_type = OP_NOP;
+ 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.
+ int l = utfc_ptr2len(ml_get_pos(&p2));
+ if (l > 1) {
+ p2.col += l - 1;
+ }
+
+ for (linenr_T lnum = p1.lnum; lnum <= p2.lnum; lnum++) {
+ char *akt = NULL;
+
+ if (region_type == kMTLineWise) {
+ akt = xstrdup(ml_get(lnum));
+ } else if (region_type == kMTBlockWise) {
+ struct block_def bd;
+ block_prep(&oap, &bd, lnum, false);
+ akt = block_def2str(&bd);
+ } else if (p1.lnum < lnum && lnum < p2.lnum) {
+ akt = xstrdup(ml_get(lnum));
+ } else {
+ struct block_def bd;
+ charwise_block_prep(p1, p2, &bd, lnum, inclusive);
+ akt = block_def2str(&bd);
+ }
+
+ assert(akt != NULL);
+ tv_list_append_allocated_string(rettv->vval.v_list, akt);
+ }
+
+ virtual_op = save_virtual;
+}
+
/// Common between getreg(), getreginfo() and getregtype(): get the register
/// name from the first argument.
/// Returns zero on error.
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index e7a3aa29aa..a4af2a54be 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -84,25 +84,6 @@ static bool clipboard_delay_update = false; // delay clipboard update
static bool clipboard_needs_update = false; // clipboard was updated
static bool clipboard_didwarn = false;
-// structure used by block_prep, op_delete and op_yank for blockwise operators
-// also op_change, op_shift, op_insert, op_replace - AKelly
-struct block_def {
- int startspaces; // 'extra' cols before first char
- int endspaces; // 'extra' cols after last char
- int textlen; // chars in block
- char *textstart; // pointer to 1st char (partially) in block
- colnr_T textcol; // index of chars (partially) in block
- colnr_T start_vcol; // start col of 1st char wholly inside block
- colnr_T end_vcol; // start col of 1st char wholly after block
- int is_short; // true if line is too short to fit in block
- int is_MAX; // true if curswant==MAXCOL when starting
- int is_oneChar; // true if block within one character
- int pre_whitesp; // screen cols of ws before block
- int pre_whitesp_c; // chars of ws before block
- colnr_T end_char_vcols; // number of vcols of post-block char
- colnr_T start_char_vcols; // number of vcols of pre-block char
-};
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ops.c.generated.h"
#endif
@@ -2655,66 +2636,11 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
reg->y_array[y_idx] = xstrdup(ml_get(lnum));
break;
- case kMTCharWise: {
- colnr_T startcol = 0;
- colnr_T endcol = MAXCOL;
- bool is_oneChar = false;
- colnr_T cs, ce;
- char *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.
- && utf_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;
+ case kMTCharWise:
+ charwise_block_prep(oap->start, oap->end, &bd, lnum, oap->inclusive);
yank_copy_line(reg, &bd, y_idx, false);
break;
- }
+
// NOTREACHED
case kMTUnknown:
abort();
@@ -4203,7 +4129,7 @@ static void restore_lbr(bool lbr_saved)
/// - textlen includes the first/last char to be wholly yanked
/// - start/endspaces is the number of columns of the first/last yanked char
/// that are to be yanked.
-static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool is_del)
+void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool is_del)
{
int incr = 0;
// Avoid a problem with unwanted linebreaks in block mode.
@@ -4326,6 +4252,65 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool
restore_lbr(lbr_saved);
}
+/// Get block text from "start" to "end"
+void charwise_block_prep(pos_T start, pos_T end, struct block_def *bdp, linenr_T lnum,
+ bool inclusive)
+{
+ colnr_T startcol = 0;
+ colnr_T endcol = MAXCOL;
+ bool is_oneChar = false;
+ colnr_T cs, ce;
+ char *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.
+ && utf_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.
///
/// @param[in] oap Arguments of operator.
diff --git a/src/nvim/ops.h b/src/nvim/ops.h
index a070db7a3b..1a708fab03 100644
--- a/src/nvim/ops.h
+++ b/src/nvim/ops.h
@@ -15,6 +15,25 @@
#include "nvim/pos_defs.h"
#include "nvim/types_defs.h"
+/// structure used by block_prep, op_delete and op_yank for blockwise operators
+/// also op_change, op_shift, op_insert, op_replace - AKelly
+struct block_def {
+ int startspaces; ///< 'extra' cols before first char
+ int endspaces; ///< 'extra' cols after last char
+ int textlen; ///< chars in block
+ char *textstart; ///< pointer to 1st char (partially) in block
+ colnr_T textcol; ///< index of chars (partially) in block
+ colnr_T start_vcol; ///< start col of 1st char wholly inside block
+ colnr_T end_vcol; ///< start col of 1st char wholly after block
+ int is_short; ///< true if line is too short to fit in block
+ int is_MAX; ///< true if curswant==MAXCOL when starting
+ int is_oneChar; ///< true if block within one character
+ int pre_whitesp; ///< screen cols of ws before block
+ int pre_whitesp_c; ///< chars of ws before block
+ colnr_T end_char_vcols; ///< number of vcols of post-block char
+ colnr_T start_char_vcols; ///< number of vcols of pre-block char
+};
+
typedef int (*Indenter)(void);
/// flags for do_put()
diff --git a/test/old/testdir/test_visual.vim b/test/old/testdir/test_visual.vim
index 262a3a4fd9..084d64ba07 100644
--- a/test/old/testdir/test_visual.vim
+++ b/test/old/testdir/test_visual.vim
@@ -1634,4 +1634,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\tc", "x\tz", '', ''])
+ call cursor(1, 1)
+ call feedkeys("\<Esc>v2l", 'xt')
+ call assert_equal(["a\t"], getregion('v', '.', 'v'))
+ call cursor(1, 1)
+ call feedkeys("\<Esc>v$G", 'xt')
+ call assert_equal(["a\tc", "x\tz", ''], getregion('v', '.', 'v'))
+ call cursor(1, 1)
+ call feedkeys("\<Esc>v$j", 'xt')
+ call assert_equal(["a\tc", "x\tz"], getregion('v', '.', 'v'))
+ call cursor(1, 1)
+ call feedkeys("\<Esc>\<C-v>$j", 'xt')
+ call assert_equal(["a\tc", "x\tz"], 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\tz"], 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