0
0
mirror of https://github.com/vim/vim.git synced 2025-07-26 11:04:33 -04:00

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: #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>
This commit is contained in:
Shougo Matsushita 2024-02-21 00:02:45 +01:00 committed by Christian Brabandt
parent f865895c87
commit 3f905ab3c4
No known key found for this signature in database
GPG Key ID: F3F92DA383FDDE09
11 changed files with 474 additions and 68 deletions

View File

@ -263,6 +263,8 @@ getqflist({what}) Dict get specific quickfix list properties
getreg([{regname} [, 1 [, {list}]]])
String or List contents of a register
getreginfo([{regname}]) Dict information about a register
getregion({pos1}, {pos2}, {type})
List get the text from {pos1} to {pos2}
getregtype([{regname}]) String type of a register
getscriptinfo([{opts}]) List list of sourced scripts
gettabinfo([{expr}]) List list of tab pages
@ -4266,6 +4268,43 @@ getreginfo([{regname}]) *getreginfo()*
Can also be used as a |method|: >
GetRegname()->getreginfo()
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 |characterwise| 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 |characterwise-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>echo getregion('v', '.', mode())<CR>
<
Can also be used as a |method|: >
'.'->getregion("'a', 'v')
<
getregtype([{regname}]) *getregtype()*
The result is a String, which is type of register {regname}.
The value will be one of:

View File

@ -7760,6 +7760,7 @@ getqflist() builtin.txt /*getqflist()*
getqflist-examples quickfix.txt /*getqflist-examples*
getreg() builtin.txt /*getreg()*
getreginfo() builtin.txt /*getreginfo()*
getregion() builtin.txt /*getregion()*
getregtype() builtin.txt /*getregtype()*
getscript pi_getscript.txt /*getscript*
getscript-autoinstall pi_getscript.txt /*getscript-autoinstall*

View File

@ -1,4 +1,4 @@
*usr_41.txt* For Vim version 9.1. Last change: 2024 Feb 01
*usr_41.txt* For Vim version 9.1. Last change: 2024 Feb 20
VIM USER MANUAL - by Bram Moolenaar
@ -929,6 +929,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

View File

@ -41560,6 +41560,7 @@ Functions: ~
|foreach()| apply function to List items
|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
Autocommands: ~

View File

@ -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,
@ -5452,6 +5455,186 @@ f_getpos(typval_T *argvars, typval_T *rettv)
getpos_both(argvars, rettv, FALSE, FALSE);
}
/*
* 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.

View File

@ -2414,6 +2414,84 @@ block_prep(
#endif
}
/*
* 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.
*/

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
120,
/**/
119,
/**/