vim-patch:8.2.2344: using inclusive index for slice is not always desired

Problem:    Using inclusive index for slice is not always desired.
Solution:   Add the slice() method, which has an exclusive index. (closes
            vim/vim#7408)

6601b62943

Cherry-pick a line in docs added later.

Co-authored-by: Bram Moolenaar <Bram@vim.org>
This commit is contained in:
zeertzjq 2023-05-04 16:46:38 +08:00
parent 62351ff3d2
commit b441dafdf5
8 changed files with 92 additions and 35 deletions

View File

@ -464,6 +464,8 @@ sign_unplacelist({list}) List unplace a list of signs
simplify({filename}) String simplify filename as much as possible
sin({expr}) Float sine of {expr}
sinh({expr}) Float hyperbolic sine of {expr}
slice({expr}, {start} [, {end}]) String, List or Blob
slice of a String, List or Blob
sockconnect({mode}, {address} [, {opts}])
Number Connects to socket
sort({list} [, {func} [, {dict}]])
@ -7773,6 +7775,18 @@ sinh({expr}) *sinh()*
Can also be used as a |method|: >
Compute()->sinh()
slice({expr}, {start} [, {end}]) *slice()*
Similar to using a |slice| "expr[start : end]", but "end" is
used exclusive. And for a string the indexes are used as
character indexes instead of byte indexes.
When {end} is omitted the slice continues to the last item.
When {end} is -1 the last item is omitted.
Returns an empty value if {start} or {end} are invalid.
Can also be used as a |method|: >
GetList()->slice(offset)
sockconnect({mode}, {address} [, {opts}]) *sockconnect()*
Connect a socket to an address. If {mode} is "pipe" then
{address} should be the path of a local domain socket (on

View File

@ -270,6 +270,9 @@ similar to -1. >
:let shortlist = mylist[2:2] " List with one item: [3]
:let otherlist = mylist[:] " make a copy of the List
Notice that the last index is inclusive. If you prefer using an exclusive
index use the |slice()| method.
If the first index is beyond the last item of the List or the second item is
before the first item, the result is an empty list. There is no error
message.
@ -1178,6 +1181,9 @@ In legacy Vim script the indexes are byte indexes. This doesn't recognize
multibyte encodings, see |byteidx()| for computing the indexes. If expr8 is
a Number it is first converted to a String.
The item at index expr1b is included, it is inclusive. For an exclusive index
use the |slice()| function.
If expr1a is omitted zero is used. If expr1b is omitted the length of the
string minus one is used.

View File

@ -630,6 +630,8 @@ String manipulation: *string-functions*
submatch() get a specific match in ":s" and substitute()
strpart() get part of a string using byte index
strcharpart() get part of a string using char index
slice() take a slice of a string, using char index in
Vim9 script
strgetchar() get character from a string using char index
expand() expand special keywords
expandcmd() expand a command like done for `:edit`
@ -659,6 +661,7 @@ List manipulation: *list-functions*
filter() remove selected items from a List
map() change each List item
reduce() reduce a List to a value
slice() take a slice of a List
sort() sort a List
reverse() reverse the order of a List or Blob
uniq() remove copies of repeated adjacent items

View File

@ -1692,7 +1692,7 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool
lp->ll_n2 = tv_blob_len(lp->ll_blob) - 1;
}
if (tv_blob_set_range(lp->ll_blob, (int)lp->ll_n1, (int)lp->ll_n2, rettv) == FAIL) {
if (tv_blob_set_range(lp->ll_blob, lp->ll_n1, lp->ll_n2, rettv) == FAIL) {
return;
}
} else {
@ -3542,7 +3542,7 @@ static int eval_index(char **arg, typval_T *rettv, evalarg_T *const evalarg, boo
if (evaluate) {
int res = eval_index_inner(rettv, range,
empty1 ? NULL : &var1, empty2 ? NULL : &var2,
empty1 ? NULL : &var1, empty2 ? NULL : &var2, false,
key, keylen, verbose);
if (!empty1) {
tv_clear(&var1);
@ -3592,17 +3592,31 @@ static int check_can_index(typval_T *rettv, bool evaluate, bool verbose)
return OK;
}
/// slice() function
void f_slice(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
if (check_can_index(argvars, true, false) == OK) {
tv_copy(argvars, rettv);
eval_index_inner(rettv, true, argvars + 1,
argvars[2].v_type == VAR_UNKNOWN ? NULL : argvars + 2,
true, NULL, 0, false);
}
}
/// Apply index or range to "rettv".
/// "var1" is the first index, NULL for [:expr].
/// "var2" is the second index, NULL for [expr] and [expr: ]
///
/// @param var1 the first index, NULL for [:expr].
/// @param var2 the second index, NULL for [expr] and [expr: ]
/// @param exclusive true for slice(): second index is exclusive, use character
/// index for string.
/// Alternatively, "key" is not NULL, then key[keylen] is the dict index.
static int eval_index_inner(typval_T *rettv, bool is_range, typval_T *var1, typval_T *var2,
const char *key, ptrdiff_t keylen, bool verbose)
bool exclusive, const char *key, ptrdiff_t keylen, bool verbose)
{
int n1 = 0;
int n2 = 0;
varnumber_T n1 = 0;
varnumber_T n2 = 0;
if (var1 != NULL && rettv->v_type != VAR_DICT) {
n1 = (int)tv_get_number(var1);
n1 = tv_get_number(var1);
}
if (is_range) {
@ -3612,10 +3626,10 @@ static int eval_index_inner(typval_T *rettv, bool is_range, typval_T *var1, typv
}
return FAIL;
}
if (var2 == NULL) {
n2 = -1;
if (var2 != NULL) {
n2 = tv_get_number(var2);
} else {
n2 = (int)tv_get_number(var2);
n2 = VARNUMBER_MAX;
}
}
@ -3632,7 +3646,13 @@ static int eval_index_inner(typval_T *rettv, bool is_range, typval_T *var1, typv
const char *const s = tv_get_string(rettv);
char *v;
int len = (int)strlen(s);
if (is_range) {
if (exclusive) {
if (is_range) {
v = string_slice(s, n1, n2, exclusive);
} else {
v = char_from_string(s, n1);
}
} else if (is_range) {
// The resulting variable is a substring. If the indexes
// are out of range the result is empty.
if (n1 < 0) {
@ -3646,6 +3666,9 @@ static int eval_index_inner(typval_T *rettv, bool is_range, typval_T *var1, typv
} else if (n2 >= len) {
n2 = len;
}
if (exclusive) {
n2--;
}
if (n1 >= len || n2 < 0 || n1 > n2) {
v = NULL;
} else {
@ -3680,7 +3703,10 @@ static int eval_index_inner(typval_T *rettv, bool is_range, typval_T *var1, typv
if (n2 < 0) {
n2 = len + n2;
} else if (n2 >= len) {
n2 = len - 1;
n2 = len - (exclusive ? 0 : 1);
}
if (exclusive) {
n2--;
}
if (n1 >= len || n2 < 0 || n1 > n2) {
tv_clear(rettv);
@ -3688,10 +3714,10 @@ static int eval_index_inner(typval_T *rettv, bool is_range, typval_T *var1, typv
rettv->vval.v_blob = NULL;
} else {
blob_T *const blob = tv_blob_alloc();
ga_grow(&blob->bv_ga, n2 - n1 + 1);
blob->bv_ga.ga_len = n2 - n1 + 1;
for (int i = n1; i <= n2; i++) {
tv_blob_set(blob, i - n1, tv_blob_get(rettv->vval.v_blob, i));
ga_grow(&blob->bv_ga, (int)(n2 - n1 + 1));
blob->bv_ga.ga_len = (int)(n2 - n1 + 1);
for (int i = (int)n1; i <= (int)n2; i++) {
tv_blob_set(blob, i - (int)n1, tv_blob_get(rettv->vval.v_blob, i));
}
tv_clear(rettv);
tv_blob_set_ret(rettv, blob);
@ -3703,7 +3729,7 @@ static int eval_index_inner(typval_T *rettv, bool is_range, typval_T *var1, typv
n1 = len + n1;
}
if (n1 < len && n1 >= 0) {
const int v = (int)tv_blob_get(rettv->vval.v_blob, n1);
const int v = (int)tv_blob_get(rettv->vval.v_blob, (int)n1);
tv_clear(rettv);
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = v;
@ -3718,10 +3744,10 @@ static int eval_index_inner(typval_T *rettv, bool is_range, typval_T *var1, typv
n1 = 0;
}
if (var2 == NULL) {
n2 = -1;
n2 = VARNUMBER_MAX;
}
if (tv_list_slice_or_index(rettv->vval.v_list,
is_range, n1, n2, rettv, verbose) == FAIL) {
is_range, n1, n2, exclusive, rettv, verbose) == FAIL) {
return FAIL;
}
break;
@ -7266,7 +7292,7 @@ int check_luafunc_name(const char *const str, const bool paren)
/// Return the character "str[index]" where "index" is the character index. If
/// "index" is out of range NULL is returned.
char *char_from_string(char *str, varnumber_T index)
char *char_from_string(const char *str, varnumber_T index)
{
size_t nbyte = 0;
varnumber_T nchar = index;
@ -7290,7 +7316,7 @@ char *char_from_string(char *str, varnumber_T index)
/// If going over the end return "str_len".
/// If "idx" is negative count from the end, -1 is the last character.
/// When going over the start return -1.
static ssize_t char_idx2byte(char *str, size_t str_len, varnumber_T idx)
static ssize_t char_idx2byte(const char *str, size_t str_len, varnumber_T idx)
{
varnumber_T nchar = idx;
size_t nbyte = 0;
@ -7315,8 +7341,11 @@ static ssize_t char_idx2byte(char *str, size_t str_len, varnumber_T idx)
}
/// Return the slice "str[first:last]" using character indexes.
///
/// @param exclusive true for slice().
///
/// Return NULL when the result is empty.
char *string_slice(char *str, varnumber_T first, varnumber_T last)
char *string_slice(const char *str, varnumber_T first, varnumber_T last, bool exclusive)
{
if (str == NULL) {
return NULL;
@ -7327,11 +7356,11 @@ char *string_slice(char *str, varnumber_T first, varnumber_T last)
start_byte = 0; // first index very negative: use zero
}
ssize_t end_byte;
if (last == -1) {
if ((last == -1 && !exclusive) || last == VARNUMBER_MAX) {
end_byte = (ssize_t)slen;
} else {
end_byte = char_idx2byte(str, slen, last);
if (end_byte >= 0 && end_byte < (ssize_t)slen) {
if (!exclusive && end_byte >= 0 && end_byte < (ssize_t)slen) {
// end index is inclusive
end_byte += utf_ptr2len(str + end_byte);
}

View File

@ -371,6 +371,7 @@ return {
simplify={args=1, base=1},
sin={args=1, base=1, float_func="sin"},
sinh={args=1, base=1, float_func="sinh"},
slice={args={2, 3}, base=1},
sockconnect={args={2,3}},
sort={args={1, 3}, base=1},
soundfold={args=1, base=1},

View File

@ -765,10 +765,10 @@ int tv_list_concat(list_T *const l1, list_T *const l2, typval_T *const tv)
return OK;
}
static list_T *tv_list_slice(list_T *ol, int n1, int n2)
static list_T *tv_list_slice(list_T *ol, varnumber_T n1, varnumber_T n2)
{
list_T *l = tv_list_alloc(n2 - n1 + 1);
listitem_T *item = tv_list_find(ol, n1);
listitem_T *item = tv_list_find(ol, (int)n1);
for (; n1 <= n2; n1++) {
tv_list_append_tv(l, TV_LIST_ITEM_TV(item));
item = TV_LIST_ITEM_NEXT(rettv->vval.v_list, item);
@ -776,12 +776,12 @@ static list_T *tv_list_slice(list_T *ol, int n1, int n2)
return l;
}
int tv_list_slice_or_index(list_T *list, bool range, int n1_arg, int n2_arg, typval_T *rettv,
bool verbose)
int tv_list_slice_or_index(list_T *list, bool range, varnumber_T n1_arg, varnumber_T n2_arg,
bool exclusive, typval_T *rettv, bool verbose)
{
int len = tv_list_len(rettv->vval.v_list);
int n1 = n1_arg;
int n2 = n2_arg;
varnumber_T n1 = n1_arg;
varnumber_T n2 = n2_arg;
if (n1 < 0) {
n1 = len + n1;
@ -801,7 +801,10 @@ int tv_list_slice_or_index(list_T *list, bool range, int n1_arg, int n2_arg, typ
if (n2 < 0) {
n2 = len + n2;
} else if (n2 >= len) {
n2 = len - 1;
n2 = len - (exclusive ? 0 : 1);
}
if (exclusive) {
n2--;
}
if (n2 < 0 || n2 + 1 < n1) {
n2 = -1;
@ -2800,14 +2803,14 @@ int tv_blob_check_range(int bloblen, varnumber_T n1, varnumber_T n2, bool quiet)
/// Set bytes "n1" to "n2" (inclusive) in "dest" to the value of "src".
/// Caller must make sure "src" is a blob.
/// Returns FAIL if the number of bytes does not match.
int tv_blob_set_range(blob_T *dest, int n1, int n2, typval_T *src)
int tv_blob_set_range(blob_T *dest, varnumber_T n1, varnumber_T n2, typval_T *src)
{
if (n2 - n1 + 1 != tv_blob_len(src->vval.v_blob)) {
emsg(_("E972: Blob value does not have the right number of bytes"));
return FAIL;
}
for (int il = n1, ir = 0; il <= n2; il++) {
for (int il = (int)n1, ir = 0; il <= (int)n2; il++) {
tv_blob_set(dest, il, tv_blob_get(src->vval.v_blob, ir++));
}
return OK;

View File

@ -17,6 +17,7 @@ hashpipe:write([[
#include "nvim/cmdexpand.h"
#include "nvim/cmdhist.h"
#include "nvim/digraph.h"
#include "nvim/eval.h"
#include "nvim/eval/buffer.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"

View File

@ -935,7 +935,7 @@ func Test_visual_block_mode()
endfunc
func Test_visual_force_motion_feedkeys()
onoremap <expr> i- execute('let g:mode = mode(1)')
onoremap <expr> i- execute('let g:mode = mode(1)')->slice(0, 0)
call feedkeys('dvi-', 'x')
call assert_equal('nov', g:mode)
call feedkeys('di-', 'x')