mirror of
https://github.com/neovim/neovim.git
synced 2025-10-17 23:31:51 +00:00
Merge pull request #15211 from seandewar/blob-port
Port VimL's Blob type - vim-patch:8.1.{0735,0736,0738,0741,0742,0755,0756,0757,0765,0793,0797,0798,0802,1022,1023,1671},8.2.{0121,0184,0404,0521,0829,1473,1866,2712}
This commit is contained in:
@@ -444,6 +444,16 @@ void set_option_to(uint64_t channel_id, void *to, int type,
|
||||
#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \
|
||||
TYPVAL_ENCODE_CONV_NIL(tv)
|
||||
|
||||
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
|
||||
do { \
|
||||
const size_t len_ = (size_t)(len); \
|
||||
const blob_T *const blob_ = (blob); \
|
||||
kvi_push(edata->stack, STRING_OBJ(((String) { \
|
||||
.data = len_ != 0 ? xmemdup(blob_->bv_ga.ga_data, len_) : NULL, \
|
||||
.size = len_ \
|
||||
}))); \
|
||||
} while (0)
|
||||
|
||||
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
|
||||
do { \
|
||||
TYPVAL_ENCODE_CONV_NIL(tv); \
|
||||
@@ -584,6 +594,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata)
|
||||
#undef TYPVAL_ENCODE_CONV_STRING
|
||||
#undef TYPVAL_ENCODE_CONV_STR_STRING
|
||||
#undef TYPVAL_ENCODE_CONV_EXT_STRING
|
||||
#undef TYPVAL_ENCODE_CONV_BLOB
|
||||
#undef TYPVAL_ENCODE_CONV_NUMBER
|
||||
#undef TYPVAL_ENCODE_CONV_FLOAT
|
||||
#undef TYPVAL_ENCODE_CONV_FUNC_START
|
||||
|
@@ -516,6 +516,7 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output,
|
||||
/// @param data will be consumed
|
||||
size_t channel_send(uint64_t id, char *data, size_t len,
|
||||
bool data_owned, const char **error)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
Channel *chan = find_channel(id);
|
||||
size_t written = 0;
|
||||
|
394
src/nvim/eval.c
394
src/nvim/eval.c
@@ -69,6 +69,7 @@ static char *e_nowhitespace
|
||||
= N_("E274: No white space allowed before parenthesis");
|
||||
static char *e_invalwindow = N_("E957: Invalid window number");
|
||||
static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
|
||||
static char *e_write2 = N_("E80: Error while writing: %s");
|
||||
|
||||
// TODO(ZyX-I): move to eval/executor
|
||||
static char *e_letwrong = N_("E734: Wrong variable type for %s=");
|
||||
@@ -113,6 +114,8 @@ typedef struct {
|
||||
int fi_varcount; // nr of variables in the list
|
||||
listwatch_T fi_lw; // keep an eye on the item used.
|
||||
list_T *fi_list; // list being used
|
||||
int fi_bi; // index of blob
|
||||
blob_T *fi_blob; // blob being used
|
||||
} forinfo_T;
|
||||
|
||||
// values for vv_flags:
|
||||
@@ -227,6 +230,7 @@ static struct vimvar {
|
||||
VV(VV_TYPE_DICT, "t_dict", VAR_NUMBER, VV_RO),
|
||||
VV(VV_TYPE_FLOAT, "t_float", VAR_NUMBER, VV_RO),
|
||||
VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO),
|
||||
VV(VV_TYPE_BLOB, "t_blob", VAR_NUMBER, VV_RO),
|
||||
VV(VV_EVENT, "event", VAR_DICT, VV_RO),
|
||||
VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO),
|
||||
VV(VV_ARGV, "argv", VAR_LIST, VV_RO),
|
||||
@@ -238,6 +242,7 @@ static struct vimvar {
|
||||
VV(VV__NULL_STRING, "_null_string", VAR_STRING, VV_RO),
|
||||
VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO),
|
||||
VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO),
|
||||
VV(VV__NULL_BLOB, "_null_blob", VAR_BLOB, VV_RO),
|
||||
VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO),
|
||||
};
|
||||
#undef VV
|
||||
@@ -251,6 +256,7 @@ static struct vimvar {
|
||||
#define vv_str vv_di.di_tv.vval.v_string
|
||||
#define vv_list vv_di.di_tv.vval.v_list
|
||||
#define vv_dict vv_di.di_tv.vval.v_dict
|
||||
#define vv_blob vv_di.di_tv.vval.v_blob
|
||||
#define vv_partial vv_di.di_tv.vval.v_partial
|
||||
#define vv_tv vv_di.di_tv
|
||||
|
||||
@@ -393,6 +399,7 @@ void eval_init(void)
|
||||
set_vim_var_nr(VV_TYPE_DICT, VAR_TYPE_DICT);
|
||||
set_vim_var_nr(VV_TYPE_FLOAT, VAR_TYPE_FLOAT);
|
||||
set_vim_var_nr(VV_TYPE_BOOL, VAR_TYPE_BOOL);
|
||||
set_vim_var_nr(VV_TYPE_BLOB, VAR_TYPE_BLOB);
|
||||
|
||||
set_vim_var_bool(VV_FALSE, kBoolVarFalse);
|
||||
set_vim_var_bool(VV_TRUE, kBoolVarTrue);
|
||||
@@ -2059,18 +2066,17 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Loop until no more [idx] or .key is following.
|
||||
*/
|
||||
// Loop until no more [idx] or .key is following.
|
||||
lp->ll_tv = &v->di_tv;
|
||||
var1.v_type = VAR_UNKNOWN;
|
||||
var2.v_type = VAR_UNKNOWN;
|
||||
while (*p == '[' || (*p == '.' && lp->ll_tv->v_type == VAR_DICT)) {
|
||||
if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL)
|
||||
&& !(lp->ll_tv->v_type == VAR_DICT
|
||||
&& lp->ll_tv->vval.v_dict != NULL)) {
|
||||
if (!quiet)
|
||||
EMSG(_("E689: Can only index a List or Dictionary"));
|
||||
&& !(lp->ll_tv->v_type == VAR_DICT && lp->ll_tv->vval.v_dict != NULL)
|
||||
&& !(lp->ll_tv->v_type == VAR_BLOB && lp->ll_tv->vval.v_blob != NULL)) {
|
||||
if (!quiet) {
|
||||
EMSG(_("E689: Can only index a List, Dictionary or Blob"));
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
if (lp->ll_range) {
|
||||
@@ -2119,10 +2125,11 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
|
||||
tv_clear(&var1);
|
||||
return NULL;
|
||||
}
|
||||
if (rettv != NULL && (rettv->v_type != VAR_LIST
|
||||
|| rettv->vval.v_list == NULL)) {
|
||||
if (rettv != NULL
|
||||
&& !(rettv->v_type == VAR_LIST && rettv->vval.v_list != NULL)
|
||||
&& !(rettv->v_type == VAR_BLOB && rettv->vval.v_blob != NULL)) {
|
||||
if (!quiet) {
|
||||
EMSG(_("E709: [:] requires a List value"));
|
||||
EMSG(_("E709: [:] requires a List or Blob value"));
|
||||
}
|
||||
tv_clear(&var1);
|
||||
return NULL;
|
||||
@@ -2236,6 +2243,38 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
|
||||
|
||||
tv_clear(&var1);
|
||||
lp->ll_tv = &lp->ll_di->di_tv;
|
||||
} else if (lp->ll_tv->v_type == VAR_BLOB) {
|
||||
// Get the number and item for the only or first index of the List.
|
||||
if (empty1) {
|
||||
lp->ll_n1 = 0;
|
||||
} else {
|
||||
// Is number or string.
|
||||
lp->ll_n1 = (long)tv_get_number(&var1);
|
||||
}
|
||||
tv_clear(&var1);
|
||||
|
||||
const int bloblen = tv_blob_len(lp->ll_tv->vval.v_blob);
|
||||
if (lp->ll_n1 < 0 || lp->ll_n1 > bloblen
|
||||
|| (lp->ll_range && lp->ll_n1 == bloblen)) {
|
||||
if (!quiet) {
|
||||
EMSGN(_(e_blobidx), lp->ll_n1);
|
||||
}
|
||||
tv_clear(&var2);
|
||||
return NULL;
|
||||
}
|
||||
if (lp->ll_range && !lp->ll_empty2) {
|
||||
lp->ll_n2 = (long)tv_get_number(&var2);
|
||||
tv_clear(&var2);
|
||||
if (lp->ll_n2 < 0 || lp->ll_n2 >= bloblen || lp->ll_n2 < lp->ll_n1) {
|
||||
if (!quiet) {
|
||||
EMSGN(_(e_blobidx), lp->ll_n2);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
lp->ll_blob = lp->ll_tv->vval.v_blob;
|
||||
lp->ll_tv = NULL;
|
||||
break;
|
||||
} else {
|
||||
// Get the number and item for the only or first index of the List.
|
||||
if (empty1) {
|
||||
@@ -2329,7 +2368,50 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
|
||||
if (lp->ll_tv == NULL) {
|
||||
cc = *endp;
|
||||
*endp = NUL;
|
||||
if (op != NULL && *op != '=') {
|
||||
if (lp->ll_blob != NULL) {
|
||||
if (op != NULL && *op != '=') {
|
||||
EMSG2(_(e_letwrong), op);
|
||||
return;
|
||||
}
|
||||
if (var_check_lock(lp->ll_blob->bv_lock, lp->ll_name, TV_CSTRING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lp->ll_range && rettv->v_type == VAR_BLOB) {
|
||||
if (lp->ll_empty2) {
|
||||
lp->ll_n2 = tv_blob_len(lp->ll_blob) - 1;
|
||||
}
|
||||
|
||||
if (lp->ll_n2 - lp->ll_n1 + 1 != tv_blob_len(rettv->vval.v_blob)) {
|
||||
EMSG(_("E972: Blob value does not have the right number of bytes"));
|
||||
return;
|
||||
}
|
||||
if (lp->ll_empty2) {
|
||||
lp->ll_n2 = tv_blob_len(lp->ll_blob);
|
||||
}
|
||||
|
||||
for (int il = lp->ll_n1, ir = 0; il <= lp->ll_n2; il++) {
|
||||
tv_blob_set(lp->ll_blob, il, tv_blob_get(rettv->vval.v_blob, ir++));
|
||||
}
|
||||
} else {
|
||||
bool error = false;
|
||||
const char_u val = tv_get_number_chk(rettv, &error);
|
||||
if (!error) {
|
||||
garray_T *const gap = &lp->ll_blob->bv_ga;
|
||||
|
||||
// Allow for appending a byte. Setting a byte beyond
|
||||
// the end is an error otherwise.
|
||||
if (lp->ll_n1 < gap->ga_len || lp->ll_n1 == gap->ga_len) {
|
||||
ga_grow(&lp->ll_blob->bv_ga, 1);
|
||||
tv_blob_set(lp->ll_blob, lp->ll_n1, val);
|
||||
if (lp->ll_n1 == gap->ga_len) {
|
||||
gap->ga_len++;
|
||||
}
|
||||
}
|
||||
// error for invalid range was already given in get_lval()
|
||||
}
|
||||
}
|
||||
} else if (op != NULL && *op != '=') {
|
||||
typval_T tv;
|
||||
|
||||
if (is_const) {
|
||||
@@ -2508,19 +2590,32 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip)
|
||||
if (eval0(skipwhite(expr + 2), &tv, nextcmdp, !skip) == OK) {
|
||||
*errp = false;
|
||||
if (!skip) {
|
||||
l = tv.vval.v_list;
|
||||
if (tv.v_type != VAR_LIST) {
|
||||
EMSG(_(e_listreq));
|
||||
tv_clear(&tv);
|
||||
} else if (l == NULL) {
|
||||
// a null list is like an empty list: do nothing
|
||||
if (tv.v_type == VAR_LIST) {
|
||||
l = tv.vval.v_list;
|
||||
if (l == NULL) {
|
||||
// a null list is like an empty list: do nothing
|
||||
tv_clear(&tv);
|
||||
} else {
|
||||
// No need to increment the refcount, it's already set for
|
||||
// the list being used in "tv".
|
||||
fi->fi_list = l;
|
||||
tv_list_watch_add(l, &fi->fi_lw);
|
||||
fi->fi_lw.lw_item = tv_list_first(l);
|
||||
}
|
||||
} else if (tv.v_type == VAR_BLOB) {
|
||||
fi->fi_bi = 0;
|
||||
if (tv.vval.v_blob != NULL) {
|
||||
typval_T btv;
|
||||
|
||||
// Make a copy, so that the iteration still works when the
|
||||
// blob is changed.
|
||||
tv_blob_copy(&tv, &btv);
|
||||
fi->fi_blob = btv.vval.v_blob;
|
||||
}
|
||||
tv_clear(&tv);
|
||||
} else {
|
||||
/* No need to increment the refcount, it's already set for the
|
||||
* list being used in "tv". */
|
||||
fi->fi_list = l;
|
||||
tv_list_watch_add(l, &fi->fi_lw);
|
||||
fi->fi_lw.lw_item = tv_list_first(l);
|
||||
EMSG(_(e_listblobreq));
|
||||
tv_clear(&tv);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2542,6 +2637,19 @@ bool next_for_item(void *fi_void, char_u *arg)
|
||||
{
|
||||
forinfo_T *fi = (forinfo_T *)fi_void;
|
||||
|
||||
if (fi->fi_blob != NULL) {
|
||||
if (fi->fi_bi >= tv_blob_len(fi->fi_blob)) {
|
||||
return false;
|
||||
}
|
||||
typval_T tv;
|
||||
tv.v_type = VAR_NUMBER;
|
||||
tv.v_lock = VAR_FIXED;
|
||||
tv.vval.v_number = tv_blob_get(fi->fi_blob, fi->fi_bi);
|
||||
fi->fi_bi++;
|
||||
return ex_let_vars(arg, &tv, true,
|
||||
fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK;
|
||||
}
|
||||
|
||||
listitem_T *item = fi->fi_lw.lw_item;
|
||||
if (item == NULL) {
|
||||
return false;
|
||||
@@ -2565,6 +2673,9 @@ void free_for_info(void *fi_void)
|
||||
tv_list_watch_remove(fi->fi_list, &fi->fi_lw);
|
||||
tv_list_unref(fi->fi_list);
|
||||
}
|
||||
if (fi != NULL && fi->fi_blob != NULL) {
|
||||
tv_blob_unref(fi->fi_blob);
|
||||
}
|
||||
xfree(fi);
|
||||
}
|
||||
|
||||
@@ -2974,7 +3085,7 @@ static int do_lock_var(lval_T *lp, char_u *name_end FUNC_ATTR_UNUSED,
|
||||
} else {
|
||||
di->di_flags &= ~DI_FLAGS_LOCK;
|
||||
}
|
||||
tv_item_lock(&di->di_tv, deep, lock);
|
||||
tv_item_lock(&di->di_tv, deep, lock, false);
|
||||
}
|
||||
}
|
||||
} else if (lp->ll_range) {
|
||||
@@ -2982,16 +3093,16 @@ static int do_lock_var(lval_T *lp, char_u *name_end FUNC_ATTR_UNUSED,
|
||||
|
||||
// (un)lock a range of List items.
|
||||
while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) {
|
||||
tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock);
|
||||
tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock, false);
|
||||
li = TV_LIST_ITEM_NEXT(lp->ll_list, li);
|
||||
lp->ll_n1++;
|
||||
}
|
||||
} else if (lp->ll_list != NULL) {
|
||||
// (un)lock a List item.
|
||||
tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock);
|
||||
tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock, false);
|
||||
} else {
|
||||
// (un)lock a Dictionary item.
|
||||
tv_item_lock(&lp->ll_di->di_tv, deep, lock);
|
||||
tv_item_lock(&lp->ll_di->di_tv, deep, lock, false);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -3607,7 +3718,7 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate)
|
||||
if (op != '+' && op != '-' && op != '.')
|
||||
break;
|
||||
|
||||
if ((op != '+' || rettv->v_type != VAR_LIST)
|
||||
if ((op != '+' || (rettv->v_type != VAR_LIST && rettv->v_type != VAR_BLOB))
|
||||
&& (op == '.' || rettv->v_type != VAR_FLOAT)) {
|
||||
// For "list + ...", an illegal use of the first operand as
|
||||
// a number cannot be determined before evaluating the 2nd
|
||||
@@ -3653,6 +3764,21 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate)
|
||||
tv_clear(rettv);
|
||||
rettv->v_type = VAR_STRING;
|
||||
rettv->vval.v_string = p;
|
||||
} else if (op == '+' && rettv->v_type == VAR_BLOB
|
||||
&& var2.v_type == VAR_BLOB) {
|
||||
const blob_T *const b1 = rettv->vval.v_blob;
|
||||
const blob_T *const b2 = var2.vval.v_blob;
|
||||
blob_T *const b = tv_blob_alloc();
|
||||
|
||||
for (int i = 0; i < tv_blob_len(b1); i++) {
|
||||
ga_append(&b->bv_ga, tv_blob_get(b1, i));
|
||||
}
|
||||
for (int i = 0; i < tv_blob_len(b2); i++) {
|
||||
ga_append(&b->bv_ga, tv_blob_get(b2, i));
|
||||
}
|
||||
|
||||
tv_clear(rettv);
|
||||
tv_blob_set_ret(rettv, b);
|
||||
} else if (op == '+' && rettv->v_type == VAR_LIST
|
||||
&& var2.v_type == VAR_LIST) {
|
||||
// Concatenate Lists.
|
||||
@@ -3673,10 +3799,12 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate)
|
||||
} else {
|
||||
n1 = tv_get_number_chk(rettv, &error);
|
||||
if (error) {
|
||||
/* This can only happen for "list + non-list". For
|
||||
* "non-list + ..." or "something - ...", we returned
|
||||
* before evaluating the 2nd operand. */
|
||||
// This can only happen for "list + non-list" or
|
||||
// "blob + non-blob". For "non-list + ..." or
|
||||
// "something - ...", we returned before evaluating the
|
||||
// 2nd operand.
|
||||
tv_clear(rettv);
|
||||
tv_clear(&var2);
|
||||
return FAIL;
|
||||
}
|
||||
if (var2.v_type == VAR_FLOAT)
|
||||
@@ -3850,6 +3978,7 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)
|
||||
|
||||
// Handle sixth level expression:
|
||||
// number number constant
|
||||
// 0zFFFFFFFF Blob constant
|
||||
// "string" string constant
|
||||
// 'string' literal string constant
|
||||
// &option-name option value
|
||||
@@ -3946,7 +4075,37 @@ static int eval7(
|
||||
rettv->v_type = VAR_FLOAT;
|
||||
rettv->vval.v_float = f;
|
||||
}
|
||||
} else if (**arg == '0' && ((*arg)[1] == 'z' || (*arg)[1] == 'Z')) {
|
||||
blob_T *blob = NULL;
|
||||
// Blob constant: 0z0123456789abcdef
|
||||
if (evaluate) {
|
||||
blob = tv_blob_alloc();
|
||||
}
|
||||
char_u *bp;
|
||||
for (bp = *arg + 2; ascii_isxdigit(bp[0]); bp += 2) {
|
||||
if (!ascii_isxdigit(bp[1])) {
|
||||
if (blob != NULL) {
|
||||
EMSG(_("E973: Blob literal should have an even number of hex "
|
||||
"characters"));
|
||||
ga_clear(&blob->bv_ga);
|
||||
XFREE_CLEAR(blob);
|
||||
}
|
||||
ret = FAIL;
|
||||
break;
|
||||
}
|
||||
if (blob != NULL) {
|
||||
ga_append(&blob->bv_ga, (hex2nr(*bp) << 4) + hex2nr(*(bp + 1)));
|
||||
}
|
||||
if (bp[2] == '.' && ascii_isxdigit(bp[3])) {
|
||||
bp++;
|
||||
}
|
||||
}
|
||||
if (blob != NULL) {
|
||||
tv_blob_set_ret(rettv, blob);
|
||||
}
|
||||
*arg = bp;
|
||||
} else {
|
||||
// decimal, hex or octal number
|
||||
vim_str2nr(*arg, NULL, &len, STR2NR_ALL, &n, NULL, 0, true);
|
||||
if (len == 0) {
|
||||
EMSG2(_(e_invexpr2), *arg);
|
||||
@@ -4336,7 +4495,8 @@ eval_index(
|
||||
case VAR_STRING:
|
||||
case VAR_NUMBER:
|
||||
case VAR_LIST:
|
||||
case VAR_DICT: {
|
||||
case VAR_DICT:
|
||||
case VAR_BLOB: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -4462,6 +4622,53 @@ eval_index(
|
||||
rettv->vval.v_string = (char_u *)v;
|
||||
break;
|
||||
}
|
||||
case VAR_BLOB: {
|
||||
len = tv_blob_len(rettv->vval.v_blob);
|
||||
if (range) {
|
||||
// The resulting variable is a sub-blob. If the indexes
|
||||
// are out of range the result is empty.
|
||||
if (n1 < 0) {
|
||||
n1 = len + n1;
|
||||
if (n1 < 0) {
|
||||
n1 = 0;
|
||||
}
|
||||
}
|
||||
if (n2 < 0) {
|
||||
n2 = len + n2;
|
||||
} else if (n2 >= len) {
|
||||
n2 = len - 1;
|
||||
}
|
||||
if (n1 >= len || n2 < 0 || n1 > n2) {
|
||||
tv_clear(rettv);
|
||||
rettv->v_type = VAR_BLOB;
|
||||
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 (long i = n1; i <= n2; i++) {
|
||||
tv_blob_set(blob, i - n1, tv_blob_get(rettv->vval.v_blob, i));
|
||||
}
|
||||
tv_clear(rettv);
|
||||
tv_blob_set_ret(rettv, blob);
|
||||
}
|
||||
} else {
|
||||
// The resulting variable is a byte value.
|
||||
// If the index is too big or negative that is an error.
|
||||
if (n1 < 0) {
|
||||
n1 = len + n1;
|
||||
}
|
||||
if (n1 < len && n1 >= 0) {
|
||||
const int v = (int)tv_blob_get(rettv->vval.v_blob, n1);
|
||||
tv_clear(rettv);
|
||||
rettv->v_type = VAR_NUMBER;
|
||||
rettv->vval.v_number = v;
|
||||
} else {
|
||||
EMSGN(_(e_blobidx), n1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VAR_LIST: {
|
||||
len = tv_list_len(rettv->vval.v_list);
|
||||
if (n1 < 0) {
|
||||
@@ -5398,7 +5605,8 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
|
||||
case VAR_SPECIAL:
|
||||
case VAR_FLOAT:
|
||||
case VAR_NUMBER:
|
||||
case VAR_STRING: {
|
||||
case VAR_STRING:
|
||||
case VAR_BLOB: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -6161,6 +6369,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
|
||||
dict_T *d = NULL;
|
||||
typval_T save_val;
|
||||
typval_T save_key;
|
||||
blob_T *b = NULL;
|
||||
int rem = false;
|
||||
int todo;
|
||||
char_u *ermsg = (char_u *)(map ? "map()" : "filter()");
|
||||
@@ -6170,7 +6379,12 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
|
||||
int save_did_emsg;
|
||||
int idx = 0;
|
||||
|
||||
if (argvars[0].v_type == VAR_LIST) {
|
||||
if (argvars[0].v_type == VAR_BLOB) {
|
||||
tv_copy(&argvars[0], rettv);
|
||||
if ((b = argvars[0].vval.v_blob) == NULL) {
|
||||
return;
|
||||
}
|
||||
} else if (argvars[0].v_type == VAR_LIST) {
|
||||
tv_copy(&argvars[0], rettv);
|
||||
if ((l = argvars[0].vval.v_list) == NULL
|
||||
|| (!map
|
||||
@@ -6184,7 +6398,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
EMSG2(_(e_listdictarg), ermsg);
|
||||
EMSG2(_(e_listdictblobarg), ermsg);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6234,6 +6448,34 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
|
||||
}
|
||||
}
|
||||
hash_unlock(ht);
|
||||
} else if (argvars[0].v_type == VAR_BLOB) {
|
||||
vimvars[VV_KEY].vv_type = VAR_NUMBER;
|
||||
|
||||
for (int i = 0; i < b->bv_ga.ga_len; i++) {
|
||||
typval_T tv;
|
||||
tv.v_type = VAR_NUMBER;
|
||||
const varnumber_T val = tv_blob_get(b, i);
|
||||
tv.vval.v_number = val;
|
||||
vimvars[VV_KEY].vv_nr = idx;
|
||||
if (filter_map_one(&tv, expr, map, &rem) == FAIL || did_emsg) {
|
||||
break;
|
||||
}
|
||||
if (tv.v_type != VAR_NUMBER) {
|
||||
EMSG(_(e_invalblob));
|
||||
return;
|
||||
}
|
||||
if (map) {
|
||||
if (tv.vval.v_number != val) {
|
||||
tv_blob_set(b, i, tv.vval.v_number);
|
||||
}
|
||||
} else if (rem) {
|
||||
char_u *const p = (char_u *)argvars[0].vval.v_blob->bv_ga.ga_data;
|
||||
memmove(p + i, p + i + 1, (size_t)b->bv_ga.ga_len - i - 1);
|
||||
b->bv_ga.ga_len--;
|
||||
i--;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
} else {
|
||||
assert(argvars[0].v_type == VAR_LIST);
|
||||
vimvars[VV_KEY].vv_type = VAR_NUMBER;
|
||||
@@ -7728,10 +7970,61 @@ bool write_list(FileDescriptor *const fp, const list_T *const list,
|
||||
}
|
||||
return true;
|
||||
write_list_error:
|
||||
emsgf(_("E80: Error while writing: %s"), os_strerror(error));
|
||||
emsgf(_(e_write2), os_strerror(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Write a blob to file with descriptor `fp`.
|
||||
///
|
||||
/// @param[in] fp File to write to.
|
||||
/// @param[in] blob Blob to write.
|
||||
///
|
||||
/// @return true on success, or false on failure.
|
||||
bool write_blob(FileDescriptor *const fp, const blob_T *const blob)
|
||||
FUNC_ATTR_NONNULL_ARG(1)
|
||||
{
|
||||
int error = 0;
|
||||
const int len = tv_blob_len(blob);
|
||||
if (len > 0) {
|
||||
const ptrdiff_t written = file_write(fp, blob->bv_ga.ga_data, (size_t)len);
|
||||
if (written < (ptrdiff_t)len) {
|
||||
error = (int)written;
|
||||
goto write_blob_error;
|
||||
}
|
||||
}
|
||||
error = file_flush(fp);
|
||||
if (error != 0) {
|
||||
goto write_blob_error;
|
||||
}
|
||||
return true;
|
||||
write_blob_error:
|
||||
EMSG2(_(e_write2), os_strerror(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Read a blob from a file `fd`.
|
||||
///
|
||||
/// @param[in] fd File to read from.
|
||||
/// @param[in,out] blob Blob to write to.
|
||||
///
|
||||
/// @return true on success, or false on failure.
|
||||
bool read_blob(FILE *const fd, blob_T *const blob)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
FileInfo file_info;
|
||||
if (!os_fileinfo_fd(fileno(fd), &file_info)) {
|
||||
return false;
|
||||
}
|
||||
const int size = (int)os_fileinfo_size(&file_info);
|
||||
ga_grow(&blob->bv_ga, size);
|
||||
blob->bv_ga.ga_len = size;
|
||||
if (fread(blob->bv_ga.ga_data, 1, blob->bv_ga.ga_len, fd)
|
||||
< (size_t)blob->bv_ga.ga_len) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Saves a typval_T as a string.
|
||||
///
|
||||
/// For lists or buffers, replaces NLs with NUL and separates items with NLs.
|
||||
@@ -9248,7 +9541,10 @@ static void set_var_const(const char *name, const size_t name_len,
|
||||
}
|
||||
|
||||
if (is_const) {
|
||||
tv_item_lock(&v->di_tv, 1, true);
|
||||
// Like :lockvar! name: lock the value and what it contains, but only
|
||||
// if the reference count is up to one. That locks only literal
|
||||
// values.
|
||||
tv_item_lock(&v->di_tv, DICT_MAXNEST, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9456,6 +9752,9 @@ int var_item_copy(const vimconv_T *const conv,
|
||||
ret = FAIL;
|
||||
}
|
||||
break;
|
||||
case VAR_BLOB:
|
||||
tv_blob_copy(from, to);
|
||||
break;
|
||||
case VAR_DICT:
|
||||
to->v_type = VAR_DICT;
|
||||
to->v_lock = VAR_UNLOCKED;
|
||||
@@ -10815,6 +11114,29 @@ int typval_compare(
|
||||
// For "is" a different type always means false, for "notis"
|
||||
// it means true.
|
||||
n1 = type == EXPR_ISNOT;
|
||||
} else if (typ1->v_type == VAR_BLOB || typ2->v_type == VAR_BLOB) {
|
||||
if (type_is) {
|
||||
n1 = typ1->v_type == typ2->v_type
|
||||
&& typ1->vval.v_blob == typ2->vval.v_blob;
|
||||
if (type == EXPR_ISNOT) {
|
||||
n1 = !n1;
|
||||
}
|
||||
} else if (typ1->v_type != typ2->v_type
|
||||
|| (type != EXPR_EQUAL && type != EXPR_NEQUAL)) {
|
||||
if (typ1->v_type != typ2->v_type) {
|
||||
EMSG(_("E977: Can only compare Blob with Blob"));
|
||||
} else {
|
||||
EMSG(_(e_invalblob));
|
||||
}
|
||||
tv_clear(typ1);
|
||||
return FAIL;
|
||||
} else {
|
||||
// Compare two Blobs for being equal or unequal.
|
||||
n1 = tv_blob_equal(typ1->vval.v_blob, typ2->vval.v_blob);
|
||||
if (type == EXPR_NEQUAL) {
|
||||
n1 = !n1;
|
||||
}
|
||||
}
|
||||
} else if (typ1->v_type == VAR_LIST || typ2->v_type == VAR_LIST) {
|
||||
if (type_is) {
|
||||
n1 = typ1->v_type == typ2->v_type
|
||||
|
@@ -63,6 +63,7 @@ typedef struct lval_S {
|
||||
dict_T *ll_dict; ///< The Dictionary or NULL.
|
||||
dictitem_T *ll_di; ///< The dictitem or NULL.
|
||||
char_u *ll_newkey; ///< New key for Dict in allocated memory or NULL.
|
||||
blob_T *ll_blob; ///< The Blob or NULL.
|
||||
} lval_T;
|
||||
|
||||
/// enum used by var_flavour()
|
||||
@@ -154,6 +155,7 @@ typedef enum {
|
||||
VV_TYPE_DICT,
|
||||
VV_TYPE_FLOAT,
|
||||
VV_TYPE_BOOL,
|
||||
VV_TYPE_BLOB,
|
||||
VV_EVENT,
|
||||
VV_ECHOSPACE,
|
||||
VV_ARGV,
|
||||
@@ -165,6 +167,7 @@ typedef enum {
|
||||
VV__NULL_STRING, // String with NULL value. For test purposes only.
|
||||
VV__NULL_LIST, // List with NULL value. For test purposes only.
|
||||
VV__NULL_DICT, // Dictionary with NULL value. For test purposes only.
|
||||
VV__NULL_BLOB, // Blob with NULL value. For test purposes only.
|
||||
VV_LUA,
|
||||
} VimVarIndex;
|
||||
|
||||
|
@@ -250,7 +250,7 @@ return {
|
||||
min={args=1, base=1},
|
||||
mkdir={args={1, 3}},
|
||||
mode={args={0, 1}},
|
||||
msgpackdump={args=1},
|
||||
msgpackdump={args={1, 2}},
|
||||
msgpackparse={args=1},
|
||||
nextnonblank={args=1},
|
||||
nr2char={args={1, 2}},
|
||||
|
@@ -246,14 +246,15 @@ list_T *decode_create_map_special_dict(typval_T *const ret_tv,
|
||||
/// Convert char* string to typval_T
|
||||
///
|
||||
/// Depending on whether string has (no) NUL bytes, it may use a special
|
||||
/// dictionary or decode string to VAR_STRING.
|
||||
/// dictionary, VAR_BLOB, or decode string to VAR_STRING.
|
||||
///
|
||||
/// @param[in] s String to decode.
|
||||
/// @param[in] len String length.
|
||||
/// @param[in] hasnul Whether string has NUL byte, not or it was not yet
|
||||
/// determined.
|
||||
/// @param[in] binary If true, save special string type as kMPBinary,
|
||||
/// otherwise kMPString.
|
||||
/// @param[in] binary Determines decode type if string has NUL bytes.
|
||||
/// If true convert string to VAR_BLOB, otherwise to the
|
||||
/// kMPString special type.
|
||||
/// @param[in] s_allocated If true, then `s` was allocated and can be saved in
|
||||
/// a returned structure. If it is not saved there, it
|
||||
/// will be freed.
|
||||
@@ -269,21 +270,28 @@ typval_T decode_string(const char *const s, const size_t len,
|
||||
? ((s != NULL) && (memchr(s, NUL, len) != NULL))
|
||||
: (bool)hasnul);
|
||||
if (really_hasnul) {
|
||||
list_T *const list = tv_list_alloc(kListLenMayKnow);
|
||||
tv_list_ref(list);
|
||||
typval_T tv;
|
||||
create_special_dict(&tv, binary ? kMPBinary : kMPString, ((typval_T) {
|
||||
.v_type = VAR_LIST,
|
||||
.v_lock = VAR_UNLOCKED,
|
||||
.vval = { .v_list = list },
|
||||
}));
|
||||
const int elw_ret = encode_list_write((void *)list, s, len);
|
||||
if (s_allocated) {
|
||||
xfree((void *)s);
|
||||
}
|
||||
if (elw_ret == -1) {
|
||||
tv_clear(&tv);
|
||||
return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED };
|
||||
tv.v_lock = VAR_UNLOCKED;
|
||||
if (binary) {
|
||||
tv_blob_alloc_ret(&tv);
|
||||
ga_concat_len(&tv.vval.v_blob->bv_ga, s, len);
|
||||
} else {
|
||||
list_T *const list = tv_list_alloc(kListLenMayKnow);
|
||||
tv_list_ref(list);
|
||||
create_special_dict(&tv, kMPString,
|
||||
((typval_T){
|
||||
.v_type = VAR_LIST,
|
||||
.v_lock = VAR_UNLOCKED,
|
||||
.vval = { .v_list = list },
|
||||
}));
|
||||
const int elw_ret = encode_list_write((void *)list, s, len);
|
||||
if (s_allocated) {
|
||||
xfree((void *)s);
|
||||
}
|
||||
if (elw_ret == -1) {
|
||||
tv_clear(&tv);
|
||||
return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED };
|
||||
}
|
||||
}
|
||||
return tv;
|
||||
} else {
|
||||
|
@@ -47,6 +47,14 @@ const char *const encode_special_var_names[] = {
|
||||
# include "eval/encode.c.generated.h"
|
||||
#endif
|
||||
|
||||
/// Msgpack callback for writing to a Blob
|
||||
int encode_blob_write(void *const data, const char *const buf, const size_t len)
|
||||
FUNC_ATTR_NONNULL_ARG(1)
|
||||
{
|
||||
ga_concat_len(&((blob_T *)data)->bv_ga, buf, len);
|
||||
return (int)len;
|
||||
}
|
||||
|
||||
/// Msgpack callback for writing to readfile()-style list
|
||||
int encode_list_write(void *const data, const char *const buf, const size_t len)
|
||||
FUNC_ATTR_NONNULL_ARG(1)
|
||||
@@ -319,6 +327,30 @@ int encode_read_from_list(ListReaderState *const state, char *const buf,
|
||||
|
||||
#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type)
|
||||
|
||||
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
|
||||
do { \
|
||||
const blob_T *const blob_ = (blob); \
|
||||
const int len_ = (len); \
|
||||
if (len_ == 0) { \
|
||||
ga_concat(gap, "0z"); \
|
||||
} else { \
|
||||
/* Allocate space for "0z", the two hex chars per byte, and a */ \
|
||||
/* "." separator after every eight hex chars. */ \
|
||||
/* Example: "0z00112233.44556677.8899" */ \
|
||||
ga_grow(gap, 2 + 2 * len_ + (len_ - 1) / 4); \
|
||||
ga_concat(gap, "0z"); \
|
||||
char numbuf[NUMBUFLEN]; \
|
||||
for (int i_ = 0; i_ < len_; i_++) { \
|
||||
if (i_ > 0 && (i_ & 3) == 0) { \
|
||||
ga_append(gap, '.'); \
|
||||
} \
|
||||
vim_snprintf((char *)numbuf, ARRAY_SIZE(numbuf), "%02X", \
|
||||
(int)tv_blob_get(blob_, i_)); \
|
||||
ga_concat(gap, numbuf); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
|
||||
do { \
|
||||
char numbuf[NUMBUFLEN]; \
|
||||
@@ -705,6 +737,28 @@ static inline int convert_to_json_string(garray_T *const gap,
|
||||
return FAIL; \
|
||||
} while (0)
|
||||
|
||||
#undef TYPVAL_ENCODE_CONV_BLOB
|
||||
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
|
||||
do { \
|
||||
const blob_T *const blob_ = (blob); \
|
||||
const int len_ = (len); \
|
||||
if (len_ == 0) { \
|
||||
ga_concat(gap, "[]"); \
|
||||
} else { \
|
||||
ga_append(gap, '['); \
|
||||
char numbuf[NUMBUFLEN]; \
|
||||
for (int i_ = 0; i_ < len_; i_++) { \
|
||||
if (i_ > 0) { \
|
||||
ga_concat(gap, ", "); \
|
||||
} \
|
||||
vim_snprintf((char *)numbuf, ARRAY_SIZE(numbuf), "%d", \
|
||||
(int)tv_blob_get(blob_, i_)); \
|
||||
ga_concat(gap, numbuf); \
|
||||
} \
|
||||
ga_append(gap, ']'); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#undef TYPVAL_ENCODE_CONV_FUNC_START
|
||||
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
|
||||
return conv_error(_("E474: Error while dumping %s, %s: " \
|
||||
@@ -770,6 +824,7 @@ bool encode_check_json_key(const typval_T *const tv)
|
||||
#undef TYPVAL_ENCODE_CONV_STRING
|
||||
#undef TYPVAL_ENCODE_CONV_STR_STRING
|
||||
#undef TYPVAL_ENCODE_CONV_EXT_STRING
|
||||
#undef TYPVAL_ENCODE_CONV_BLOB
|
||||
#undef TYPVAL_ENCODE_CONV_NUMBER
|
||||
#undef TYPVAL_ENCODE_CONV_FLOAT
|
||||
#undef TYPVAL_ENCODE_CONV_FUNC_START
|
||||
@@ -904,6 +959,15 @@ char *encode_tv2json(typval_T *tv, size_t *len)
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
|
||||
do { \
|
||||
const size_t len_ = (size_t)(len); \
|
||||
msgpack_pack_bin(packer, len_); \
|
||||
if (len_ > 0) { \
|
||||
msgpack_pack_bin_body(packer, (blob)->bv_ga.ga_data, len_); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
|
||||
msgpack_pack_int64(packer, (int64_t)(num))
|
||||
|
||||
@@ -982,6 +1046,7 @@ char *encode_tv2json(typval_T *tv, size_t *len)
|
||||
#undef TYPVAL_ENCODE_CONV_STRING
|
||||
#undef TYPVAL_ENCODE_CONV_STR_STRING
|
||||
#undef TYPVAL_ENCODE_CONV_EXT_STRING
|
||||
#undef TYPVAL_ENCODE_CONV_BLOB
|
||||
#undef TYPVAL_ENCODE_CONV_NUMBER
|
||||
#undef TYPVAL_ENCODE_CONV_FLOAT
|
||||
#undef TYPVAL_ENCODE_CONV_FUNC_START
|
||||
|
@@ -38,6 +38,20 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2,
|
||||
case VAR_SPECIAL: {
|
||||
break;
|
||||
}
|
||||
case VAR_BLOB: {
|
||||
if (*op != '+' || tv2->v_type != VAR_BLOB) {
|
||||
break;
|
||||
}
|
||||
// Blob += Blob
|
||||
if (tv1->vval.v_blob != NULL && tv2->vval.v_blob != NULL) {
|
||||
blob_T *const b1 = tv1->vval.v_blob;
|
||||
blob_T *const b2 = tv2->vval.v_blob;
|
||||
for (int i = 0; i < tv_blob_len(b2); i++) {
|
||||
ga_append(&b1->bv_ga, (char)tv_blob_get(b2, i));
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
case VAR_LIST: {
|
||||
if (*op != '+' || tv2->v_type != VAR_LIST) {
|
||||
break;
|
||||
|
@@ -96,6 +96,7 @@ PRAGMA_DIAG_POP
|
||||
|
||||
|
||||
static char *e_listarg = N_("E686: Argument of %s must be a List");
|
||||
static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob");
|
||||
static char *e_invalwindow = N_("E957: Invalid window number");
|
||||
|
||||
/// Dummy va_list for passing to vim_snprintf
|
||||
@@ -321,8 +322,20 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
tv_list_append_tv(l, &argvars[1]);
|
||||
tv_copy(&argvars[0], rettv);
|
||||
}
|
||||
} else if (argvars[0].v_type == VAR_BLOB) {
|
||||
blob_T *const b = argvars[0].vval.v_blob;
|
||||
if (b != NULL
|
||||
&& !var_check_lock(b->bv_lock, N_("add() argument"), TV_TRANSLATE)) {
|
||||
bool error = false;
|
||||
const varnumber_T n = tv_get_number_chk(&argvars[1], &error);
|
||||
|
||||
if (!error) {
|
||||
ga_append(&b->bv_ga, (int)n);
|
||||
tv_copy(&argvars[0], rettv);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
EMSG(_(e_listreq));
|
||||
EMSG(_(e_listblobreq));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -959,7 +972,17 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
}
|
||||
|
||||
ptrdiff_t input_len = 0;
|
||||
char *input = save_tv_as_string(&argvars[1], &input_len, false);
|
||||
char *input = NULL;
|
||||
if (argvars[1].v_type == VAR_BLOB) {
|
||||
const blob_T *const b = argvars[1].vval.v_blob;
|
||||
input_len = tv_blob_len(b);
|
||||
if (input_len > 0) {
|
||||
input = xmemdup(b->bv_ga.ga_data, input_len);
|
||||
}
|
||||
} else {
|
||||
input = save_tv_as_string(&argvars[1], &input_len, false);
|
||||
}
|
||||
|
||||
if (!input) {
|
||||
// Either the error has been handled by save_tv_as_string(),
|
||||
// or there is no input to send.
|
||||
@@ -1874,6 +1897,10 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
n = argvars[0].vval.v_special == kSpecialVarNull;
|
||||
break;
|
||||
}
|
||||
case VAR_BLOB: {
|
||||
n = (tv_blob_len(argvars[0].vval.v_blob) == 0);
|
||||
break;
|
||||
}
|
||||
case VAR_UNKNOWN: {
|
||||
internal_error("f_empty(UNKNOWN)");
|
||||
break;
|
||||
@@ -2791,7 +2818,23 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
typval_T *tv = NULL;
|
||||
bool what_is_dict = false;
|
||||
|
||||
if (argvars[0].v_type == VAR_LIST) {
|
||||
if (argvars[0].v_type == VAR_BLOB) {
|
||||
bool error = false;
|
||||
int idx = tv_get_number_chk(&argvars[1], &error);
|
||||
|
||||
if (!error) {
|
||||
rettv->v_type = VAR_NUMBER;
|
||||
if (idx < 0) {
|
||||
idx = tv_blob_len(argvars[0].vval.v_blob) + idx;
|
||||
}
|
||||
if (idx < 0 || idx >= tv_blob_len(argvars[0].vval.v_blob)) {
|
||||
rettv->vval.v_number = -1;
|
||||
} else {
|
||||
rettv->vval.v_number = tv_blob_get(argvars[0].vval.v_blob, idx);
|
||||
tv = rettv;
|
||||
}
|
||||
}
|
||||
} else if (argvars[0].v_type == VAR_LIST) {
|
||||
if ((l = argvars[0].vval.v_list) != NULL) {
|
||||
bool error = false;
|
||||
|
||||
@@ -2852,7 +2895,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
EMSG2(_(e_listdictarg), "get()");
|
||||
EMSG2(_(e_listdictblobarg), "get()");
|
||||
}
|
||||
|
||||
if (tv == NULL) {
|
||||
@@ -4791,8 +4834,38 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
bool ic = false;
|
||||
|
||||
rettv->vval.v_number = -1;
|
||||
if (argvars[0].v_type != VAR_LIST) {
|
||||
EMSG(_(e_listreq));
|
||||
if (argvars[0].v_type == VAR_BLOB) {
|
||||
bool error = false;
|
||||
int start = 0;
|
||||
|
||||
if (argvars[2].v_type != VAR_UNKNOWN) {
|
||||
start = tv_get_number_chk(&argvars[2], &error);
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
blob_T *const b = argvars[0].vval.v_blob;
|
||||
if (b == NULL) {
|
||||
return;
|
||||
}
|
||||
if (start < 0) {
|
||||
start = tv_blob_len(b) + start;
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
for (idx = start; idx < tv_blob_len(b); idx++) {
|
||||
typval_T tv;
|
||||
tv.v_type = VAR_NUMBER;
|
||||
tv.vval.v_number = tv_blob_get(b, idx);
|
||||
if (tv_equal(&tv, &argvars[1], ic, false)) {
|
||||
rettv->vval.v_number = idx;
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else if (argvars[0].v_type != VAR_LIST) {
|
||||
EMSG(_(e_listblobreq));
|
||||
return;
|
||||
}
|
||||
list_T *const l = argvars[0].vval.v_list;
|
||||
@@ -4921,8 +4994,46 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
list_T *l;
|
||||
bool error = false;
|
||||
|
||||
if (argvars[0].v_type != VAR_LIST) {
|
||||
EMSG2(_(e_listarg), "insert()");
|
||||
if (argvars[0].v_type == VAR_BLOB) {
|
||||
blob_T *const b = argvars[0].vval.v_blob;
|
||||
|
||||
if (b == NULL
|
||||
|| var_check_lock(b->bv_lock, N_("insert() argument"),
|
||||
TV_TRANSLATE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
long before = 0;
|
||||
const int len = tv_blob_len(b);
|
||||
|
||||
if (argvars[2].v_type != VAR_UNKNOWN) {
|
||||
before = (long)tv_get_number_chk(&argvars[2], &error);
|
||||
if (error) {
|
||||
return; // type error; errmsg already given
|
||||
}
|
||||
if (before < 0 || before > len) {
|
||||
EMSG2(_(e_invarg2), tv_get_string(&argvars[2]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
const int val = tv_get_number_chk(&argvars[1], &error);
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
if (val < 0 || val > 255) {
|
||||
EMSG2(_(e_invarg2), tv_get_string(&argvars[1]));
|
||||
return;
|
||||
}
|
||||
|
||||
ga_grow(&b->bv_ga, 1);
|
||||
char_u *const p = (char_u *)b->bv_ga.ga_data;
|
||||
memmove(p + before + 1, p + before, (size_t)len - before);
|
||||
*(p + before) = val;
|
||||
b->bv_ga.ga_len++;
|
||||
|
||||
tv_copy(&argvars[0], rettv);
|
||||
} else if (argvars[0].v_type != VAR_LIST) {
|
||||
EMSG2(_(e_listblobarg), "insert()");
|
||||
} else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
|
||||
N_("insert() argument"), TV_TRANSLATE)) {
|
||||
long before = 0;
|
||||
@@ -5582,6 +5693,10 @@ static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
tv_get_string(&argvars[0]));
|
||||
break;
|
||||
}
|
||||
case VAR_BLOB: {
|
||||
rettv->vval.v_number = tv_blob_len(argvars[0].vval.v_blob);
|
||||
break;
|
||||
}
|
||||
case VAR_LIST: {
|
||||
rettv->vval.v_number = tv_list_len(argvars[0].vval.v_list);
|
||||
break;
|
||||
@@ -6392,9 +6507,16 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
EMSG2(_(e_listarg), "msgpackdump()");
|
||||
return;
|
||||
}
|
||||
list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
|
||||
list_T *const list = argvars[0].vval.v_list;
|
||||
msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write);
|
||||
msgpack_packer *packer;
|
||||
if (argvars[1].v_type != VAR_UNKNOWN
|
||||
&& strequal(tv_get_string(&argvars[1]), "B")) {
|
||||
tv_blob_alloc_ret(rettv);
|
||||
packer = msgpack_packer_new(rettv->vval.v_blob, &encode_blob_write);
|
||||
} else {
|
||||
packer = msgpack_packer_new(tv_list_alloc_ret(rettv, kListLenMayKnow),
|
||||
&encode_list_write);
|
||||
}
|
||||
const char *const msg = _("msgpackdump() argument, index %i");
|
||||
// Assume that translation will not take more then 4 times more space
|
||||
char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN];
|
||||
@@ -6402,23 +6524,50 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
TV_LIST_ITER(list, li, {
|
||||
vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx);
|
||||
idx++;
|
||||
if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {
|
||||
if (encode_vim_to_msgpack(packer, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {
|
||||
break;
|
||||
}
|
||||
});
|
||||
msgpack_packer_free(lpacker);
|
||||
msgpack_packer_free(packer);
|
||||
}
|
||||
|
||||
/// "msgpackparse" function
|
||||
static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
static int msgpackparse_convert_item(const msgpack_object data,
|
||||
const msgpack_unpack_return result,
|
||||
list_T *const ret_list,
|
||||
const bool fail_if_incomplete)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
if (argvars[0].v_type != VAR_LIST) {
|
||||
EMSG2(_(e_listarg), "msgpackparse()");
|
||||
return;
|
||||
switch (result) {
|
||||
case MSGPACK_UNPACK_PARSE_ERROR:
|
||||
EMSG2(_(e_invarg2), "Failed to parse msgpack string");
|
||||
return FAIL;
|
||||
case MSGPACK_UNPACK_NOMEM_ERROR:
|
||||
EMSG(_(e_outofmem));
|
||||
return FAIL;
|
||||
case MSGPACK_UNPACK_CONTINUE:
|
||||
if (fail_if_incomplete) {
|
||||
EMSG2(_(e_invarg2), "Incomplete msgpack string");
|
||||
return FAIL;
|
||||
}
|
||||
return NOTDONE;
|
||||
case MSGPACK_UNPACK_SUCCESS: {
|
||||
typval_T tv = { .v_type = VAR_UNKNOWN };
|
||||
if (msgpack_to_vim(data, &tv) == FAIL) {
|
||||
EMSG2(_(e_invarg2), "Failed to convert msgpack string");
|
||||
return FAIL;
|
||||
}
|
||||
tv_list_append_owned_tv(ret_list, tv);
|
||||
return OK;
|
||||
}
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
|
||||
const list_T *const list = argvars[0].vval.v_list;
|
||||
}
|
||||
|
||||
static void msgpackparse_unpack_list(const list_T *const list,
|
||||
list_T *const ret_list)
|
||||
FUNC_ATTR_NONNULL_ARG(2)
|
||||
{
|
||||
if (tv_list_len(list) == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -6437,43 +6586,28 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
do {
|
||||
if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) {
|
||||
EMSG(_(e_outofmem));
|
||||
goto f_msgpackparse_exit;
|
||||
goto end;
|
||||
}
|
||||
size_t read_bytes;
|
||||
const int rlret = encode_read_from_list(
|
||||
&lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, &read_bytes);
|
||||
if (rlret == FAIL) {
|
||||
EMSG2(_(e_invarg2), "List item is not a string");
|
||||
goto f_msgpackparse_exit;
|
||||
goto end;
|
||||
}
|
||||
msgpack_unpacker_buffer_consumed(unpacker, read_bytes);
|
||||
if (read_bytes == 0) {
|
||||
break;
|
||||
}
|
||||
while (unpacker->off < unpacker->used) {
|
||||
const msgpack_unpack_return result = msgpack_unpacker_next(unpacker,
|
||||
&unpacked);
|
||||
if (result == MSGPACK_UNPACK_PARSE_ERROR) {
|
||||
EMSG2(_(e_invarg2), "Failed to parse msgpack string");
|
||||
goto f_msgpackparse_exit;
|
||||
}
|
||||
if (result == MSGPACK_UNPACK_NOMEM_ERROR) {
|
||||
EMSG(_(e_outofmem));
|
||||
goto f_msgpackparse_exit;
|
||||
}
|
||||
if (result == MSGPACK_UNPACK_SUCCESS) {
|
||||
typval_T tv = { .v_type = VAR_UNKNOWN };
|
||||
if (msgpack_to_vim(unpacked.data, &tv) == FAIL) {
|
||||
EMSG2(_(e_invarg2), "Failed to convert msgpack string");
|
||||
goto f_msgpackparse_exit;
|
||||
}
|
||||
tv_list_append_owned_tv(ret_list, tv);
|
||||
}
|
||||
if (result == MSGPACK_UNPACK_CONTINUE) {
|
||||
if (rlret == OK) {
|
||||
EMSG2(_(e_invarg2), "Incomplete msgpack string");
|
||||
}
|
||||
const msgpack_unpack_return result
|
||||
= msgpack_unpacker_next(unpacker, &unpacked);
|
||||
const int conv_result = msgpackparse_convert_item(unpacked.data, result,
|
||||
ret_list, rlret == OK);
|
||||
if (conv_result == NOTDONE) {
|
||||
break;
|
||||
} else if (conv_result == FAIL) {
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (rlret == OK) {
|
||||
@@ -6481,10 +6615,47 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
}
|
||||
} while (true);
|
||||
|
||||
f_msgpackparse_exit:
|
||||
msgpack_unpacked_destroy(&unpacked);
|
||||
end:
|
||||
msgpack_unpacker_free(unpacker);
|
||||
return;
|
||||
msgpack_unpacked_destroy(&unpacked);
|
||||
}
|
||||
|
||||
static void msgpackparse_unpack_blob(const blob_T *const blob,
|
||||
list_T *const ret_list)
|
||||
FUNC_ATTR_NONNULL_ARG(2)
|
||||
{
|
||||
const int len = tv_blob_len(blob);
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
msgpack_unpacked unpacked;
|
||||
msgpack_unpacked_init(&unpacked);
|
||||
for (size_t offset = 0; offset < (size_t)len;) {
|
||||
const msgpack_unpack_return result
|
||||
= msgpack_unpack_next(&unpacked, blob->bv_ga.ga_data, len, &offset);
|
||||
if (msgpackparse_convert_item(unpacked.data, result, ret_list, true)
|
||||
!= OK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
msgpack_unpacked_destroy(&unpacked);
|
||||
}
|
||||
|
||||
/// "msgpackparse" function
|
||||
static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) {
|
||||
EMSG2(_(e_listblobarg), "msgpackparse()");
|
||||
return;
|
||||
}
|
||||
list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
|
||||
if (argvars[0].v_type == VAR_LIST) {
|
||||
msgpackparse_unpack_list(argvars[0].vval.v_list, ret_list);
|
||||
} else {
|
||||
msgpackparse_unpack_blob(argvars[0].vval.v_blob, ret_list);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -6895,6 +7066,7 @@ static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
{
|
||||
bool binary = false;
|
||||
bool blob = false;
|
||||
FILE *fd;
|
||||
char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1
|
||||
int io_size = sizeof(buf);
|
||||
@@ -6907,22 +7079,41 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
if (argvars[1].v_type != VAR_UNKNOWN) {
|
||||
if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
|
||||
binary = true;
|
||||
} else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) {
|
||||
blob = true;
|
||||
}
|
||||
if (argvars[2].v_type != VAR_UNKNOWN) {
|
||||
maxline = tv_get_number(&argvars[2]);
|
||||
}
|
||||
}
|
||||
|
||||
list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown);
|
||||
|
||||
// Always open the file in binary mode, library functions have a mind of
|
||||
// their own about CR-LF conversion.
|
||||
const char *const fname = tv_get_string(&argvars[0]);
|
||||
|
||||
if (os_isdir((const char_u *)fname)) {
|
||||
EMSG2(_(e_isadir2), fname);
|
||||
return;
|
||||
}
|
||||
if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) {
|
||||
EMSG2(_(e_notopen), *fname == NUL ? _("<empty>") : fname);
|
||||
return;
|
||||
}
|
||||
|
||||
if (blob) {
|
||||
tv_blob_alloc_ret(rettv);
|
||||
if (!read_blob(fd, rettv->vval.v_blob)) {
|
||||
EMSG2(_(e_notread), fname);
|
||||
// An empty blob is returned on error.
|
||||
tv_blob_free(rettv->vval.v_blob);
|
||||
rettv->vval.v_blob = NULL;
|
||||
}
|
||||
fclose(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown);
|
||||
|
||||
while (maxline < 0 || tv_list_len(l) < maxline) {
|
||||
readlen = (int)fread(buf, 1, io_size, fd);
|
||||
|
||||
@@ -7191,8 +7382,64 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (argvars[0].v_type == VAR_BLOB) {
|
||||
blob_T *const b = argvars[0].vval.v_blob;
|
||||
|
||||
if (b != NULL && var_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool error = false;
|
||||
idx = (long)tv_get_number_chk(&argvars[1], &error);
|
||||
|
||||
if (!error) {
|
||||
const int len = tv_blob_len(b);
|
||||
|
||||
if (idx < 0) {
|
||||
// count from the end
|
||||
idx = len + idx;
|
||||
}
|
||||
if (idx < 0 || idx >= len) {
|
||||
EMSGN(_(e_blobidx), idx);
|
||||
return;
|
||||
}
|
||||
if (argvars[2].v_type == VAR_UNKNOWN) {
|
||||
// Remove one item, return its value.
|
||||
char_u *const p = (char_u *)b->bv_ga.ga_data;
|
||||
rettv->vval.v_number = (varnumber_T)(*(p + idx));
|
||||
memmove(p + idx, p + idx + 1, (size_t)len - idx - 1);
|
||||
b->bv_ga.ga_len--;
|
||||
} else {
|
||||
// Remove range of items, return blob with values.
|
||||
end = (long)tv_get_number_chk(&argvars[2], &error);
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
if (end < 0) {
|
||||
// count from the end
|
||||
end = len + end;
|
||||
}
|
||||
if (end >= len || idx > end) {
|
||||
EMSGN(_(e_blobidx), end);
|
||||
return;
|
||||
}
|
||||
blob_T *const blob = tv_blob_alloc();
|
||||
blob->bv_ga.ga_len = end - idx + 1;
|
||||
ga_grow(&blob->bv_ga, end - idx + 1);
|
||||
|
||||
char_u *const p = (char_u *)b->bv_ga.ga_data;
|
||||
memmove((char_u *)blob->bv_ga.ga_data, p + idx,
|
||||
(size_t)(end - idx + 1));
|
||||
tv_blob_set_ret(rettv, blob);
|
||||
|
||||
if (len - end - 1 > 0) {
|
||||
memmove(p + idx, p + end + 1, (size_t)(len - end - 1));
|
||||
}
|
||||
b->bv_ga.ga_len -= end - idx + 1;
|
||||
}
|
||||
}
|
||||
} else if (argvars[0].v_type != VAR_LIST) {
|
||||
EMSG2(_(e_listdictarg), "remove()");
|
||||
EMSG2(_(e_listdictblobarg), "remove()");
|
||||
} else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
|
||||
arg_errmsg, TV_TRANSLATE)) {
|
||||
bool error = false;
|
||||
@@ -7466,13 +7713,25 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
*/
|
||||
static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
{
|
||||
list_T *l;
|
||||
if (argvars[0].v_type != VAR_LIST) {
|
||||
EMSG2(_(e_listarg), "reverse()");
|
||||
} else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
|
||||
N_("reverse() argument"), TV_TRANSLATE)) {
|
||||
tv_list_reverse(l);
|
||||
tv_list_set_ret(rettv, l);
|
||||
if (argvars[0].v_type == VAR_BLOB) {
|
||||
blob_T *const b = argvars[0].vval.v_blob;
|
||||
const int len = tv_blob_len(b);
|
||||
|
||||
for (int i = 0; i < len / 2; i++) {
|
||||
const char_u tmp = tv_blob_get(b, i);
|
||||
tv_blob_set(b, i, tv_blob_get(b, len - i - 1));
|
||||
tv_blob_set(b, len - i - 1, tmp);
|
||||
}
|
||||
tv_blob_set_ret(rettv, b);
|
||||
} else if (argvars[0].v_type != VAR_LIST) {
|
||||
EMSG2(_(e_listblobarg), "reverse()");
|
||||
} else {
|
||||
list_T *const l = argvars[0].vval.v_list;
|
||||
if (!var_check_lock(tv_list_locked(l), N_("reverse() argument"),
|
||||
TV_TRANSLATE)) {
|
||||
tv_list_reverse(l);
|
||||
tv_list_set_ret(rettv, l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11292,15 +11551,16 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
int n = -1;
|
||||
|
||||
switch (argvars[0].v_type) {
|
||||
case VAR_NUMBER: n = VAR_TYPE_NUMBER; break;
|
||||
case VAR_STRING: n = VAR_TYPE_STRING; break;
|
||||
case VAR_NUMBER: n = VAR_TYPE_NUMBER; break;
|
||||
case VAR_STRING: n = VAR_TYPE_STRING; break;
|
||||
case VAR_PARTIAL:
|
||||
case VAR_FUNC: n = VAR_TYPE_FUNC; break;
|
||||
case VAR_LIST: n = VAR_TYPE_LIST; break;
|
||||
case VAR_DICT: n = VAR_TYPE_DICT; break;
|
||||
case VAR_FLOAT: n = VAR_TYPE_FLOAT; break;
|
||||
case VAR_BOOL: n = VAR_TYPE_BOOL; break;
|
||||
case VAR_SPECIAL:n = VAR_TYPE_SPECIAL; break;
|
||||
case VAR_FUNC: n = VAR_TYPE_FUNC; break;
|
||||
case VAR_LIST: n = VAR_TYPE_LIST; break;
|
||||
case VAR_DICT: n = VAR_TYPE_DICT; break;
|
||||
case VAR_FLOAT: n = VAR_TYPE_FLOAT; break;
|
||||
case VAR_BOOL: n = VAR_TYPE_BOOL; break;
|
||||
case VAR_SPECIAL: n = VAR_TYPE_SPECIAL; break;
|
||||
case VAR_BLOB: n = VAR_TYPE_BLOB; break;
|
||||
case VAR_UNKNOWN: {
|
||||
internal_error("f_type(UNKNOWN)");
|
||||
break;
|
||||
@@ -11679,16 +11939,17 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
return;
|
||||
}
|
||||
|
||||
if (argvars[0].v_type != VAR_LIST) {
|
||||
EMSG2(_(e_listarg), "writefile()");
|
||||
if (argvars[0].v_type == VAR_LIST) {
|
||||
TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
|
||||
if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
} else if (argvars[0].v_type != VAR_BLOB) {
|
||||
EMSG2(_(e_invarg2),
|
||||
_("writefile() first argument must be a List or a Blob"));
|
||||
return;
|
||||
}
|
||||
const list_T *const list = argvars[0].vval.v_list;
|
||||
TV_LIST_ITER_CONST(list, li, {
|
||||
if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
bool binary = false;
|
||||
bool append = false;
|
||||
@@ -11728,7 +11989,13 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
emsgf(_("E482: Can't open file %s for writing: %s"),
|
||||
fname, os_strerror(error));
|
||||
} else {
|
||||
if (write_list(&fp, list, binary)) {
|
||||
bool write_ok;
|
||||
if (argvars[0].v_type == VAR_BLOB) {
|
||||
write_ok = write_blob(&fp, argvars[0].vval.v_blob);
|
||||
} else {
|
||||
write_ok = write_list(&fp, argvars[0].vval.v_list, binary);
|
||||
}
|
||||
if (write_ok) {
|
||||
rettv->vval.v_number = 0;
|
||||
}
|
||||
if ((error = file_close(&fp, do_fsync)) != 0) {
|
||||
|
@@ -2125,6 +2125,77 @@ void tv_dict_set_keys_readonly(dict_T *const dict)
|
||||
});
|
||||
}
|
||||
|
||||
//{{{1 Blobs
|
||||
//{{{2 Alloc/free
|
||||
|
||||
/// Allocate an empty blob.
|
||||
///
|
||||
/// Caller should take care of the reference count.
|
||||
///
|
||||
/// @return [allocated] new blob.
|
||||
blob_T *tv_blob_alloc(void)
|
||||
FUNC_ATTR_NONNULL_RET
|
||||
{
|
||||
blob_T *const blob = xcalloc(1, sizeof(blob_T));
|
||||
ga_init(&blob->bv_ga, 1, 100);
|
||||
return blob;
|
||||
}
|
||||
|
||||
/// Free a blob. Ignores the reference count.
|
||||
///
|
||||
/// @param[in,out] b Blob to free.
|
||||
void tv_blob_free(blob_T *const b)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
ga_clear(&b->bv_ga);
|
||||
xfree(b);
|
||||
}
|
||||
|
||||
/// Unreference a blob.
|
||||
///
|
||||
/// Decrements the reference count and frees blob when it becomes zero.
|
||||
///
|
||||
/// @param[in,out] b Blob to operate on.
|
||||
void tv_blob_unref(blob_T *const b)
|
||||
{
|
||||
if (b != NULL && --b->bv_refcount <= 0) {
|
||||
tv_blob_free(b);
|
||||
}
|
||||
}
|
||||
|
||||
//{{{2 Operations on the whole blob
|
||||
|
||||
/// Check whether two blobs are equal.
|
||||
///
|
||||
/// @param[in] b1 First blob.
|
||||
/// @param[in] b2 Second blob.
|
||||
///
|
||||
/// @return true if blobs are equal, false otherwise.
|
||||
bool tv_blob_equal(const blob_T *const b1, const blob_T *const b2)
|
||||
FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
const int len1 = tv_blob_len(b1);
|
||||
const int len2 = tv_blob_len(b2);
|
||||
|
||||
// empty and NULL are considered the same
|
||||
if (len1 == 0 && len2 == 0) {
|
||||
return true;
|
||||
}
|
||||
if (b1 == b2) {
|
||||
return true;
|
||||
}
|
||||
if (len1 != len2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < b1->bv_ga.ga_len; i++) {
|
||||
if (tv_blob_get(b1, i) != tv_blob_get(b2, i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//{{{1 Generic typval operations
|
||||
//{{{2 Init/alloc/clear
|
||||
//{{{3 Alloc
|
||||
@@ -2169,6 +2240,44 @@ void tv_dict_alloc_ret(typval_T *const ret_tv)
|
||||
tv_dict_set_ret(ret_tv, d);
|
||||
}
|
||||
|
||||
/// Allocate an empty blob for a return value.
|
||||
///
|
||||
/// Also sets reference count.
|
||||
///
|
||||
/// @param[out] ret_tv Structure where blob is saved.
|
||||
void tv_blob_alloc_ret(typval_T *const ret_tv)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
blob_T *const b = tv_blob_alloc();
|
||||
tv_blob_set_ret(ret_tv, b);
|
||||
}
|
||||
|
||||
/// Copy a blob typval to a different typval.
|
||||
///
|
||||
/// @param[in] from Blob object to copy from.
|
||||
/// @param[out] to Blob object to copy to.
|
||||
void tv_blob_copy(typval_T *const from, typval_T *const to)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
assert(from->v_type == VAR_BLOB);
|
||||
|
||||
to->v_type = VAR_BLOB;
|
||||
to->v_lock = VAR_UNLOCKED;
|
||||
if (from->vval.v_blob == NULL) {
|
||||
to->vval.v_blob = NULL;
|
||||
} else {
|
||||
tv_blob_alloc_ret(to);
|
||||
int len = from->vval.v_blob->bv_ga.ga_len;
|
||||
|
||||
if (len > 0) {
|
||||
to->vval.v_blob->bv_ga.ga_data
|
||||
= xmemdup(from->vval.v_blob->bv_ga.ga_data, (size_t)len);
|
||||
}
|
||||
to->vval.v_blob->bv_ga.ga_len = len;
|
||||
to->vval.v_blob->bv_ga.ga_maxlen = len;
|
||||
}
|
||||
}
|
||||
|
||||
//{{{3 Clear
|
||||
#define TYPVAL_ENCODE_ALLOW_SPECIALS false
|
||||
|
||||
@@ -2210,6 +2319,13 @@ void tv_dict_alloc_ret(typval_T *const ret_tv)
|
||||
|
||||
#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type)
|
||||
|
||||
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
|
||||
do { \
|
||||
tv_blob_unref(tv->vval.v_blob); \
|
||||
tv->vval.v_blob = NULL; \
|
||||
tv->v_lock = VAR_UNLOCKED; \
|
||||
} while (0)
|
||||
|
||||
static inline int _nothing_conv_func_start(typval_T *const tv,
|
||||
char_u *const fun)
|
||||
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1)
|
||||
@@ -2392,6 +2508,7 @@ static inline void _nothing_conv_dict_end(typval_T *const tv,
|
||||
#undef TYPVAL_ENCODE_CONV_STRING
|
||||
#undef TYPVAL_ENCODE_CONV_STR_STRING
|
||||
#undef TYPVAL_ENCODE_CONV_EXT_STRING
|
||||
#undef TYPVAL_ENCODE_CONV_BLOB
|
||||
#undef TYPVAL_ENCODE_CONV_FUNC_START
|
||||
#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS
|
||||
#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF
|
||||
@@ -2449,6 +2566,10 @@ void tv_free(typval_T *tv)
|
||||
xfree(tv->vval.v_string);
|
||||
break;
|
||||
}
|
||||
case VAR_BLOB: {
|
||||
tv_blob_unref(tv->vval.v_blob);
|
||||
break;
|
||||
}
|
||||
case VAR_LIST: {
|
||||
tv_list_unref(tv->vval.v_list);
|
||||
break;
|
||||
@@ -2509,6 +2630,12 @@ void tv_copy(const typval_T *const from, typval_T *const to)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VAR_BLOB: {
|
||||
if (from->vval.v_blob != NULL) {
|
||||
to->vval.v_blob->bv_refcount++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VAR_LIST: {
|
||||
tv_list_ref(to->vval.v_list);
|
||||
break;
|
||||
@@ -2533,7 +2660,10 @@ void tv_copy(const typval_T *const from, typval_T *const to)
|
||||
/// @param[out] tv Item to (un)lock.
|
||||
/// @param[in] deep Levels to (un)lock, -1 to (un)lock everything.
|
||||
/// @param[in] lock True if it is needed to lock an item, false to unlock.
|
||||
void tv_item_lock(typval_T *const tv, const int deep, const bool lock)
|
||||
/// @param[in] check_refcount If true, do not lock a list or dict with a
|
||||
/// reference count larger than 1.
|
||||
void tv_item_lock(typval_T *const tv, const int deep, const bool lock,
|
||||
const bool check_refcount)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
// TODO(ZyX-I): Make this not recursive
|
||||
@@ -2560,14 +2690,21 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock)
|
||||
CHANGE_LOCK(lock, tv->v_lock);
|
||||
|
||||
switch (tv->v_type) {
|
||||
case VAR_BLOB: {
|
||||
blob_T *const b = tv->vval.v_blob;
|
||||
if (b != NULL && !(check_refcount && b->bv_refcount > 1)) {
|
||||
CHANGE_LOCK(lock, b->bv_lock);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VAR_LIST: {
|
||||
list_T *const l = tv->vval.v_list;
|
||||
if (l != NULL) {
|
||||
if (l != NULL && !(check_refcount && l->lv_refcount > 1)) {
|
||||
CHANGE_LOCK(lock, l->lv_lock);
|
||||
if (deep < 0 || deep > 1) {
|
||||
// Recursive: lock/unlock the items the List contains.
|
||||
TV_LIST_ITER(l, li, {
|
||||
tv_item_lock(TV_LIST_ITEM_TV(li), deep - 1, lock);
|
||||
tv_item_lock(TV_LIST_ITEM_TV(li), deep - 1, lock, check_refcount);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2575,12 +2712,12 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock)
|
||||
}
|
||||
case VAR_DICT: {
|
||||
dict_T *const d = tv->vval.v_dict;
|
||||
if (d != NULL) {
|
||||
if (d != NULL && !(check_refcount && d->dv_refcount > 1)) {
|
||||
CHANGE_LOCK(lock, d->dv_lock);
|
||||
if (deep < 0 || deep > 1) {
|
||||
// recursive: lock/unlock the items the List contains
|
||||
TV_DICT_ITER(d, di, {
|
||||
tv_item_lock(&di->di_tv, deep - 1, lock);
|
||||
tv_item_lock(&di->di_tv, deep - 1, lock, check_refcount);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2646,10 +2783,11 @@ bool tv_check_lock(const typval_T *tv, const char *name,
|
||||
VarLockStatus lock = VAR_UNLOCKED;
|
||||
|
||||
switch (tv->v_type) {
|
||||
// case VAR_BLOB:
|
||||
// if (tv->vval.v_blob != NULL)
|
||||
// lock = tv->vval.v_blob->bv_lock;
|
||||
// break;
|
||||
case VAR_BLOB:
|
||||
if (tv->vval.v_blob != NULL) {
|
||||
lock = tv->vval.v_blob->bv_lock;
|
||||
}
|
||||
break;
|
||||
case VAR_LIST:
|
||||
if (tv->vval.v_list != NULL) {
|
||||
lock = tv->vval.v_list->lv_lock;
|
||||
@@ -2769,6 +2907,9 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic,
|
||||
recursive_cnt--;
|
||||
return r;
|
||||
}
|
||||
case VAR_BLOB: {
|
||||
return tv_blob_equal(tv1->vval.v_blob, tv2->vval.v_blob);
|
||||
}
|
||||
case VAR_NUMBER: {
|
||||
return tv1->vval.v_number == tv2->vval.v_number;
|
||||
}
|
||||
@@ -2835,6 +2976,10 @@ bool tv_check_str_or_nr(const typval_T *const tv)
|
||||
EMSG(_("E728: Expected a Number or a String, Dictionary found"));
|
||||
return false;
|
||||
}
|
||||
case VAR_BLOB: {
|
||||
EMSG(_("E974: Expected a Number or a String, Blob found"));
|
||||
return false;
|
||||
}
|
||||
case VAR_BOOL: {
|
||||
EMSG(_("E5299: Expected a Number or a String, Boolean found"));
|
||||
return false;
|
||||
@@ -2860,6 +3005,7 @@ static const char *const num_errors[] = {
|
||||
[VAR_LIST]=N_("E745: Using a List as a Number"),
|
||||
[VAR_DICT]=N_("E728: Using a Dictionary as a Number"),
|
||||
[VAR_FLOAT]=N_("E805: Using a Float as a Number"),
|
||||
[VAR_BLOB]=N_("E974: Using a Blob as a Number"),
|
||||
[VAR_UNKNOWN]=N_("E685: using an invalid value as a Number"),
|
||||
};
|
||||
|
||||
@@ -2888,6 +3034,7 @@ bool tv_check_num(const typval_T *const tv)
|
||||
case VAR_LIST:
|
||||
case VAR_DICT:
|
||||
case VAR_FLOAT:
|
||||
case VAR_BLOB:
|
||||
case VAR_UNKNOWN: {
|
||||
EMSG(_(num_errors[tv->v_type]));
|
||||
return false;
|
||||
@@ -2905,6 +3052,7 @@ static const char *const str_errors[] = {
|
||||
[VAR_LIST]=N_("E730: using List as a String"),
|
||||
[VAR_DICT]=N_("E731: using Dictionary as a String"),
|
||||
[VAR_FLOAT]=((const char *)e_float_as_string),
|
||||
[VAR_BLOB]=N_("E976: using Blob as a String"),
|
||||
[VAR_UNKNOWN]=N_("E908: using an invalid value as a String"),
|
||||
};
|
||||
|
||||
@@ -2933,6 +3081,7 @@ bool tv_check_str(const typval_T *const tv)
|
||||
case VAR_LIST:
|
||||
case VAR_DICT:
|
||||
case VAR_FLOAT:
|
||||
case VAR_BLOB:
|
||||
case VAR_UNKNOWN: {
|
||||
EMSG(_(str_errors[tv->v_type]));
|
||||
return false;
|
||||
@@ -2980,6 +3129,7 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error)
|
||||
case VAR_PARTIAL:
|
||||
case VAR_LIST:
|
||||
case VAR_DICT:
|
||||
case VAR_BLOB:
|
||||
case VAR_FLOAT: {
|
||||
EMSG(_(num_errors[tv->v_type]));
|
||||
break;
|
||||
@@ -3075,6 +3225,10 @@ float_T tv_get_float(const typval_T *const tv)
|
||||
EMSG(_("E907: Using a special value as a Float"));
|
||||
break;
|
||||
}
|
||||
case VAR_BLOB: {
|
||||
EMSG(_("E975: Using a Blob as a Float"));
|
||||
break;
|
||||
}
|
||||
case VAR_UNKNOWN: {
|
||||
emsgf(_(e_intern2), "tv_get_float(UNKNOWN)");
|
||||
break;
|
||||
@@ -3134,6 +3288,7 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf)
|
||||
case VAR_LIST:
|
||||
case VAR_DICT:
|
||||
case VAR_FLOAT:
|
||||
case VAR_BLOB:
|
||||
case VAR_UNKNOWN: {
|
||||
EMSG(_(str_errors[tv->v_type]));
|
||||
return false;
|
||||
|
@@ -64,6 +64,7 @@ enum ListLenSpecials {
|
||||
typedef struct listvar_S list_T;
|
||||
typedef struct dictvar_S dict_T;
|
||||
typedef struct partial_S partial_T;
|
||||
typedef struct blobvar_S blob_T;
|
||||
|
||||
typedef struct ufunc ufunc_T;
|
||||
|
||||
@@ -123,6 +124,7 @@ typedef enum {
|
||||
VAR_SPECIAL, ///< Special value (null), .v_special
|
||||
///< is used.
|
||||
VAR_PARTIAL, ///< Partial, .v_partial is used.
|
||||
VAR_BLOB, ///< Blob, .v_blob is used.
|
||||
} VarType;
|
||||
|
||||
/// Structure that holds an internal variable value
|
||||
@@ -138,6 +140,7 @@ typedef struct {
|
||||
list_T *v_list; ///< List for VAR_LIST, can be NULL.
|
||||
dict_T *v_dict; ///< Dictionary for VAR_DICT, can be NULL.
|
||||
partial_T *v_partial; ///< Closure: function with args.
|
||||
blob_T *v_blob; ///< Blob for VAR_BLOB, can be NULL.
|
||||
} vval; ///< Actual value.
|
||||
} typval_T;
|
||||
|
||||
@@ -252,6 +255,13 @@ struct dictvar_S {
|
||||
LuaRef lua_table_ref;
|
||||
};
|
||||
|
||||
/// Structure to hold info about a Blob
|
||||
struct blobvar_S {
|
||||
garray_T bv_ga; ///< Growarray with the data.
|
||||
int bv_refcount; ///< Reference count.
|
||||
VarLockStatus bv_lock; ///< VAR_UNLOCKED, VAR_LOCKED, VAR_FIXED.
|
||||
};
|
||||
|
||||
/// Type used for script ID
|
||||
typedef int scid_T;
|
||||
/// Format argument for scid_T
|
||||
@@ -711,6 +721,65 @@ static inline bool tv_dict_is_watched(const dict_T *const d)
|
||||
return d && !QUEUE_EMPTY(&d->watchers);
|
||||
}
|
||||
|
||||
static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b)
|
||||
REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1);
|
||||
|
||||
/// Set a blob as the return value.
|
||||
///
|
||||
/// Increments the reference count.
|
||||
///
|
||||
/// @param[out] tv Object to receive the blob.
|
||||
/// @param[in,out] b Blob to pass to the object.
|
||||
static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b)
|
||||
{
|
||||
tv->v_type = VAR_BLOB;
|
||||
tv->vval.v_blob = b;
|
||||
if (b != NULL) {
|
||||
b->bv_refcount++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int tv_blob_len(const blob_T *const b)
|
||||
REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT;
|
||||
|
||||
/// Get the length of the data in the blob, in bytes.
|
||||
///
|
||||
/// @param[in] b Blob to check.
|
||||
static inline int tv_blob_len(const blob_T *const b)
|
||||
{
|
||||
if (b == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return b->bv_ga.ga_len;
|
||||
}
|
||||
|
||||
static inline char_u tv_blob_get(const blob_T *const b, int idx)
|
||||
REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT;
|
||||
|
||||
/// Get the byte at index `idx` in the blob.
|
||||
///
|
||||
/// @param[in] b Blob to index. Cannot be NULL.
|
||||
/// @param[in] idx Index in a blob. Must be valid.
|
||||
///
|
||||
/// @return Byte value at the given index.
|
||||
static inline char_u tv_blob_get(const blob_T *const b, int idx)
|
||||
{
|
||||
return ((char_u *)b->bv_ga.ga_data)[idx];
|
||||
}
|
||||
|
||||
static inline void tv_blob_set(blob_T *const b, int idx, char_u c)
|
||||
REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL;
|
||||
|
||||
/// Store the byte `c` at index `idx` in the blob.
|
||||
///
|
||||
/// @param[in] b Blob to index. Cannot be NULL.
|
||||
/// @param[in] idx Index in a blob. Must be valid.
|
||||
/// @param[in] c Value to store.
|
||||
static inline void tv_blob_set(blob_T *const b, int idx, char_u c)
|
||||
{
|
||||
((char_u *)b->bv_ga.ga_data)[idx] = c;
|
||||
}
|
||||
|
||||
/// Initialize VimL object
|
||||
///
|
||||
/// Initializes to unlocked VAR_UNKNOWN object.
|
||||
|
@@ -83,6 +83,13 @@
|
||||
/// @param len String length.
|
||||
/// @param type EXT type.
|
||||
|
||||
/// @def TYPVAL_ENCODE_CONV_BLOB
|
||||
/// @brief Macros used to convert a blob
|
||||
///
|
||||
/// @param tv Pointer to typval where value is stored. May not be NULL.
|
||||
/// @param blob Pointer to the blob to convert.
|
||||
/// @param len Blob length.
|
||||
|
||||
/// @def TYPVAL_ENCODE_CONV_FUNC_START
|
||||
/// @brief Macros used when starting to convert a funcref or a partial
|
||||
///
|
||||
@@ -330,6 +337,11 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
|
||||
TYPVAL_ENCODE_CONV_FLOAT(tv, tv->vval.v_float);
|
||||
break;
|
||||
}
|
||||
case VAR_BLOB: {
|
||||
TYPVAL_ENCODE_CONV_BLOB(tv, tv->vval.v_blob,
|
||||
tv_blob_len(tv->vval.v_blob));
|
||||
break;
|
||||
}
|
||||
case VAR_FUNC: {
|
||||
TYPVAL_ENCODE_CONV_FUNC_START(tv, tv->vval.v_string);
|
||||
TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, 0);
|
||||
|
@@ -939,13 +939,18 @@ EXTERN char_u e_readonlyvar[] INIT(= N_(
|
||||
"E46: Cannot change read-only variable \"%.*s\""));
|
||||
EXTERN char_u e_stringreq[] INIT(= N_("E928: String required"));
|
||||
EXTERN char_u e_dictreq[] INIT(= N_("E715: Dictionary required"));
|
||||
EXTERN char_u e_blobidx[] INIT(= N_("E979: Blob index out of range: %" PRId64));
|
||||
EXTERN char_u e_invalblob[] INIT(= N_("E978: Invalid operation for Blob"));
|
||||
EXTERN char_u e_toomanyarg[] INIT(= N_(
|
||||
"E118: Too many arguments for function: %s"));
|
||||
EXTERN char_u e_dictkey[] INIT(= N_(
|
||||
"E716: Key not present in Dictionary: \"%s\""));
|
||||
EXTERN char_u e_listreq[] INIT(= N_("E714: List required"));
|
||||
EXTERN char_u e_listblobreq[] INIT(= N_("E897: List or Blob required"));
|
||||
EXTERN char_u e_listdictarg[] INIT(= N_(
|
||||
"E712: Argument of %s must be a List or Dictionary"));
|
||||
EXTERN char_u e_listdictblobarg[] INIT(= N_(
|
||||
"E896: Argument of %s must be a List, Dictionary or Blob"));
|
||||
EXTERN char_u e_readerrf[] INIT(= N_("E47: Error while reading errorfile"));
|
||||
EXTERN char_u e_sandbox[] INIT(= N_("E48: Not allowed in sandbox"));
|
||||
EXTERN char_u e_secure[] INIT(= N_("E523: Not allowed here"));
|
||||
|
@@ -481,6 +481,14 @@ static bool typval_conv_special = false;
|
||||
#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \
|
||||
TYPVAL_ENCODE_CONV_NIL(tv)
|
||||
|
||||
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
|
||||
do { \
|
||||
const blob_T *const blob_ = (blob); \
|
||||
lua_pushlstring(lstate, \
|
||||
blob_ != NULL ? (const char *)blob_->bv_ga.ga_data : "", \
|
||||
(size_t)(len)); \
|
||||
} while (0)
|
||||
|
||||
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
|
||||
do { \
|
||||
TYPVAL_ENCODE_CONV_NIL(tv); \
|
||||
@@ -579,6 +587,7 @@ static bool typval_conv_special = false;
|
||||
#undef TYPVAL_ENCODE_CONV_STRING
|
||||
#undef TYPVAL_ENCODE_CONV_STR_STRING
|
||||
#undef TYPVAL_ENCODE_CONV_EXT_STRING
|
||||
#undef TYPVAL_ENCODE_CONV_BLOB
|
||||
#undef TYPVAL_ENCODE_CONV_NUMBER
|
||||
#undef TYPVAL_ENCODE_CONV_FLOAT
|
||||
#undef TYPVAL_ENCODE_CONV_FUNC_START
|
||||
|
@@ -1653,6 +1653,13 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer,
|
||||
break;
|
||||
}
|
||||
case kSDItemVariable: {
|
||||
if (entry.data.global_var.value.v_type == VAR_TYPE_BLOB) {
|
||||
// Strings and Blobs both pack as msgpack BINs; differentiate them by
|
||||
// storing an additional VAR_TYPE_BLOB element alongside Blobs
|
||||
list_T *const list = tv_list_alloc(1);
|
||||
tv_list_append_number(list, VAR_TYPE_BLOB);
|
||||
entry.data.global_var.additional_elements = list;
|
||||
}
|
||||
const size_t arr_size = 2 + (size_t)(
|
||||
tv_list_len(entry.data.global_var.additional_elements));
|
||||
msgpack_pack_array(spacker, arr_size);
|
||||
@@ -3937,15 +3944,38 @@ shada_read_next_item_start:
|
||||
entry->data.global_var.name =
|
||||
xmemdupz(unpacked.data.via.array.ptr[0].via.bin.ptr,
|
||||
unpacked.data.via.array.ptr[0].via.bin.size);
|
||||
if (msgpack_to_vim(unpacked.data.via.array.ptr[1],
|
||||
&(entry->data.global_var.value)) == FAIL) {
|
||||
SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, 2,
|
||||
entry->data.global_var.additional_elements,
|
||||
"variable");
|
||||
bool is_blob = false;
|
||||
// A msgpack BIN could be a String or Blob; an additional VAR_TYPE_BLOB
|
||||
// element is stored with Blobs which can be used to differentiate them
|
||||
if (unpacked.data.via.array.ptr[1].type == MSGPACK_OBJECT_BIN) {
|
||||
const listitem_T *type_item
|
||||
= tv_list_first(entry->data.global_var.additional_elements);
|
||||
if (type_item != NULL) {
|
||||
const typval_T *type_tv = TV_LIST_ITEM_TV(type_item);
|
||||
if (type_tv->v_type != VAR_NUMBER
|
||||
|| type_tv->vval.v_number != VAR_TYPE_BLOB) {
|
||||
emsgf(_(READERR("variable", "has wrong variable type")),
|
||||
initial_fpos);
|
||||
goto shada_read_next_item_error;
|
||||
}
|
||||
is_blob = true;
|
||||
}
|
||||
}
|
||||
if (is_blob) {
|
||||
const msgpack_object_bin *const bin
|
||||
= &unpacked.data.via.array.ptr[1].via.bin;
|
||||
blob_T *const blob = tv_blob_alloc();
|
||||
ga_concat_len(&blob->bv_ga, bin->ptr, (size_t)bin->size);
|
||||
tv_blob_set_ret(&entry->data.global_var.value, blob);
|
||||
} else if (msgpack_to_vim(unpacked.data.via.array.ptr[1],
|
||||
&(entry->data.global_var.value)) == FAIL) {
|
||||
emsgf(_(READERR("variable", "has value that cannot "
|
||||
"be converted to the VimL value")), initial_fpos);
|
||||
goto shada_read_next_item_error;
|
||||
}
|
||||
SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, 2,
|
||||
entry->data.global_var.additional_elements,
|
||||
"variable");
|
||||
break;
|
||||
}
|
||||
case kSDItemSubString: {
|
||||
|
349
src/nvim/testdir/test_blob.vim
Normal file
349
src/nvim/testdir/test_blob.vim
Normal file
@@ -0,0 +1,349 @@
|
||||
" Tests for the Blob types
|
||||
|
||||
func TearDown()
|
||||
" Run garbage collection after every test
|
||||
call test_garbagecollect_now()
|
||||
endfunc
|
||||
|
||||
" Tests for Blob type
|
||||
|
||||
" Blob creation from constant
|
||||
func Test_blob_create()
|
||||
let b = 0zDEADBEEF
|
||||
call assert_equal(v:t_blob, type(b))
|
||||
call assert_equal(4, len(b))
|
||||
call assert_equal(0xDE, b[0])
|
||||
call assert_equal(0xAD, b[1])
|
||||
call assert_equal(0xBE, b[2])
|
||||
call assert_equal(0xEF, b[3])
|
||||
call assert_fails('let x = b[4]')
|
||||
|
||||
call assert_equal(0xDE, get(b, 0))
|
||||
call assert_equal(0xEF, get(b, 3))
|
||||
|
||||
call assert_fails('let b = 0z1', 'E973:')
|
||||
call assert_fails('let b = 0z1x', 'E973:')
|
||||
call assert_fails('let b = 0z12345', 'E973:')
|
||||
|
||||
call assert_equal(0z, v:_null_blob)
|
||||
|
||||
let b = 0z001122.33445566.778899.aabbcc.dd
|
||||
call assert_equal(0z00112233445566778899aabbccdd, b)
|
||||
call assert_fails('let b = 0z1.1')
|
||||
call assert_fails('let b = 0z.')
|
||||
call assert_fails('let b = 0z001122.')
|
||||
call assert_fails('call get("", 1)', 'E896:')
|
||||
call assert_equal(0, len(v:_null_blob))
|
||||
endfunc
|
||||
|
||||
" assignment to a blob
|
||||
func Test_blob_assign()
|
||||
let b = 0zDEADBEEF
|
||||
let b2 = b[1:2]
|
||||
call assert_equal(0zADBE, b2)
|
||||
|
||||
let bcopy = b[:]
|
||||
call assert_equal(b, bcopy)
|
||||
call assert_false(b is bcopy)
|
||||
|
||||
let b = 0zDEADBEEF
|
||||
let b2 = b
|
||||
call assert_true(b is b2)
|
||||
let b[:] = 0z11223344
|
||||
call assert_equal(0z11223344, b)
|
||||
call assert_equal(0z11223344, b2)
|
||||
call assert_true(b is b2)
|
||||
|
||||
let b = 0zDEADBEEF
|
||||
let b[3:] = 0z66
|
||||
call assert_equal(0zDEADBE66, b)
|
||||
let b[:1] = 0z8899
|
||||
call assert_equal(0z8899BE66, b)
|
||||
|
||||
call assert_fails('let b[2:3] = 0z112233', 'E972:')
|
||||
call assert_fails('let b[2:3] = 0z11', 'E972:')
|
||||
call assert_fails('let b[3:2] = 0z', 'E979:')
|
||||
|
||||
let b = 0zDEADBEEF
|
||||
let b += 0z99
|
||||
call assert_equal(0zDEADBEEF99, b)
|
||||
|
||||
call assert_fails('let b .= 0z33', 'E734:')
|
||||
call assert_fails('let b .= "xx"', 'E734:')
|
||||
call assert_fails('let b += "xx"', 'E734:')
|
||||
call assert_fails('let b[1:1] .= 0z55', 'E734:')
|
||||
|
||||
let l = [0z12]
|
||||
let m = deepcopy(l)
|
||||
let m[0] = 0z34 " E742 or E741 should not occur.
|
||||
endfunc
|
||||
|
||||
func Test_blob_get_range()
|
||||
let b = 0z0011223344
|
||||
call assert_equal(0z2233, b[2:3])
|
||||
call assert_equal(0z223344, b[2:-1])
|
||||
call assert_equal(0z00, b[0:-5])
|
||||
call assert_equal(0z, b[0:-11])
|
||||
call assert_equal(0z44, b[-1:])
|
||||
call assert_equal(0z0011223344, b[:])
|
||||
call assert_equal(0z0011223344, b[:-1])
|
||||
call assert_equal(0z, b[5:6])
|
||||
endfunc
|
||||
|
||||
func Test_blob_get()
|
||||
let b = 0z0011223344
|
||||
call assert_equal(0x00, get(b, 0))
|
||||
call assert_equal(0x22, get(b, 2, 999))
|
||||
call assert_equal(0x44, get(b, 4))
|
||||
call assert_equal(0x44, get(b, -1))
|
||||
call assert_equal(-1, get(b, 5))
|
||||
call assert_equal(999, get(b, 5, 999))
|
||||
call assert_equal(-1, get(b, -8))
|
||||
call assert_equal(999, get(b, -8, 999))
|
||||
call assert_equal(10, get(v:_null_blob, 2, 10))
|
||||
|
||||
call assert_equal(0x00, b[0])
|
||||
call assert_equal(0x22, b[2])
|
||||
call assert_equal(0x44, b[4])
|
||||
call assert_equal(0x44, b[-1])
|
||||
call assert_fails('echo b[5]', 'E979:')
|
||||
call assert_fails('echo b[-8]', 'E979:')
|
||||
endfunc
|
||||
|
||||
func Test_blob_to_string()
|
||||
let b = 0z00112233445566778899aabbccdd
|
||||
call assert_equal('0z00112233.44556677.8899AABB.CCDD', string(b))
|
||||
call assert_equal(b, eval(string(b)))
|
||||
call remove(b, 4, -1)
|
||||
call assert_equal('0z00112233', string(b))
|
||||
call remove(b, 0, 3)
|
||||
call assert_equal('0z', string(b))
|
||||
endfunc
|
||||
|
||||
func Test_blob_compare()
|
||||
let b1 = 0z0011
|
||||
let b2 = 0z1100
|
||||
let b3 = 0z001122
|
||||
call assert_true(b1 == b1)
|
||||
call assert_false(b1 == b2)
|
||||
call assert_false(b1 == b3)
|
||||
call assert_true(b1 != b2)
|
||||
call assert_true(b1 != b3)
|
||||
call assert_true(b1 == 0z0011)
|
||||
call assert_fails('echo b1 == 9', 'E977:')
|
||||
call assert_fails('echo b1 != 9', 'E977:')
|
||||
|
||||
call assert_false(b1 is b2)
|
||||
let b2 = b1
|
||||
call assert_true(b1 == b2)
|
||||
call assert_true(b1 is b2)
|
||||
let b2 = copy(b1)
|
||||
call assert_true(b1 == b2)
|
||||
call assert_false(b1 is b2)
|
||||
let b2 = b1[:]
|
||||
call assert_true(b1 == b2)
|
||||
call assert_false(b1 is b2)
|
||||
|
||||
call assert_fails('let x = b1 > b2')
|
||||
call assert_fails('let x = b1 < b2')
|
||||
call assert_fails('let x = b1 - b2')
|
||||
call assert_fails('let x = b1 / b2')
|
||||
call assert_fails('let x = b1 * b2')
|
||||
endfunc
|
||||
|
||||
" test for range assign
|
||||
func Test_blob_range_assign()
|
||||
let b = 0z00
|
||||
let b[1] = 0x11
|
||||
let b[2] = 0x22
|
||||
call assert_equal(0z001122, b)
|
||||
call assert_fails('let b[4] = 0x33', 'E979:')
|
||||
endfunc
|
||||
|
||||
func Test_blob_for_loop()
|
||||
let blob = 0z00010203
|
||||
let i = 0
|
||||
for byte in blob
|
||||
call assert_equal(i, byte)
|
||||
let i += 1
|
||||
endfor
|
||||
call assert_equal(4, i)
|
||||
|
||||
let blob = 0z00
|
||||
call remove(blob, 0)
|
||||
call assert_equal(0, len(blob))
|
||||
for byte in blob
|
||||
call assert_error('loop over empty blob')
|
||||
endfor
|
||||
|
||||
let blob = 0z0001020304
|
||||
let i = 0
|
||||
for byte in blob
|
||||
call assert_equal(i, byte)
|
||||
if i == 1
|
||||
call remove(blob, 0)
|
||||
elseif i == 3
|
||||
call remove(blob, 3)
|
||||
endif
|
||||
let i += 1
|
||||
endfor
|
||||
call assert_equal(5, i)
|
||||
endfunc
|
||||
|
||||
func Test_blob_concatenate()
|
||||
let b = 0z0011
|
||||
let b += 0z2233
|
||||
call assert_equal(0z00112233, b)
|
||||
|
||||
call assert_fails('let b += "a"')
|
||||
call assert_fails('let b += 88')
|
||||
|
||||
let b = 0zDEAD + 0zBEEF
|
||||
call assert_equal(0zDEADBEEF, b)
|
||||
endfunc
|
||||
|
||||
func Test_blob_add()
|
||||
let b = 0z0011
|
||||
call add(b, 0x22)
|
||||
call assert_equal(0z001122, b)
|
||||
call add(b, '51')
|
||||
call assert_equal(0z00112233, b)
|
||||
|
||||
call assert_fails('call add(b, [9])', 'E745:')
|
||||
call assert_fails('call add("", 0x01)', 'E897:')
|
||||
endfunc
|
||||
|
||||
func Test_blob_empty()
|
||||
call assert_false(empty(0z001122))
|
||||
call assert_true(empty(0z))
|
||||
call assert_true(empty(v:_null_blob))
|
||||
endfunc
|
||||
|
||||
" Test removing items in blob
|
||||
func Test_blob_func_remove()
|
||||
" Test removing 1 element
|
||||
let b = 0zDEADBEEF
|
||||
call assert_equal(0xDE, remove(b, 0))
|
||||
call assert_equal(0zADBEEF, b)
|
||||
|
||||
let b = 0zDEADBEEF
|
||||
call assert_equal(0xEF, remove(b, -1))
|
||||
call assert_equal(0zDEADBE, b)
|
||||
|
||||
let b = 0zDEADBEEF
|
||||
call assert_equal(0xAD, remove(b, 1))
|
||||
call assert_equal(0zDEBEEF, b)
|
||||
|
||||
" Test removing range of element(s)
|
||||
let b = 0zDEADBEEF
|
||||
call assert_equal(0zBE, remove(b, 2, 2))
|
||||
call assert_equal(0zDEADEF, b)
|
||||
|
||||
let b = 0zDEADBEEF
|
||||
call assert_equal(0zADBE, remove(b, 1, 2))
|
||||
call assert_equal(0zDEEF, b)
|
||||
|
||||
" Test invalid cases
|
||||
let b = 0zDEADBEEF
|
||||
call assert_fails("call remove(b, 5)", 'E979:')
|
||||
call assert_fails("call remove(b, 1, 5)", 'E979:')
|
||||
call assert_fails("call remove(b, 3, 2)", 'E979:')
|
||||
call assert_fails("call remove(1, 0)", 'E896:')
|
||||
call assert_fails("call remove(b, b)", 'E974:')
|
||||
call assert_fails("call remove(v:_null_blob, 1, 2)", 'E979:')
|
||||
|
||||
" Translated from v8.2.3284
|
||||
let b = 0zDEADBEEF
|
||||
lockvar b
|
||||
call assert_fails('call remove(b, 0)', 'E741:')
|
||||
unlockvar b
|
||||
endfunc
|
||||
|
||||
func Test_blob_read_write()
|
||||
let b = 0zDEADBEEF
|
||||
call writefile(b, 'Xblob')
|
||||
let br = readfile('Xblob', 'B')
|
||||
call assert_equal(b, br)
|
||||
call delete('Xblob')
|
||||
|
||||
" This was crashing when calling readfile() with a directory.
|
||||
call assert_fails("call readfile('.', 'B')", 'E17: "." is a directory')
|
||||
endfunc
|
||||
|
||||
" filter() item in blob
|
||||
func Test_blob_filter()
|
||||
call assert_equal(0z, filter(0zDEADBEEF, '0'))
|
||||
call assert_equal(0zADBEEF, filter(0zDEADBEEF, 'v:val != 0xDE'))
|
||||
call assert_equal(0zDEADEF, filter(0zDEADBEEF, 'v:val != 0xBE'))
|
||||
call assert_equal(0zDEADBE, filter(0zDEADBEEF, 'v:val != 0xEF'))
|
||||
call assert_equal(0zDEADBEEF, filter(0zDEADBEEF, '1'))
|
||||
call assert_equal(0z01030103, filter(0z010203010203, 'v:val != 0x02'))
|
||||
call assert_equal(0zADEF, filter(0zDEADBEEF, 'v:key % 2'))
|
||||
endfunc
|
||||
|
||||
" map() item in blob
|
||||
func Test_blob_map()
|
||||
call assert_equal(0zDFAEBFF0, map(0zDEADBEEF, 'v:val + 1'))
|
||||
call assert_equal(0z00010203, map(0zDEADBEEF, 'v:key'))
|
||||
call assert_equal(0zDEAEC0F2, map(0zDEADBEEF, 'v:key + v:val'))
|
||||
|
||||
call assert_fails("call map(0z00, '[9]')", 'E978:')
|
||||
endfunc
|
||||
|
||||
func Test_blob_index()
|
||||
call assert_equal(2, index(0zDEADBEEF, 0xBE))
|
||||
call assert_equal(-1, index(0zDEADBEEF, 0))
|
||||
call assert_equal(2, index(0z11111111, 0x11, 2))
|
||||
call assert_equal(3, index(0z11110111, 0x11, 2))
|
||||
call assert_equal(2, index(0z11111111, 0x11, -2))
|
||||
call assert_equal(3, index(0z11110111, 0x11, -2))
|
||||
|
||||
call assert_fails('call index("asdf", 0)', 'E897:')
|
||||
endfunc
|
||||
|
||||
func Test_blob_insert()
|
||||
let b = 0zDEADBEEF
|
||||
call insert(b, 0x33)
|
||||
call assert_equal(0z33DEADBEEF, b)
|
||||
|
||||
let b = 0zDEADBEEF
|
||||
call insert(b, 0x33, 2)
|
||||
call assert_equal(0zDEAD33BEEF, b)
|
||||
|
||||
call assert_fails('call insert(b, -1)', 'E475:')
|
||||
call assert_fails('call insert(b, 257)', 'E475:')
|
||||
call assert_fails('call insert(b, 0, [9])', 'E745:')
|
||||
call assert_equal(0, insert(v:_null_blob, 0x33))
|
||||
|
||||
" Translated from v8.2.3284
|
||||
let b = 0zDEADBEEF
|
||||
lockvar b
|
||||
call assert_fails('call insert(b, 3)', 'E741:')
|
||||
unlockvar b
|
||||
endfunc
|
||||
|
||||
func Test_blob_reverse()
|
||||
call assert_equal(0zEFBEADDE, reverse(0zDEADBEEF))
|
||||
call assert_equal(0zBEADDE, reverse(0zDEADBE))
|
||||
call assert_equal(0zADDE, reverse(0zDEAD))
|
||||
call assert_equal(0zDE, reverse(0zDE))
|
||||
call assert_equal(0z, reverse(v:_null_blob))
|
||||
endfunc
|
||||
|
||||
func Test_blob_lock()
|
||||
let b = 0z112233
|
||||
lockvar b
|
||||
call assert_fails('let b = 0z44', 'E741:')
|
||||
unlockvar b
|
||||
let b = 0z44
|
||||
endfunc
|
||||
|
||||
func Test_blob_sort()
|
||||
if has('float')
|
||||
call assert_fails('call sort([1.0, 0z11], "f")', 'E975:')
|
||||
else
|
||||
call assert_fails('call sort(["abc", 0z11], "f")', 'E702:')
|
||||
endif
|
||||
endfunc
|
||||
|
||||
" vim: shiftwidth=2 sts=2 expandtab
|
@@ -244,18 +244,33 @@ func Test_const_with_eval_name()
|
||||
call assert_fails('const {s2} = "bar"', 'E995:')
|
||||
endfunc
|
||||
|
||||
func Test_lock_depth_is_1()
|
||||
const l = [1, 2, 3]
|
||||
const d = {'foo': 10}
|
||||
|
||||
" Modify list - setting item is OK, adding/removing items not
|
||||
let l[0] = 42
|
||||
func Test_lock_depth_is_2()
|
||||
" Modify list - error when changing item or adding/removing items
|
||||
const l = [1, 2, [3, 4]]
|
||||
call assert_fails('let l[0] = 42', 'E741:')
|
||||
call assert_fails('let l[2][0] = 42', 'E741:')
|
||||
call assert_fails('call add(l, 4)', 'E741:')
|
||||
call assert_fails('unlet l[1]', 'E741:')
|
||||
|
||||
" Modify dict - changing item is OK, adding/removing items not
|
||||
let d['foo'] = 'hello'
|
||||
let d.foo = 44
|
||||
" Modify blob - error when changing
|
||||
const b = 0z001122
|
||||
call assert_fails('let b[0] = 42', 'E741:')
|
||||
|
||||
" Modify dict - error when changing item or adding/removing items
|
||||
const d = {'foo': 10}
|
||||
call assert_fails("let d['foo'] = 'hello'", 'E741:')
|
||||
call assert_fails("let d.foo = 'hello'", 'E741:')
|
||||
call assert_fails("let d['bar'] = 'hello'", 'E741:')
|
||||
call assert_fails("unlet d['foo']", 'E741:')
|
||||
|
||||
" Modifying list or dict item contents is OK.
|
||||
let lvar = ['a', 'b']
|
||||
let bvar = 0z1122
|
||||
const l2 = [0, lvar, bvar]
|
||||
let l2[1][0] = 'c'
|
||||
let l2[2][1] = 0x33
|
||||
call assert_equal([0, ['c', 'b'], 0z1133], l2)
|
||||
|
||||
const d2 = #{a: 0, b: lvar, c: 4}
|
||||
let d2.b[1] = 'd'
|
||||
endfunc
|
||||
|
@@ -65,9 +65,11 @@ func Test_E963()
|
||||
endfunc
|
||||
|
||||
func Test_for_invalid()
|
||||
call assert_fails("for x in 99", 'E714:')
|
||||
call assert_fails("for x in function('winnr')", 'E714:')
|
||||
call assert_fails("for x in {'a': 9}", 'E714:')
|
||||
" Vim gives incorrect emsg here until v8.2.3284, but the exact emsg from that
|
||||
" patch cannot be used until v8.2.2658 is ported (for loop over Strings)
|
||||
call assert_fails("for x in 99", 'E897:')
|
||||
call assert_fails("for x in function('winnr')", 'E897:')
|
||||
call assert_fails("for x in {'a': 9}", 'E897:')
|
||||
|
||||
if 0
|
||||
/1/5/2/s/\n
|
||||
|
@@ -81,7 +81,11 @@ func Test_filter_map_dict_expr_funcref()
|
||||
call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
|
||||
endfunc
|
||||
|
||||
func Test_map_fails()
|
||||
func Test_map_filter_fails()
|
||||
call assert_fails('call map([1], "42 +")', 'E15:')
|
||||
call assert_fails('call filter([1], "42 +")', 'E15:')
|
||||
call assert_fails("let l = map('abc', '\"> \" . v:val')", 'E896:')
|
||||
call assert_fails("let l = filter('abc', '\"> \" . v:val')", 'E896:')
|
||||
endfunc
|
||||
|
||||
" vim: shiftwidth=2 sts=2 expandtab
|
||||
|
@@ -72,4 +72,8 @@ func Test_fnamemodify_er()
|
||||
" :e never includes the whole filename, so "a.b":e:e:e --> "b"
|
||||
call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e'))
|
||||
call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e:e'))
|
||||
|
||||
call assert_equal('', fnamemodify(v:_null_string, v:_null_string))
|
||||
endfunc
|
||||
|
||||
" vim: shiftwidth=2 sts=2 expandtab
|
||||
|
@@ -139,7 +139,7 @@ func Test_list_func_remove()
|
||||
call assert_fails("call remove(l, 5)", 'E684:')
|
||||
call assert_fails("call remove(l, 1, 5)", 'E684:')
|
||||
call assert_fails("call remove(l, 3, 2)", 'E16:')
|
||||
call assert_fails("call remove(1, 0)", 'E712:')
|
||||
call assert_fails("call remove(1, 0)", 'E896:')
|
||||
call assert_fails("call remove(l, l)", 'E745:')
|
||||
endfunc
|
||||
|
||||
@@ -616,6 +616,8 @@ func Test_reverse_sort_uniq()
|
||||
call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 1))
|
||||
call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i'))
|
||||
call assert_equal(['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l)))
|
||||
|
||||
call assert_fails('call reverse("")', 'E899:')
|
||||
endfunc
|
||||
|
||||
" splitting a string to a List
|
||||
|
@@ -46,11 +46,8 @@ func Test_dict_method()
|
||||
call assert_equal(#{one: 1, two: 2, three: 3, four: 4}, d->extend(#{four: 4}))
|
||||
call assert_equal(#{one: 1, two: 2, three: 3}, d->filter('v:val != 4'))
|
||||
call assert_equal(2, d->get('two'))
|
||||
" Nvim doesn't support Blobs yet; expect a different emsg
|
||||
" call assert_fails("let x = d->index(2)", 'E897:')
|
||||
" call assert_fails("let x = d->insert(0)", 'E899:')
|
||||
call assert_fails("let x = d->index(2)", 'E714:')
|
||||
call assert_fails("let x = d->insert(0)", 'E686:')
|
||||
call assert_fails("let x = d->index(2)", 'E897:')
|
||||
call assert_fails("let x = d->insert(0)", 'E899:')
|
||||
call assert_true(d->has_key('two'))
|
||||
call assert_equal([['one', 1], ['two', 2], ['three', 3]], d->items())
|
||||
call assert_fails("let x = d->join()", 'E714:')
|
||||
@@ -63,9 +60,7 @@ func Test_dict_method()
|
||||
call assert_equal(2, d->remove("two"))
|
||||
let d.two = 2
|
||||
call assert_fails('let x = d->repeat(2)', 'E731:')
|
||||
" Nvim doesn't support Blobs yet; expect a different emsg
|
||||
" call assert_fails('let x = d->reverse()', 'E899:')
|
||||
call assert_fails('let x = d->reverse()', 'E686:')
|
||||
call assert_fails('let x = d->reverse()', 'E899:')
|
||||
call assert_fails('let x = d->sort()', 'E686:')
|
||||
call assert_equal("{'one': 1, 'two': 2, 'three': 3}", d->string())
|
||||
call assert_equal(v:t_dict, d->type())
|
||||
|
@@ -2221,6 +2221,10 @@ func Xproperty_tests(cchar)
|
||||
call g:Xsetlist([], 'a', {'context':246})
|
||||
let d = g:Xgetlist({'context':1})
|
||||
call assert_equal(246, d.context)
|
||||
" set other Vim data types as context
|
||||
call g:Xsetlist([], 'a', {'context' : v:_null_blob})
|
||||
call g:Xsetlist([], 'a', {'context' : ''})
|
||||
call test_garbagecollect_now()
|
||||
if a:cchar == 'l'
|
||||
" Test for copying context across two different location lists
|
||||
new | only
|
||||
|
@@ -95,7 +95,6 @@ func Test_rename_copy()
|
||||
endfunc
|
||||
|
||||
func Test_rename_fails()
|
||||
throw 'skipped: TODO: '
|
||||
call writefile(['foo'], 'Xrenamefile')
|
||||
|
||||
" Can't rename into a non-existing directory.
|
||||
|
@@ -168,7 +168,6 @@ func Test_swapname()
|
||||
endfunc
|
||||
|
||||
func Test_swapfile_delete()
|
||||
throw 'skipped: need the "blob" feature for this test'
|
||||
autocmd! SwapExists
|
||||
function s:swap_exists()
|
||||
let v:swapchoice = s:swap_choice
|
||||
|
@@ -368,7 +368,6 @@ endfunc
|
||||
|
||||
" Check that reading a truncted undo file doesn't hang.
|
||||
func Test_undofile_truncated()
|
||||
throw 'skipped: TODO: '
|
||||
new
|
||||
call setline(1, 'hello')
|
||||
set ul=100
|
||||
|
@@ -1152,6 +1152,10 @@ func Test_type()
|
||||
call assert_equal(v:t_float, type(0.0))
|
||||
call assert_equal(v:t_bool, type(v:false))
|
||||
call assert_equal(v:t_bool, type(v:true))
|
||||
call assert_equal(v:t_string, type(v:_null_string))
|
||||
call assert_equal(v:t_list, type(v:_null_list))
|
||||
call assert_equal(v:t_dict, type(v:_null_dict))
|
||||
call assert_equal(v:t_blob, type(v:_null_blob))
|
||||
endfunc
|
||||
|
||||
"-------------------------------------------------------------------------------
|
||||
|
@@ -17,6 +17,8 @@ func Test_writefile()
|
||||
call assert_equal("morning", l[3])
|
||||
call assert_equal("vimmers", l[4])
|
||||
call delete(f)
|
||||
|
||||
call assert_fails('call writefile("text", "Xfile")', 'E475: Invalid argument: writefile() first argument must be a List or a Blob')
|
||||
endfunc
|
||||
|
||||
func Test_writefile_ignore_regexp_error()
|
||||
|
@@ -102,6 +102,7 @@ typedef enum {
|
||||
#define VAR_TYPE_FLOAT 5
|
||||
#define VAR_TYPE_BOOL 6
|
||||
#define VAR_TYPE_SPECIAL 7
|
||||
#define VAR_TYPE_BLOB 10
|
||||
|
||||
|
||||
// values for xp_context when doing command line completion
|
||||
|
Reference in New Issue
Block a user