From b4757e627e6c83d1c8e5535d4887a82d6a5efdd0 Mon Sep 17 00:00:00 2001 From: Shougo Matsushita Date: Tue, 7 May 2024 20:49:24 +0200 Subject: 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: #14609 closes: #14617 Co-authored-by: Justin M. Keyes Signed-off-by: Shougo Matsushita Signed-off-by: Christian Brabandt --- src/evalfunc.c | 314 ++++++++++++++++++++++++++++---------- src/testdir/test_vim9_builtin.vim | 22 ++- src/testdir/test_visual.vim | 77 +++++++++- src/version.c | 2 + 4 files changed, 326 insertions(+), 89 deletions(-) (limited to 'src') diff --git a/src/evalfunc.c b/src/evalfunc.c index 7c7a202439..cca7e2ca4c 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -73,6 +73,7 @@ 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_getregionpos(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); @@ -2136,6 +2137,8 @@ static funcentry_T global_functions[] = ret_dict_any, f_getreginfo}, {"getregion", 2, 3, FEARG_1, arg3_list_list_dict, ret_list_string, f_getregion}, + {"getregionpos", 2, 3, FEARG_1, arg3_list_list_dict, + ret_list_string, f_getregionpos}, {"getregtype", 0, 1, FEARG_1, arg1_string, ret_string, f_getregtype}, {"getscriptinfo", 0, 1, 0, arg1_dict_any, @@ -5481,40 +5484,35 @@ block_def2str(struct block_def *bd) return ret; } -/* - * "getregion()" function - */ - static void -f_getregion(typval_T *argvars, typval_T *rettv) -{ - linenr_T lnum; - oparg_T oa; - struct block_def bd; - char_u *akt = NULL; - int inclusive = TRUE; - int fnum1 = -1, fnum2 = -1; - pos_T p1, p2; - char_u *type; - buf_T *save_curbuf; - buf_T *findbuf; - char_u default_type[] = "v"; - int save_virtual; - int l; - int region_type = -1; - int is_select_exclusive; + static int +getregionpos( + typval_T *argvars, + typval_T *rettv, + pos_T *p1, pos_T *p2, + int *inclusive, + int *region_type, + oparg_T *oa, + int *fnum) +{ + int fnum1 = -1, fnum2 = -1; + char_u *type; + buf_T *findbuf; + char_u default_type[] = "v"; + int is_select_exclusive; + int l; if (rettv_list_alloc(rettv) == FAIL) - return; + return FAIL; if (check_for_list_arg(argvars, 0) == FAIL || check_for_list_arg(argvars, 1) == FAIL || check_for_opt_dict_arg(argvars, 2) == FAIL) - return; + return FAIL; - 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; if (argvars[2].v_type == VAR_DICT) { @@ -5532,125 +5530,152 @@ f_getregion(typval_T *argvars, typval_T *rettv) } if (type[0] == 'v' && type[1] == NUL) - region_type = MCHAR; + *region_type = MCHAR; else if (type[0] == 'V' && type[1] == NUL) - region_type = MLINE; + *region_type = MLINE; else if (type[0] == Ctrl_V && type[1] == NUL) - region_type = MBLOCK; + *region_type = MBLOCK; else { semsg(_(e_invalid_value_for_argument_str_str), "type", type); - return; + return FAIL; } 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) + if (p1->lnum < 1 || p1->lnum > findbuf->b_ml.ml_line_count) { - semsg(_(e_invalid_line_number_nr), p1.lnum); - return; + 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) + 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; + semsg(_(e_invalid_column_number_nr), p1->col); + return FAIL; } - if (p2.lnum < 1 || p2.lnum > findbuf->b_ml.ml_line_count) + if (p2->lnum < 1 || p2->lnum > findbuf->b_ml.ml_line_count) { - semsg(_(e_invalid_line_number_nr), p2.lnum); - return; + 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) + 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; + semsg(_(e_invalid_column_number_nr), p2->col); + return FAIL; } - save_curbuf = curbuf; curbuf = findbuf; curwin->w_buffer = curbuf; - save_virtual = virtual_op; virtual_op = virtual_active(); - // NOTE: Adjust is needed. - p1.col--; - p2.col--; + // NOTE: Adjustment is needed. + p1->col--; + p2->col--; - if (!LT_POS(p1, p2)) + if (!LT_POS(*p1, *p2)) { // swap position pos_T p; - p = p1; - p1 = p2; - p2 = p; + p = *p1; + *p1 = *p2; + *p2 = p; } - if (region_type == MCHAR) + if (*region_type == MCHAR) { // handle 'selection' == "exclusive" - if (is_select_exclusive && !EQUAL_POS(p1, p2)) + if (is_select_exclusive && !EQUAL_POS(*p1, *p2)) { - if (p2.coladd > 0) - p2.coladd--; - else if (p2.col > 0) + if (p2->coladd > 0) + p2->coladd--; + else if (p2->col > 0) { - p2.col--; + p2->col--; - mb_adjustpos(curbuf, &p2); + mb_adjustpos(curbuf, p2); } - else if (p2.lnum > 1) + else if (p2->lnum > 1) { - p2.lnum--; - p2.col = ml_get_len(p2.lnum); - if (p2.col > 0) + p2->lnum--; + p2->col = ml_get_len(p2->lnum); + if (p2->col > 0) { - p2.col--; + p2->col--; - mb_adjustpos(curbuf, &p2); + 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 == MBLOCK) + else if (*region_type == MBLOCK) { colnr_T sc1, ec1, sc2, ec2; - getvvcol(curwin, &p1, &sc1, NULL, &ec1); - getvvcol(curwin, &p2, &sc2, NULL, &ec2); - oa.motion_type = MBLOCK; - 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 = MBLOCK; + 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. - l = utfc_ptr2len((char_u *)ml_get_pos(&p2)); + l = mb_ptr2len((char_u *)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) +{ + pos_T p1, p2; + int inclusive = TRUE; + int region_type = -1; + oparg_T oa; + int fnum; + + buf_T *save_curbuf; + int save_virtual; + char_u *akt = NULL; + linenr_T lnum; + + save_curbuf = curbuf; + save_virtual = virtual_op; + + if (getregionpos(argvars, rettv, + &p1, &p2, &inclusive, ®ion_type, &oa, &fnum) == FAIL) + return; for (lnum = p1.lnum; lnum <= p2.lnum; lnum++) { int ret = 0; + struct block_def bd; if (region_type == MLINE) akt = vim_strsave(ml_get(lnum)); @@ -5681,6 +5706,127 @@ f_getregion(typval_T *argvars, typval_T *rettv) } } + // 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, *l2, *l3; + buf_T *findbuf; + int max_col1, max_col2; + + l1 = list_alloc(); + if (l1 == NULL) + return; + + if (list_append_list(rettv->vval.v_list, l1) == FAIL) + { + vim_free(l1); + return; + } + + l2 = list_alloc(); + if (l2 == NULL) + return; + + if (list_append_list(l1, l2) == FAIL) + { + vim_free(l2); + return; + } + + l3 = list_alloc(); + if (l3 == NULL) + return; + + if (list_append_list(l1, l3) == FAIL) + { + vim_free(l3); + return; + } + + findbuf = bufnr != 0 ? buflist_findnr(bufnr) : curbuf; + + max_col1 = ml_get_buf_len(findbuf, lnum1); + list_append_number(l2, bufnr); + list_append_number(l2, lnum1); + list_append_number(l2, col1 > max_col1 ? max_col1 : col1); + list_append_number(l2, coladd1); + + max_col2 = ml_get_buf_len(findbuf, lnum2); + list_append_number(l3, bufnr); + list_append_number(l3, lnum2); + list_append_number(l3, col2 > max_col2 ? max_col2 : col2); + list_append_number(l3, coladd2); +} + +/* + * "getregionpos()" function + */ + static void +f_getregionpos(typval_T *argvars, typval_T *rettv) +{ + pos_T p1, p2; + int inclusive = TRUE; + int region_type = -1; + oparg_T oa; + int fnum; + int lnum; + + buf_T *save_curbuf; + int save_virtual; + + save_curbuf = curbuf; + save_virtual = virtual_op; + + if (getregionpos(argvars, rettv, + &p1, &p2, &inclusive, ®ion_type, &oa, &fnum) == FAIL) + return; + + for (lnum = p1.lnum; lnum <= p2.lnum; lnum++) + { + struct block_def bd; + int start_col, end_col; + + if (region_type == MLINE) + { + start_col = 1; + end_col = MAXCOL; + } + else if (region_type == MBLOCK) + { + 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; diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim index 83153ad085..7a830189c5 100644 --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -5181,10 +5181,26 @@ enddef def Test_getregion() assert_equal(['x'], getregion(getpos('.'), getpos('.'))->map((_, _) => 'x')) - - v9.CheckSourceDefAndScriptFailure(['getregion(10, getpos("."))'], ['E1013: Argument 1: type mismatch, expected list but got number', 'E1211: List required for argument 1']) - assert_equal([''], getregion(getpos('.'), getpos('.'))) + assert_equal(['x'], getregionpos(getpos('.'), getpos('.'))->map((_, _) => 'x')) + + v9.CheckSourceDefAndScriptFailure( + ['getregion(10, getpos("."))'], + ['E1013: Argument 1: type mismatch, expected list but got number', 'E1211: List required for argument 1'] + ) + v9.CheckSourceDefAndScriptFailure( + ['getregionpos(10, getpos("."))'], + ['E1013: Argument 1: type mismatch, expected list but got number', 'E1211: List required for argument 1'] + ) + assert_equal( + [''], + getregion(getpos('.'), getpos('.')) + ) + assert_equal( + [[[bufnr('%'), 1, 0, 0], [bufnr('%'), 1, 0, 0]]], + getregionpos(getpos('.'), getpos('.')) + ) v9.CheckSourceDefExecFailure(['getregion(getpos("a"), getpos("."))'], 'E1209:') + v9.CheckSourceDefExecFailure(['getregionpos(getpos("a"), getpos("."))'], 'E1209:') 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 dff6150135..9b8eed40e7 100644 --- a/src/testdir/test_visual.vim +++ b/src/testdir/test_visual.vim @@ -1642,16 +1642,44 @@ func Test_visual_getregion() call feedkeys("\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': "\" })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 2, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 2, 0]], + \ ], + \ getpos('.')->getregionpos(getpos('v'), {'type': "\" })) #" Line visual mode call cursor(1, 1) @@ -1746,9 +1774,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 @@ -1759,13 +1791,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', "\"] @@ -1788,6 +1827,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:') @@ -1819,6 +1860,12 @@ func Test_visual_getregion() call feedkeys("\\jj", 'xt') call assert_equal(['e', ' ', '5'], \ getregion(getpos('v'), getpos('.'), {'type': "\" })) + 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("\vj", 'xt') call assert_equal(['abcdefghijk«', "\U0001f1e6"], @@ -1831,7 +1878,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': "\" })) + \ getregion(getpos("'a"), getpos("'b"), {'type': "\" })) call assert_equal(['k«', '🇦«🇧«🇨'], \ getregion(getpos("'a"), getpos("'b"), {'type': 'v' })) call assert_equal(['k«'], @@ -1958,4 +2005,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 diff --git a/src/version.c b/src/version.c index 5312bfef40..128197063f 100644 --- a/src/version.c +++ b/src/version.c @@ -704,6 +704,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 394, /**/ 393, /**/ -- cgit v1.2.3