mirror of
https://github.com/neovim/neovim.git
synced 2025-01-01 17:23:36 -07:00
vim-patch:8.2.0602: :unlet $VAR does not work properly (#13238)
Problem: :unlet $VAR does not work properly.
Solution: Make ":lockvar $VAR" fail. Check the "skip" flag.
7e0868efcf
Include patch 8.2.0601 changes so that ex_unletlock() can execute a callback if there are no errors.
This commit is contained in:
parent
d80f262f89
commit
8fb786e415
135
src/nvim/eval.c
135
src/nvim/eval.c
@ -64,6 +64,7 @@ static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary");
|
|||||||
static char *e_illvar = N_("E461: Illegal variable name: %s");
|
static char *e_illvar = N_("E461: Illegal variable name: %s");
|
||||||
static char *e_cannot_mod = N_("E995: Cannot modify existing variable");
|
static char *e_cannot_mod = N_("E995: Cannot modify existing variable");
|
||||||
static char *e_invalwindow = N_("E957: Invalid window number");
|
static char *e_invalwindow = N_("E957: Invalid window number");
|
||||||
|
static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
|
||||||
|
|
||||||
// TODO(ZyX-I): move to eval/executor
|
// TODO(ZyX-I): move to eval/executor
|
||||||
static char *e_letwrong = N_("E734: Wrong variable type for %s=");
|
static char *e_letwrong = N_("E734: Wrong variable type for %s=");
|
||||||
@ -2638,22 +2639,18 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx)
|
|||||||
xp->xp_pattern = arg;
|
xp->xp_pattern = arg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// ":unlet[!] var1 ... " command.
|
||||||
* ":unlet[!] var1 ... " command.
|
|
||||||
*/
|
|
||||||
void ex_unlet(exarg_T *eap)
|
void ex_unlet(exarg_T *eap)
|
||||||
{
|
{
|
||||||
ex_unletlock(eap, eap->arg, 0);
|
ex_unletlock(eap, eap->arg, 0, do_unlet_var);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(ZyX-I): move to eval/ex_cmds
|
// TODO(ZyX-I): move to eval/ex_cmds
|
||||||
|
|
||||||
/*
|
/// ":lockvar" and ":unlockvar" commands
|
||||||
* ":lockvar" and ":unlockvar" commands
|
|
||||||
*/
|
|
||||||
void ex_lockvar(exarg_T *eap)
|
void ex_lockvar(exarg_T *eap)
|
||||||
{
|
{
|
||||||
char_u *arg = eap->arg;
|
char_u *arg = eap->arg;
|
||||||
int deep = 2;
|
int deep = 2;
|
||||||
|
|
||||||
if (eap->forceit) {
|
if (eap->forceit) {
|
||||||
@ -2663,30 +2660,41 @@ void ex_lockvar(exarg_T *eap)
|
|||||||
arg = skipwhite(arg);
|
arg = skipwhite(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
ex_unletlock(eap, arg, deep);
|
ex_unletlock(eap, arg, deep, do_lock_var);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(ZyX-I): move to eval/ex_cmds
|
// TODO(ZyX-I): move to eval/ex_cmds
|
||||||
|
|
||||||
/*
|
/// Common parsing logic for :unlet, :lockvar and :unlockvar.
|
||||||
* ":unlet", ":lockvar" and ":unlockvar" are quite similar.
|
///
|
||||||
*/
|
/// Invokes `callback` afterwards if successful and `eap->skip == false`.
|
||||||
static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep)
|
///
|
||||||
|
/// @param[in] eap Ex command arguments for the command.
|
||||||
|
/// @param[in] argstart Start of the string argument for the command.
|
||||||
|
/// @param[in] deep Levels to (un)lock for :(un)lockvar, -1 to (un)lock
|
||||||
|
/// everything.
|
||||||
|
/// @param[in] callback Appropriate handler for the command.
|
||||||
|
static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep,
|
||||||
|
ex_unletlock_callback callback)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
char_u *arg = argstart;
|
char_u *arg = argstart;
|
||||||
char_u *name_end;
|
char_u *name_end;
|
||||||
bool error = false;
|
bool error = false;
|
||||||
lval_T lv;
|
lval_T lv;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (*arg == '$') {
|
if (*arg == '$') {
|
||||||
const char *name = (char *)++arg;
|
lv.ll_name = (const char *)arg;
|
||||||
|
lv.ll_tv = NULL;
|
||||||
|
arg++;
|
||||||
if (get_env_len((const char_u **)&arg) == 0) {
|
if (get_env_len((const char_u **)&arg) == 0) {
|
||||||
EMSG2(_(e_invarg2), name - 1);
|
EMSG2(_(e_invarg2), arg - 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
os_unsetenv(name);
|
if (!error && !eap->skip && callback(&lv, arg, eap, deep) == FAIL) {
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
name_end = arg;
|
name_end = arg;
|
||||||
} else {
|
} else {
|
||||||
// Parse the name and find the end.
|
// Parse the name and find the end.
|
||||||
@ -2707,17 +2715,8 @@ static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!error && !eap->skip) {
|
if (!error && !eap->skip && callback(&lv, name_end, eap, deep) == FAIL) {
|
||||||
if (eap->cmdidx == CMD_unlet) {
|
error = true;
|
||||||
if (do_unlet_var(&lv, name_end, eap->forceit) == FAIL) {
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (do_lock_var(&lv, name_end, deep,
|
|
||||||
eap->cmdidx == CMD_lockvar) == FAIL) {
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!eap->skip) {
|
if (!eap->skip) {
|
||||||
@ -2732,8 +2731,19 @@ static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep)
|
|||||||
|
|
||||||
// TODO(ZyX-I): move to eval/ex_cmds
|
// TODO(ZyX-I): move to eval/ex_cmds
|
||||||
|
|
||||||
static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit)
|
/// Unlet a variable indicated by `lp`.
|
||||||
|
///
|
||||||
|
/// @param[in] lp The lvalue.
|
||||||
|
/// @param[in] name_end End of the string argument for the command.
|
||||||
|
/// @param[in] eap Ex command arguments for :unlet.
|
||||||
|
/// @param[in] deep Unused.
|
||||||
|
///
|
||||||
|
/// @return OK on success, or FAIL on failure.
|
||||||
|
static int do_unlet_var(lval_T *lp, char_u *name_end, exarg_T *eap,
|
||||||
|
int deep FUNC_ATTR_UNUSED)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
|
int forceit = eap->forceit;
|
||||||
int ret = OK;
|
int ret = OK;
|
||||||
int cc;
|
int cc;
|
||||||
|
|
||||||
@ -2741,8 +2751,10 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit)
|
|||||||
cc = *name_end;
|
cc = *name_end;
|
||||||
*name_end = NUL;
|
*name_end = NUL;
|
||||||
|
|
||||||
// Normal name or expanded name.
|
// Environment variable, normal name or expanded name.
|
||||||
if (do_unlet(lp->ll_name, lp->ll_name_len, forceit) == FAIL) {
|
if (*lp->ll_name == '$') {
|
||||||
|
os_unsetenv(lp->ll_name + 1);
|
||||||
|
} else if (do_unlet(lp->ll_name, lp->ll_name_len, forceit) == FAIL) {
|
||||||
ret = FAIL;
|
ret = FAIL;
|
||||||
}
|
}
|
||||||
*name_end = cc;
|
*name_end = cc;
|
||||||
@ -2816,7 +2828,7 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit)
|
|||||||
///
|
///
|
||||||
/// @param[in] name Variable name to unlet.
|
/// @param[in] name Variable name to unlet.
|
||||||
/// @param[in] name_len Variable name length.
|
/// @param[in] name_len Variable name length.
|
||||||
/// @param[in] fonceit If true, do not complain if variable doesn’t exist.
|
/// @param[in] forceit If true, do not complain if variable doesn’t exist.
|
||||||
///
|
///
|
||||||
/// @return OK if it existed, FAIL otherwise.
|
/// @return OK if it existed, FAIL otherwise.
|
||||||
int do_unlet(const char *const name, const size_t name_len, const bool forceit)
|
int do_unlet(const char *const name, const size_t name_len, const bool forceit)
|
||||||
@ -2883,14 +2895,21 @@ int do_unlet(const char *const name, const size_t name_len, const bool forceit)
|
|||||||
|
|
||||||
// TODO(ZyX-I): move to eval/ex_cmds
|
// TODO(ZyX-I): move to eval/ex_cmds
|
||||||
|
|
||||||
/*
|
/// Lock or unlock variable indicated by `lp`.
|
||||||
* Lock or unlock variable indicated by "lp".
|
///
|
||||||
* "deep" is the levels to go (-1 for unlimited);
|
/// Locks if `eap->cmdidx == CMD_lockvar`, unlocks otherwise.
|
||||||
* "lock" is TRUE for ":lockvar", FALSE for ":unlockvar".
|
///
|
||||||
*/
|
/// @param[in] lp The lvalue.
|
||||||
static int do_lock_var(lval_T *lp, char_u *const name_end, const int deep,
|
/// @param[in] name_end Unused.
|
||||||
const bool lock)
|
/// @param[in] eap Ex command arguments for :(un)lockvar.
|
||||||
|
/// @param[in] deep Levels to (un)lock, -1 to (un)lock everything.
|
||||||
|
///
|
||||||
|
/// @return OK on success, or FAIL on failure.
|
||||||
|
static int do_lock_var(lval_T *lp, char_u *name_end FUNC_ATTR_UNUSED,
|
||||||
|
exarg_T *eap, int deep)
|
||||||
|
FUNC_ATTR_NONNULL_ARG(1, 3)
|
||||||
{
|
{
|
||||||
|
bool lock = eap->cmdidx == CMD_lockvar;
|
||||||
int ret = OK;
|
int ret = OK;
|
||||||
|
|
||||||
if (deep == 0) { // Nothing to do.
|
if (deep == 0) { // Nothing to do.
|
||||||
@ -2898,25 +2917,31 @@ static int do_lock_var(lval_T *lp, char_u *const name_end, const int deep,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lp->ll_tv == NULL) {
|
if (lp->ll_tv == NULL) {
|
||||||
// Normal name or expanded name.
|
if (*lp->ll_name == '$') {
|
||||||
dictitem_T *const di = find_var(
|
EMSG2(_(e_lock_unlock), lp->ll_name);
|
||||||
(const char *)lp->ll_name, lp->ll_name_len, NULL,
|
|
||||||
true);
|
|
||||||
if (di == NULL) {
|
|
||||||
ret = FAIL;
|
ret = FAIL;
|
||||||
} else if ((di->di_flags & DI_FLAGS_FIX)
|
|
||||||
&& di->di_tv.v_type != VAR_DICT
|
|
||||||
&& di->di_tv.v_type != VAR_LIST) {
|
|
||||||
// For historical reasons this error is not given for Lists and
|
|
||||||
// Dictionaries. E.g. b: dictionary may be locked/unlocked.
|
|
||||||
emsgf(_("E940: Cannot lock or unlock variable %s"), lp->ll_name);
|
|
||||||
} else {
|
} else {
|
||||||
if (lock) {
|
// Normal name or expanded name.
|
||||||
di->di_flags |= DI_FLAGS_LOCK;
|
dictitem_T *const di = find_var(
|
||||||
|
(const char *)lp->ll_name, lp->ll_name_len, NULL,
|
||||||
|
true);
|
||||||
|
if (di == NULL) {
|
||||||
|
ret = FAIL;
|
||||||
|
} else if ((di->di_flags & DI_FLAGS_FIX)
|
||||||
|
&& di->di_tv.v_type != VAR_DICT
|
||||||
|
&& di->di_tv.v_type != VAR_LIST) {
|
||||||
|
// For historical reasons this error is not given for Lists and
|
||||||
|
// Dictionaries. E.g. b: dictionary may be locked/unlocked.
|
||||||
|
EMSG2(_(e_lock_unlock), lp->ll_name);
|
||||||
|
ret = FAIL;
|
||||||
} else {
|
} else {
|
||||||
di->di_flags &= ~DI_FLAGS_LOCK;
|
if (lock) {
|
||||||
|
di->di_flags |= DI_FLAGS_LOCK;
|
||||||
|
} else {
|
||||||
|
di->di_flags &= ~DI_FLAGS_LOCK;
|
||||||
|
}
|
||||||
|
tv_item_lock(&di->di_tv, deep, lock);
|
||||||
}
|
}
|
||||||
tv_item_lock(&di->di_tv, deep, lock);
|
|
||||||
}
|
}
|
||||||
} else if (lp->ll_range) {
|
} else if (lp->ll_range) {
|
||||||
listitem_T *li = lp->ll_li;
|
listitem_T *li = lp->ll_li;
|
||||||
|
@ -233,6 +233,8 @@ typedef enum {
|
|||||||
kDictListItems, ///< List dictionary contents: [keys, values].
|
kDictListItems, ///< List dictionary contents: [keys, values].
|
||||||
} DictListType;
|
} DictListType;
|
||||||
|
|
||||||
|
typedef int (*ex_unletlock_callback)(lval_T *, char_u *, exarg_T *, int);
|
||||||
|
|
||||||
// Used for checking if local variables or arguments used in a lambda.
|
// Used for checking if local variables or arguments used in a lambda.
|
||||||
extern bool *eval_lavars_used;
|
extern bool *eval_lavars_used;
|
||||||
|
|
||||||
|
@ -1393,6 +1393,20 @@ func Test_compound_assignment_operators()
|
|||||||
let @/ = ''
|
let @/ = ''
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
func Test_unlet_env()
|
||||||
|
let $TESTVAR = 'yes'
|
||||||
|
call assert_equal('yes', $TESTVAR)
|
||||||
|
call assert_fails('lockvar $TESTVAR', 'E940')
|
||||||
|
call assert_fails('unlockvar $TESTVAR', 'E940')
|
||||||
|
call assert_equal('yes', $TESTVAR)
|
||||||
|
if 0
|
||||||
|
unlet $TESTVAR
|
||||||
|
endif
|
||||||
|
call assert_equal('yes', $TESTVAR)
|
||||||
|
unlet $TESTVAR
|
||||||
|
call assert_equal('', $TESTVAR)
|
||||||
|
endfunc
|
||||||
|
|
||||||
func Test_funccall_garbage_collect()
|
func Test_funccall_garbage_collect()
|
||||||
func Func(x, ...)
|
func Func(x, ...)
|
||||||
call add(a:x, a:000)
|
call add(a:x, a:000)
|
||||||
|
Loading…
Reference in New Issue
Block a user