From d8eed31d3c86dc74832a67de91480f1eabd27218 Mon Sep 17 00:00:00 2001 From: Yegappan Lakshmanan Date: Sun, 3 Apr 2022 07:56:27 -0700 Subject: [PATCH 1/2] Optionally display error messages if expansion fails with expandcmd() --- runtime/doc/builtin.txt | 18 ++++++++++++++---- src/dict.c | 10 ++++++++++ src/evalfunc.c | 21 ++++++++++++++++----- src/proto/dict.pro | 1 + src/testdir/test_expand.vim | 18 +++++++++++++++--- src/testdir/test_vim9_builtin.vim | 1 + 6 files changed, 57 insertions(+), 12 deletions(-) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index f2e98298df2622..08872d04e2a30f 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -161,7 +161,8 @@ exists_compiled({expr}) Number |TRUE| if {expr} exists at compile time exp({expr}) Float exponential of {expr} expand({expr} [, {nosuf} [, {list}]]) any expand special keywords in {expr} -expandcmd({expr}) String expand {expr} like with `:edit` +expandcmd({string} [, {options}]) + String expand {string} like with `:edit` extend({expr1}, {expr2} [, {expr3}]) List/Dict insert items of {expr2} into {expr1} extendnew({expr1}, {expr2} [, {expr3}]) @@ -2293,18 +2294,27 @@ expand({string} [, {nosuf} [, {list}]]) *expand()* Can also be used as a |method|: > Getpattern()->expand() -expandcmd({string}) *expandcmd()* +expandcmd({string} [, {options}]) *expandcmd()* Expand special items in String {string} like what is done for an Ex command such as `:edit`. This expands special keywords, like with |expand()|, and environment variables, anywhere in {string}. "~user" and "~/path" are only expanded at the start. + + The following items are supported in the {options} Dict + argument: + errmsg If set to TRUE, error messages are displayed + if an error is encountered during expansion. + By default, error messages are not displayed. + Returns the expanded string. If an error is encountered during expansion, the unmodified {string} is returned. + Example: > :echo expandcmd('make %<.o') -< make /path/runtime/doc/builtin.o ~ - + make /path/runtime/doc/builtin.o + :echo expandcmd('make %<.o', {'errmsg': v:true}) +< Can also be used as a |method|: > GetCommand()->expandcmd() < diff --git a/src/dict.c b/src/dict.c index c2dc6a7c23b304..5664bdf91d82ed 100644 --- a/src/dict.c +++ b/src/dict.c @@ -648,6 +648,16 @@ dict_find(dict_T *d, char_u *key, int len) return HI2DI(hi); } +/* + * Returns TRUE if "key[len]" is present in Dictionary "d". + * If "len" is negative use strlen(key). + */ + int +dict_has_key(dict_T *d, char *key, int len) +{ + return dict_find(d, (char_u *)key, len) != NULL; +} + /* * Get a typval_T item from a dictionary and copy it into "rettv". * Returns FAIL if the entry doesn't exist or out of memory. diff --git a/src/evalfunc.c b/src/evalfunc.c index 5a0428ecff99ff..69d52d9213814e 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -1761,7 +1761,7 @@ static funcentry_T global_functions[] = ret_float, FLOAT_FUNC(f_exp)}, {"expand", 1, 3, FEARG_1, arg3_string_bool_bool, ret_any, f_expand}, - {"expandcmd", 1, 1, FEARG_1, arg1_string, + {"expandcmd", 1, 2, FEARG_1, arg2_string_dict, ret_string, f_expandcmd}, {"extend", 2, 3, FEARG_1, arg23_extend, ret_extend, f_extend}, @@ -4152,10 +4152,17 @@ f_expandcmd(typval_T *argvars, typval_T *rettv) exarg_T eap; char_u *cmdstr; char *errormsg = NULL; + int emsgoff = TRUE; - if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL) + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_opt_dict_arg(argvars, 1) == FAIL)) return; + if (argvars[1].v_type == VAR_DICT + && dict_has_key(argvars[1].vval.v_dict, "errmsg", -1)) + emsgoff = FALSE; + rettv->v_type = VAR_STRING; cmdstr = vim_strsave(tv_get_string(&argvars[0])); @@ -4167,9 +4174,13 @@ f_expandcmd(typval_T *argvars, typval_T *rettv) eap.nextcmd = NULL; eap.cmdidx = CMD_USER; - ++emsg_off; - expand_filename(&eap, &cmdstr, &errormsg); - --emsg_off; + if (emsgoff) + ++emsg_off; + if (expand_filename(&eap, &cmdstr, &errormsg) == FAIL) + if (!emsgoff && errormsg != NULL && *errormsg != NUL) + emsg(errormsg); + if (emsgoff) + --emsg_off; rettv->vval.v_string = cmdstr; } diff --git a/src/proto/dict.pro b/src/proto/dict.pro index 7db011d210176c..89d1c0ba92ca96 100644 --- a/src/proto/dict.pro +++ b/src/proto/dict.pro @@ -27,6 +27,7 @@ char_u *dict_iterate_next(dict_iterator_T *iter, typval_T **tv_result); int dict_add_dict(dict_T *d, char *key, dict_T *dict); long dict_len(dict_T *d); dictitem_T *dict_find(dict_T *d, char_u *key, int len); +int dict_has_key(dict_T *d, char *key, int len); int dict_get_tv(dict_T *d, char_u *key, typval_T *rettv); char_u *dict_get_string(dict_T *d, char_u *key, int save); varnumber_T dict_get_number(dict_T *d, char_u *key); diff --git a/src/testdir/test_expand.vim b/src/testdir/test_expand.vim index ce414e4b11bcfd..638f9c7c3466d6 100644 --- a/src/testdir/test_expand.vim +++ b/src/testdir/test_expand.vim @@ -90,14 +90,26 @@ func Test_expandcmd() " Test for expression expansion `= let $FOO= "blue" call assert_equal("blue sky", expandcmd("`=$FOO .. ' sky'`")) + let x = expandcmd("`=axbycz`") + call assert_equal('`=axbycz`', x) + call assert_fails('let x = expandcmd("`=axbycz`", #{errmsg: 1})', 'E121:') + let x = expandcmd("`=axbycz`", #{abc: []}) + call assert_equal('`=axbycz`', x) " Test for env variable with spaces let $FOO= "foo bar baz" call assert_equal("e foo bar baz", expandcmd("e $FOO")) - if has('unix') - " test for using the shell to expand a command argument - call assert_equal('{1..4}', expandcmd('{1..4}')) + if has('unix') && executable('bash') + " test for using the shell to expand a command argument. + " only bash supports the {..} syntax + set shell=bash + let x = expandcmd('{1..4}') + call assert_equal('{1..4}', x) + call assert_fails("let x = expandcmd('{1..4}', #{errmsg: v:true})", 'E77:') + let x = expandcmd('{1..4}', #{error: v:true}) + call assert_equal('{1..4}', x) + set shell& endif unlet $FOO diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim index 01f29d9d2bcfd7..b20ff144ca087f 100644 --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -1020,6 +1020,7 @@ def Test_expandcmd() expandcmd('')->assert_equal('') v9.CheckDefAndScriptFailure(['expandcmd([1])'], ['E1013: Argument 1: type mismatch, expected string but got list', 'E1174: String required for argument 1']) + v9.CheckDefAndScriptFailure(['expandcmd("abc", [])'], ['E1013: Argument 2: type mismatch, expected dict but got list', 'E1206: Dictionary required for argument 2']) enddef def Test_extend_arg_types() From 01e6aa64e34bf032421ff6c0243729086108a138 Mon Sep 17 00:00:00 2001 From: Yegappan Lakshmanan Date: Sun, 3 Apr 2022 11:59:35 -0700 Subject: [PATCH 2/2] Fix the memory leak and use dict_get_bool() --- src/evalfunc.c | 3 ++- src/filepath.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/evalfunc.c b/src/evalfunc.c index 69d52d9213814e..48296734649595 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -4160,7 +4160,8 @@ f_expandcmd(typval_T *argvars, typval_T *rettv) return; if (argvars[1].v_type == VAR_DICT - && dict_has_key(argvars[1].vval.v_dict, "errmsg", -1)) + && dict_get_bool(argvars[1].vval.v_dict, (char_u *)"errmsg", + VVAL_FALSE)) emsgoff = FALSE; rettv->v_type = VAR_STRING; diff --git a/src/filepath.c b/src/filepath.c index 5bf31ea8e82e0b..3786ef63307aa2 100644 --- a/src/filepath.c +++ b/src/filepath.c @@ -3999,7 +3999,7 @@ gen_expand_wildcards( // When returning FAIL the array must be freed here. if (retval == FAIL) - ga_clear(&ga); + ga_clear_strings(&ga); *num_file = ga.ga_len; *file = (ga.ga_data != NULL) ? (char_u **)ga.ga_data