summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorShougo Matsushita <Shougo.Matsu@gmail.com>2024-02-21 00:02:45 +0100
committerChristian Brabandt <cb@256bit.org>2024-02-21 00:02:45 +0100
commit3f905ab3c4f66562f4a224bf00f49d98a0b0da91 (patch)
treeaf43d2032e101b700ec3c9ac6f9bb318f256c310 /src
parentf865895c874b0936b0563ebfef7490aac8cb8a1f (diff)
patch 9.1.0120: hard to get visual region using Vim scriptv9.1.0120
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?= <doubleloop@o2.pl> Co-authored-by: Shougo Matsushita <Shougo.Matsu@gmail.com> Signed-off-by: Shougo Matsushita <Shougo.Matsu@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
Diffstat (limited to 'src')
-rw-r--r--src/evalfunc.c187
-rw-r--r--src/ops.c78
-rw-r--r--src/proto/ops.pro1
-rw-r--r--src/register.c66
-rw-r--r--src/testdir/test_vim9_builtin.vim9
-rw-r--r--src/testdir/test_visual.vim155
-rw-r--r--src/version.c2
7 files changed, 431 insertions, 67 deletions
diff --git a/src/evalfunc.c b/src/evalfunc.c
index d13eb1c9f5..81f747f843 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,
@@ -5453,6 +5456,186 @@ f_getpos(typval_T *argvars, typval_T *rettv)
}
/*
+ * 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.
* Returns zero on error.
diff --git a/src/ops.c b/src/ops.c
index 3fefbc8a40..e66b4b5ee7 100644
--- a/src/ops.c
+++ b/src/ops.c
@@ -2415,6 +2415,84 @@ block_prep(
}
/*
+ * 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.
*/
void
diff --git a/src/proto/ops.pro b/src/proto/ops.pro
index 193e89430e..bebe63553b 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 3f1506a5d8..98015e05ba 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 aa819811cc..e2585d4f69 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 17f0fd0687..34c572e617 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\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
diff --git a/src/version.c b/src/version.c
index f3d8fd8d1d..d495165516 100644
--- 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 */
/**/
+ 120,
+/**/
119,
/**/
118,