diff options
author | zeertzjq <zeertzjq@outlook.com> | 2024-02-22 20:32:52 +0800 |
---|---|---|
committer | zeertzjq <zeertzjq@outlook.com> | 2024-02-23 06:39:03 +0800 |
commit | 20e4001eeedc80b1f2857fcaca81f7a211a09b40 (patch) | |
tree | af478c0dc95acf36eaf48d6be9d71539313fd457 | |
parent | bb15fa035610bb9765ca16900703804a88faa3bb (diff) | |
download | rneovim-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.txt | 35 | ||||
-rw-r--r-- | runtime/doc/usr_41.txt | 1 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/vimfn.lua | 40 | ||||
-rw-r--r-- | src/nvim/eval.lua | 43 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 144 | ||||
-rw-r--r-- | src/nvim/ops.c | 141 | ||||
-rw-r--r-- | src/nvim/ops.h | 19 | ||||
-rw-r--r-- | test/old/testdir/test_visual.vim | 155 |
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 |