vim-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: vim/vim#14609
closes: vim/vim#14617

b4757e627e

Co-authored-by: Shougo Matsushita <Shougo.Matsu@gmail.com>
Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
This commit is contained in:
zeertzjq 2024-05-20 06:15:58 +08:00
parent 5c2616846a
commit d89144626e
6 changed files with 299 additions and 71 deletions

View File

@ -2969,6 +2969,23 @@ getregion({pos1}, {pos2} [, {opts}]) *getregion()*
\ getpos('v'), getpos('.'), #{ type: mode() })<CR>
<
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.
getregtype([{regname}]) *getregtype()*
The result is a String, which is type of register {regname}.
The value will be one of:

View File

@ -794,6 +794,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
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

View File

@ -3581,6 +3581,28 @@ function vim.fn.getreginfo(regname) end
--- @return string[]
function vim.fn.getregion(pos1, pos2, opts) end
--- 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.
---
--- @param pos1 table
--- @param pos2 table
--- @param opts? table
--- @return integer[][][]
function vim.fn.getregionpos(pos1, pos2, opts) end
--- The result is a String, which is type of register {regname}.
--- The value will be one of:
--- "v" for |charwise| text

View File

@ -4414,6 +4414,31 @@ M.funcs = {
returns = 'string[]',
signature = 'getregion({pos1}, {pos2} [, {opts}])',
},
getregionpos = {
args = { 2, 3 },
base = 1,
desc = [=[
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.
]=],
name = 'getregionpos',
params = { { 'pos1', 'table' }, { 'pos2', 'table' }, { 'opts', 'table' } },
returns = 'integer[][][]',
signature = 'getregionpos({pos1}, {pos2} [, {opts}])',
},
getregtype = {
args = { 0, 1 },
base = 1,

View File

@ -2823,24 +2823,25 @@ static char *block_def2str(struct block_def *bd)
return ret;
}
/// "getregion()" function
static void f_getregion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
static int getregionpos(typval_T *argvars, typval_T *rettv, pos_T *p1, pos_T *p2,
bool *const inclusive, MotionType *region_type, oparg_T *oa,
int *const fnum)
FUNC_ATTR_NONNULL_ALL
{
tv_list_alloc_ret(rettv, kListLenMayKnow);
if (tv_check_for_list_arg(argvars, 0) == FAIL
|| tv_check_for_list_arg(argvars, 1) == FAIL
|| tv_check_for_opt_dict_arg(argvars, 2) == FAIL) {
return;
return FAIL;
}
int fnum1 = -1;
int fnum2 = -1;
pos_T p1, p2;
if (list2fpos(&argvars[0], &p1, &fnum1, NULL, false) != OK
|| list2fpos(&argvars[1], &p2, &fnum2, NULL, false) != OK
if (list2fpos(&argvars[0], p1, &fnum1, NULL, false) != OK
|| list2fpos(&argvars[1], p2, &fnum2, NULL, false) != OK
|| fnum1 != fnum2) {
return;
return FAIL;
}
bool is_select_exclusive;
@ -2858,108 +2859,123 @@ static void f_getregion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
type = default_type;
}
MotionType region_type = kMTUnknown;
if (type[0] == 'v' && type[1] == NUL) {
region_type = kMTCharWise;
*region_type = kMTCharWise;
} else if (type[0] == 'V' && type[1] == NUL) {
region_type = kMTLineWise;
*region_type = kMTLineWise;
} else if (type[0] == Ctrl_V && type[1] == NUL) {
region_type = kMTBlockWise;
*region_type = kMTBlockWise;
} else {
semsg(_(e_invargNval), "type", type);
return;
return FAIL;
}
buf_T *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;
return FAIL;
}
if (p1.lnum < 1 || p1.lnum > findbuf->b_ml.ml_line_count) {
semsg(_(e_invalid_line_number_nr), p1.lnum);
return;
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;
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;
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;
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;
}
buf_T *const save_curbuf = curbuf;
curbuf = findbuf;
curwin->w_buffer = curbuf;
const TriState save_virtual = virtual_op;
virtual_op = virtual_active(curwin);
// NOTE: Adjust is needed.
p1.col--;
p2.col--;
// NOTE: Adjustment is needed.
p1->col--;
p2->col--;
if (!lt(p1, p2)) {
if (!lt(*p1, *p2)) {
// swap position
pos_T p = p1;
p1 = p2;
p2 = p;
pos_T p = *p1;
*p1 = *p2;
*p2 = p;
}
oparg_T oa;
bool inclusive = true;
if (region_type == kMTCharWise) {
if (*region_type == kMTCharWise) {
// handle 'selection' == "exclusive"
if (is_select_exclusive && !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 = ml_get_len(p2.lnum);
if (p2.col > 0) {
p2.col--;
mark_mb_adjustpos(curbuf, &p2);
if (is_select_exclusive && !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 = ml_get_len(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;
if (*ml_get_pos(p2) == NUL && !virtual_op) {
*inclusive = false;
}
} else if (region_type == kMTBlockWise) {
} else if (*region_type == kMTBlockWise) {
colnr_T sc1, ec1, sc2, ec2;
getvvcol(curwin, &p1, &sc1, NULL, &ec1);
getvvcol(curwin, &p2, &sc2, NULL, &ec2);
oa.motion_type = kMTBlockWise;
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 = kMTBlockWise;
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.
int l = utfc_ptr2len(ml_get_pos(&p2));
int l = utfc_ptr2len(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, EvalFuncData fptr)
{
buf_T *const save_curbuf = curbuf;
const TriState save_virtual = virtual_op;
pos_T p1, p2;
bool inclusive = true;
MotionType region_type = kMTUnknown;
oparg_T oa;
int fnum;
if (getregionpos(argvars, rettv,
&p1, &p2, &inclusive, &region_type, &oa, &fnum) == FAIL) {
return;
}
for (linenr_T lnum = p1.lnum; lnum <= p2.lnum; lnum++) {
@ -2983,6 +2999,80 @@ static void f_getregion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tv_list_append_allocated_string(rettv->vval.v_list, akt);
}
// 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 = tv_list_alloc(2);
tv_list_append_list(rettv->vval.v_list, l1);
list_T *l2 = tv_list_alloc(4);
tv_list_append_list(l1, l2);
list_T *l3 = tv_list_alloc(4);
tv_list_append_list(l1, l3);
buf_T *findbuf = bufnr != 0 ? buflist_findnr(bufnr) : curbuf;
int max_col1 = ml_get_buf_len(findbuf, lnum1);
tv_list_append_number(l2, bufnr);
tv_list_append_number(l2, lnum1);
tv_list_append_number(l2, col1 > max_col1 ? max_col1 : col1);
tv_list_append_number(l2, coladd1);
int max_col2 = ml_get_buf_len(findbuf, lnum2);
tv_list_append_number(l3, bufnr);
tv_list_append_number(l3, lnum2);
tv_list_append_number(l3, col2 > max_col2 ? max_col2 : col2);
tv_list_append_number(l3, coladd2);
}
/// "getregionpos()" function
static void f_getregionpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
buf_T *const save_curbuf = curbuf;
const TriState save_virtual = virtual_op;
pos_T p1, p2;
bool inclusive = true;
MotionType region_type = kMTUnknown;
oparg_T oa;
int fnum;
if (getregionpos(argvars, rettv,
&p1, &p2, &inclusive, &region_type, &oa, &fnum) == FAIL) {
return;
}
for (linenr_T lnum = p1.lnum; lnum <= p2.lnum; lnum++) {
int start_col, end_col;
if (region_type == kMTLineWise) {
start_col = 1;
end_col = MAXCOL;
} else if (region_type == kMTBlockWise) {
struct block_def bd;
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;

View File

@ -1646,16 +1646,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)
@ -1750,9 +1778,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
@ -1763,13 +1795,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>"]
@ -1792,6 +1831,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:')
@ -1823,6 +1864,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"],
@ -1835,7 +1882,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«'],
@ -1962,4 +2009,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