Compare commits

...

9 Commits

Author SHA1 Message Date
Bram Moolenaar
4a091b9978 patch 8.2.1672: v_lock is used when it is not initialized
Problem:    v_lock is used when it is not initialized. (Yegappan Lakshmanan)
Solution:   Initialize the typval in eval1().
2020-09-12 22:10:00 +02:00
Bram Moolenaar
c6e57b74fa patch 8.2.1671: Vim9: stray error for missing white space
Problem:    Vim9: stray error for missing white space.
Solution:   Do not skip over white space after member. (closes #6817)
2020-09-12 21:27:03 +02:00
Bram Moolenaar
8f13d82fa6 patch 8.2.1670: a couple of gcc compiler warnings
Problem:    A couple of gcc compiler warnings.
Solution:   Initialize local variables. (Dominique Pellé, closes #6944)
2020-09-12 21:04:23 +02:00
Bram Moolenaar
b0fa5e17c5 patch 8.2.1669: Vim9: memory leak when storing a value fails
Problem:    Vim9: memory leak when storing a value fails.
Solution:   Free the value when not storing it.
2020-09-12 19:51:42 +02:00
Bram Moolenaar
dadaddd59f patch 8.2.1668: Vim9: not accepting 0 or 1 as bool when type is any
Problem:    Vim9: not accepting 0 or 1 as bool when type is any.
Solution:   Convert the type with the CHECKTYPE instruction. (closes #6913)
2020-09-12 19:11:23 +02:00
Bram Moolenaar
0f769815c8 patch 8.2.1667: local function name cannot shadow a global function name
Problem:    Local function name cannot shadow a global function name.
Solution:   Ignore global functions when checking a script-local or scoped
            function name. (closes #6926)
2020-09-12 18:32:34 +02:00
Bram Moolenaar
b00ef0508b patch 8.2.1666: the initial value of 'backupskip' can have duplicate items
Problem:    The initial value of 'backupskip' can have duplicate items.
Solution:   Remove duplicates, like when it is set later. (Tom Ryder,
            closes #6940)
2020-09-12 14:53:53 +02:00
Bram Moolenaar
635414dd2f patch 8.2.1665: cannot do fuzzy string matching
Problem:    Cannot do fuzzy string matching.
Solution:   Add matchfuzzy(). (Yegappan Lakshmanan, closes #6932)
2020-09-11 22:25:15 +02:00
Bram Moolenaar
c2c8205634 patch 8.2.1664: memory leak when using :mkview with a terminal buffer
Problem:    Memory leak when using :mkview with a terminal buffer.
Solution:   Don't use a hastab for :mkview. (Rob Pilling, closes #6935)
2020-09-11 22:10:22 +02:00
21 changed files with 669 additions and 96 deletions

View File

@@ -2641,6 +2641,7 @@ matcharg({nr}) List arguments of |:match|
matchdelete({id} [, {win}]) Number delete match identified by {id}
matchend({expr}, {pat} [, {start} [, {count}]])
Number position where {pat} ends in {expr}
matchfuzzy({list}, {str}) List fuzzy match {str} in {list}
matchlist({expr}, {pat} [, {start} [, {count}]])
List match and submatches of {pat} in {expr}
matchstr({expr}, {pat} [, {start} [, {count}]])
@@ -7307,6 +7308,29 @@ matchend({expr}, {pat} [, {start} [, {count}]]) *matchend()*
Can also be used as a |method|: >
GetText()->matchend('word')
matchfuzzy({list}, {str}) *matchfuzzy()*
Returns a list with all the strings in {list} that fuzzy
match {str}. The strings in the returned list are sorted
based on the matching score. {str} is treated as a literal
string and regular expression matching is NOT supported.
The maximum supported {str} length is 256.
If there are no matching strings or there is an error, then an
empty list is returned. If length of {str} is greater than
256, then returns an empty list.
Example: >
:echo matchfuzzy(["clay", "crow"], "cay")
< results in ["clay"]. >
:echo getbufinfo()->map({_, v -> v.name})->matchfuzzy("ndl")
< results in a list of buffer names fuzzy matching "ndl". >
:echo v:oldfiles->matchfuzzy("test")
< results in a list of file names fuzzy matching "test". >
:let l = readfile("buffer.c")->matchfuzzy("str")
< results in a list of lines in "buffer.c" fuzzy matching "str".
matchlist({expr}, {pat} [, {start} [, {count}]]) *matchlist()*
Same as |match()|, but return a |List|. The first item in the
list is the matched string, same as what matchstr() would

View File

@@ -603,6 +603,7 @@ String manipulation: *string-functions*
charclass() class of a character
match() position where a pattern matches in a string
matchend() position where a pattern match ends in a string
matchfuzzy() fuzzy matches a string in a list of strings
matchstr() match of a pattern in a string
matchstrpos() match and positions of a pattern in a string
matchlist() like matchstr() and also return submatches

View File

@@ -2103,6 +2103,8 @@ eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
char_u *p;
int getnext;
CLEAR_POINTER(rettv);
/*
* Get the first variable.
*/
@@ -3586,7 +3588,7 @@ eval_index(
;
if (keylen == 0)
return FAIL;
*arg = skipwhite(key + keylen);
*arg = key + keylen;
}
else
{

View File

@@ -750,6 +750,7 @@ static funcentry_T global_functions[] =
{"matcharg", 1, 1, FEARG_1, ret_list_string, f_matcharg},
{"matchdelete", 1, 2, FEARG_1, ret_number, f_matchdelete},
{"matchend", 2, 4, FEARG_1, ret_number, f_matchend},
{"matchfuzzy", 2, 2, FEARG_1, ret_list_string, f_matchfuzzy},
{"matchlist", 2, 4, FEARG_1, ret_list_string, f_matchlist},
{"matchstr", 2, 4, FEARG_1, ret_string, f_matchstr},
{"matchstrpos", 2, 4, FEARG_1, ret_list_any, f_matchstrpos},

View File

@@ -2950,7 +2950,7 @@ set_var_const(
if (ht == NULL || *varname == NUL)
{
semsg(_(e_illvar), name);
return;
goto failed;
}
is_script_local = ht == get_script_local_ht();
@@ -2960,7 +2960,7 @@ set_var_const(
&& name[1] == ':')
{
vim9_declare_error(name);
return;
goto failed;
}
di = find_var_in_ht(ht, 0, varname, TRUE);
@@ -2971,7 +2971,7 @@ set_var_const(
if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL)
&& var_wrong_func_name(name, di == NULL))
return;
goto failed;
if (need_convert_to_bool(type, tv))
{
@@ -2989,7 +2989,7 @@ set_var_const(
if (flags & LET_IS_CONST)
{
emsg(_(e_cannot_mod));
return;
goto failed;
}
if (is_script_local && in_vim9script())
@@ -2997,17 +2997,17 @@ set_var_const(
if ((flags & LET_NO_COMMAND) == 0)
{
semsg(_(e_redefining_script_item_str), name);
return;
goto failed;
}
// check the type and adjust to bool if needed
if (check_script_var_type(&di->di_tv, tv, name) == FAIL)
return;
goto failed;
}
if (var_check_ro(di->di_flags, name, FALSE)
|| var_check_lock(di->di_tv.v_lock, name, FALSE))
return;
goto failed;
}
else
// can only redefine once
@@ -3037,7 +3037,7 @@ set_var_const(
di->di_tv.vval.v_string = tv->vval.v_string;
tv->vval.v_string = NULL;
}
return;
goto failed;
}
else if (di->di_tv.v_type == VAR_NUMBER)
{
@@ -3051,12 +3051,12 @@ set_var_const(
redraw_all_later(SOME_VALID);
}
#endif
return;
goto failed;
}
else if (di->di_tv.v_type != tv->v_type)
{
semsg(_("E963: setting %s to value with wrong type"), name);
return;
goto failed;
}
}
@@ -3068,21 +3068,21 @@ set_var_const(
if (ht == &vimvarht || ht == get_funccal_args_ht())
{
semsg(_(e_illvar), name);
return;
goto failed;
}
// Make sure the variable name is valid.
if (!valid_varname(varname))
return;
goto failed;
di = alloc(sizeof(dictitem_T) + STRLEN(varname));
if (di == NULL)
return;
goto failed;
STRCPY(di->di_key, varname);
if (hash_add(ht, DI2HIKEY(di)) == FAIL)
{
vim_free(di);
return;
goto failed;
}
di->di_flags = DI_FLAGS_ALLOC;
if (flags & LET_IS_CONST)
@@ -3128,6 +3128,10 @@ set_var_const(
// if the reference count is up to one. That locks only literal
// values.
item_lock(&di->di_tv, DICT_MAXNEST, TRUE, TRUE);
return;
failed:
if (!copy)
clear_tv(tv_arg);
}
/*

View File

@@ -2634,7 +2634,7 @@ add_text_props_for_append(
int n;
char_u *props;
int new_len = 0; // init for gcc
char_u *new_line;
char_u *new_line = NULL;
textprop_T prop;
// Make two rounds:

View File

@@ -37,6 +37,7 @@
static void set_options_default(int opt_flags);
static void set_string_default_esc(char *name, char_u *val, int escape);
static char_u *find_dup_item(char_u *origval, char_u *newval, long_u flags);
static char_u *option_expand(int opt_idx, char_u *val);
static void didset_options(void);
static void didset_options2(void);
@@ -139,6 +140,9 @@ set_init_1(int clean_arg)
int len;
garray_T ga;
int mustfree;
char_u *item;
opt_idx = findoption((char_u *)"backupskip");
ga_init2(&ga, 1, 100);
for (n = 0; n < (long)(sizeof(names) / sizeof(char *)); ++n)
@@ -158,15 +162,20 @@ set_init_1(int clean_arg)
{
// First time count the NUL, otherwise count the ','.
len = (int)STRLEN(p) + 3;
if (ga_grow(&ga, len) == OK)
item = alloc(len);
STRCPY(item, p);
add_pathsep(item);
STRCAT(item, "*");
if (find_dup_item(ga.ga_data, item, options[opt_idx].flags)
== NULL
&& ga_grow(&ga, len) == OK)
{
if (ga.ga_len > 0)
STRCAT(ga.ga_data, ",");
STRCAT(ga.ga_data, p);
add_pathsep(ga.ga_data);
STRCAT(ga.ga_data, "*");
STRCAT(ga.ga_data, item);
ga.ga_len += len;
}
vim_free(item);
}
if (mustfree)
vim_free(p);
@@ -667,6 +676,46 @@ set_string_default(char *name, char_u *val)
set_string_default_esc(name, val, FALSE);
}
/*
* For an option value that contains comma separated items, find "newval" in
* "origval". Return NULL if not found.
*/
static char_u *
find_dup_item(char_u *origval, char_u *newval, long_u flags)
{
int bs = 0;
size_t newlen;
char_u *s;
if (origval == NULL)
return NULL;
newlen = STRLEN(newval);
for (s = origval; *s != NUL; ++s)
{
if ((!(flags & P_COMMA)
|| s == origval
|| (s[-1] == ',' && !(bs & 1)))
&& STRNCMP(s, newval, newlen) == 0
&& (!(flags & P_COMMA)
|| s[newlen] == ','
|| s[newlen] == NUL))
return s;
// Count backslashes. Only a comma with an even number of backslashes
// or a single backslash preceded by a comma before it is recognized as
// a separator.
if ((s > origval + 1
&& s[-1] == '\\'
&& s[-2] != ',')
|| (s == origval + 1
&& s[-1] == '\\'))
++bs;
else
bs = 0;
}
return NULL;
}
/*
* Set the Vi-default value of a number option.
* Used for 'lines' and 'columns'.
@@ -1572,7 +1621,6 @@ do_set(
#endif
unsigned newlen;
int comma;
int bs;
int new_value_alloced; // new string option
// was allocated
@@ -1811,39 +1859,20 @@ do_set(
if (removing || (flags & P_NODUP))
{
i = (int)STRLEN(newval);
bs = 0;
for (s = origval; *s; ++s)
{
if ((!(flags & P_COMMA)
|| s == origval
|| (s[-1] == ',' && !(bs & 1)))
&& STRNCMP(s, newval, i) == 0
&& (!(flags & P_COMMA)
|| s[i] == ','
|| s[i] == NUL))
break;
// Count backslashes. Only a comma with an
// even number of backslashes or a single
// backslash preceded by a comma before it
// is recognized as a separator
if ((s > origval + 1
&& s[-1] == '\\'
&& s[-2] != ',')
|| (s == origval + 1
&& s[-1] == '\\'))
++bs;
else
bs = 0;
}
s = find_dup_item(origval, newval, flags);
// do not add if already there
if ((adding || prepending) && *s)
if ((adding || prepending) && s != NULL)
{
prepending = FALSE;
adding = FALSE;
STRCPY(newval, origval);
}
// if no duplicate, move pointer to end of
// original value
if (s == NULL)
s = origval + (int)STRLEN(origval);
}
// concatenate the two strings; add a ',' if

View File

@@ -36,4 +36,5 @@ void find_pattern_in_path(char_u *ptr, int dir, int len, int whole, int skip_com
spat_T *get_spat(int idx);
int get_spat_last_idx(void);
void f_searchcount(typval_T *argvars, typval_T *rettv);
void f_matchfuzzy(typval_T *argvars, typval_T *rettv);
/* vim: set ft=c : */

View File

@@ -11,6 +11,7 @@ int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, evalarg_T
char_u *fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error);
ufunc_T *find_func_even_dead(char_u *name, int is_global, cctx_T *cctx);
ufunc_T *find_func(char_u *name, int is_global, cctx_T *cctx);
int func_is_global(ufunc_T *ufunc);
void copy_func(char_u *lambda, char_u *global);
int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv, funcexe_T *funcexe, dict_T *selfdict);
void save_funccal(funccal_entry_T *entry);

View File

@@ -4165,4 +4165,344 @@ f_searchcount(typval_T *argvars, typval_T *rettv)
the_end:
restore_last_search_pattern();
}
/*
* Fuzzy string matching
*
* Ported from the lib_fts library authored by Forrest Smith.
* https://github.com/forrestthewoods/lib_fts/tree/master/code
*
* Blog describing the algorithm:
* https://www.forrestthewoods.com/blog/reverse_engineering_sublime_texts_fuzzy_match/
*
* Each matching string is assigned a score. The following factors are checked:
* Matched letter
* Unmatched letter
* Consecutively matched letters
* Proximity to start
* Letter following a separator (space, underscore)
* Uppercase letter following lowercase (aka CamelCase)
*
* Matched letters are good. Unmatched letters are bad. Matching near the start
* is good. Matching the first letter in the middle of a phrase is good.
* Matching the uppercase letters in camel case entries is good.
*
* The score assigned for each factor is explained below.
* File paths are different from file names. File extensions may be ignorable.
* Single words care about consecutive matches but not separators or camel
* case.
* Score starts at 0
* Matched letter: +0 points
* Unmatched letter: -1 point
* Consecutive match bonus: +5 points
* Separator bonus: +10 points
* Camel case bonus: +10 points
* Unmatched leading letter: -3 points (max: -9)
*
* There is some nuance to this. Scores dont have an intrinsic meaning. The
* score range isnt 0 to 100. Its roughly [-50, 50]. Longer words have a
* lower minimum score due to unmatched letter penalty. Longer search patterns
* have a higher maximum score due to match bonuses.
*
* Separator and camel case bonus is worth a LOT. Consecutive matches are worth
* quite a bit.
*
* There is a penalty if you DONT match the first three letters. Which
* effectively rewards matching near the start. However theres no difference
* in matching between the middle and end.
*
* There is not an explicit bonus for an exact match. Unmatched letters receive
* a penalty. So shorter strings and closer matches are worth more.
*/
typedef struct
{
listitem_T *item;
int score;
} fuzzyItem_T;
static int
fuzzy_match_recursive(
char_u *fuzpat,
char_u *str,
int *outScore,
char_u *strBegin,
char_u *srcMatches,
char_u *matches,
int maxMatches,
int nextMatch,
int *recursionCount,
int recursionLimit)
{
// Recursion params
int recursiveMatch = FALSE;
char_u bestRecursiveMatches[256];
int bestRecursiveScore = 0;
int first_match;
int matched;
// Count recursions
++*recursionCount;
if (*recursionCount >= recursionLimit)
return FALSE;
// Detect end of strings
if (*fuzpat == '\0' || *str == '\0')
return FALSE;
// Loop through fuzpat and str looking for a match
first_match = TRUE;
while (*fuzpat != '\0' && *str != '\0')
{
// Found match
if (vim_tolower(*fuzpat) == vim_tolower(*str))
{
char_u recursiveMatches[256];
int recursiveScore = 0;
// Supplied matches buffer was too short
if (nextMatch >= maxMatches)
return FALSE;
// "Copy-on-Write" srcMatches into matches
if (first_match && srcMatches)
{
memcpy(matches, srcMatches, nextMatch);
first_match = FALSE;
}
// Recursive call that "skips" this match
if (fuzzy_match_recursive(fuzpat, str + 1, &recursiveScore,
strBegin, matches, recursiveMatches,
sizeof(recursiveMatches), nextMatch, recursionCount,
recursionLimit))
{
// Pick best recursive score
if (!recursiveMatch || recursiveScore > bestRecursiveScore)
{
memcpy(bestRecursiveMatches, recursiveMatches, 256);
bestRecursiveScore = recursiveScore;
}
recursiveMatch = TRUE;
}
// Advance
matches[nextMatch++] = (char_u)(str - strBegin);
++fuzpat;
}
++str;
}
// Determine if full fuzpat was matched
matched = *fuzpat == '\0' ? TRUE : FALSE;
// Calculate score
if (matched)
{
// bonus for adjacent matches
int sequential_bonus = 15;
// bonus if match occurs after a separator
int separator_bonus = 30;
// bonus if match is uppercase and prev is lower
int camel_bonus = 30;
// bonus if the first letter is matched
int first_letter_bonus = 15;
// penalty applied for every letter in str before the first match
int leading_letter_penalty = -5;
// maximum penalty for leading letters
int max_leading_letter_penalty = -15;
// penalty for every letter that doesn't matter
int unmatched_letter_penalty = -1;
int penalty;
int unmatched;
int i;
// Iterate str to end
while (*str != '\0')
++str;
// Initialize score
*outScore = 100;
// Apply leading letter penalty
penalty = leading_letter_penalty * matches[0];
if (penalty < max_leading_letter_penalty)
penalty = max_leading_letter_penalty;
*outScore += penalty;
// Apply unmatched penalty
unmatched = (int)(str - strBegin) - nextMatch;
*outScore += unmatched_letter_penalty * unmatched;
// Apply ordering bonuses
for (i = 0; i < nextMatch; ++i)
{
char_u currIdx = matches[i];
if (i > 0)
{
char_u prevIdx = matches[i - 1];
// Sequential
if (currIdx == (prevIdx + 1))
*outScore += sequential_bonus;
}
// Check for bonuses based on neighbor character value
if (currIdx > 0)
{
// Camel case
char_u neighbor = strBegin[currIdx - 1];
char_u curr = strBegin[currIdx];
int neighborSeparator;
if (islower(neighbor) && isupper(curr))
*outScore += camel_bonus;
// Separator
neighborSeparator = neighbor == '_' || neighbor == ' ';
if (neighborSeparator)
*outScore += separator_bonus;
}
else
{
// First letter
*outScore += first_letter_bonus;
}
}
}
// Return best result
if (recursiveMatch && (!matched || bestRecursiveScore > *outScore))
{
// Recursive score is better than "this"
memcpy(matches, bestRecursiveMatches, maxMatches);
*outScore = bestRecursiveScore;
return TRUE;
}
else if (matched)
return TRUE; // "this" score is better than recursive
return FALSE; // no match
}
/*
* fuzzy_match()
*
* Performs exhaustive search via recursion to find all possible matches and
* match with highest score.
* Scores values have no intrinsic meaning. Possible score range is not
* normalized and varies with pattern.
* Recursion is limited internally (default=10) to prevent degenerate cases
* (fuzpat="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").
* Uses char_u for match indices. Therefore patterns are limited to 256
* characters.
*
* Returns TRUE if fuzpat is found AND calculates a score.
*/
static int
fuzzy_match(char_u *str, char_u *fuzpat, int *outScore)
{
char_u matches[256];
int recursionCount = 0;
int recursionLimit = 10;
*outScore = 0;
return fuzzy_match_recursive(fuzpat, str, outScore, str, NULL, matches,
sizeof(matches), 0, &recursionCount, recursionLimit);
}
/*
* Sort the fuzzy matches in the descending order of the match score.
*/
static int
fuzzy_item_compare(const void *s1, const void *s2)
{
int v1 = ((fuzzyItem_T *)s1)->score;
int v2 = ((fuzzyItem_T *)s2)->score;
return v1 == v2 ? 0 : v1 > v2 ? -1 : 1;
}
/*
* Fuzzy search the string 'str' in 'strlist' and return the matching strings
* in 'fmatchlist'.
*/
static void
match_fuzzy(list_T *strlist, char_u *str, list_T *fmatchlist)
{
long len;
fuzzyItem_T *ptrs;
listitem_T *li;
long i = 0;
int found_match = FALSE;
len = list_len(strlist);
if (len == 0)
return;
ptrs = ALLOC_MULT(fuzzyItem_T, len);
if (ptrs == NULL)
return;
// For all the string items in strlist, get the fuzzy matching score
FOR_ALL_LIST_ITEMS(strlist, li)
{
int score;
ptrs[i].item = li;
ptrs[i].score = -9999;
// ignore non-string items in the list
if (li->li_tv.v_type == VAR_STRING && li->li_tv.vval.v_string != NULL)
if (fuzzy_match(li->li_tv.vval.v_string, str, &score))
{
ptrs[i].score = score;
found_match = TRUE;
}
++i;
}
if (found_match)
{
// Sort the list by the descending order of the match score
qsort((void *)ptrs, (size_t)len, sizeof(fuzzyItem_T),
fuzzy_item_compare);
// Copy the matching strings with 'score != -9999' to the return list
for (i = 0; i < len; i++)
{
if (ptrs[i].score == -9999)
break;
list_append_string(fmatchlist, ptrs[i].item->li_tv.vval.v_string,
-1);
}
}
vim_free(ptrs);
}
/*
* "matchfuzzy()" function
*/
void
f_matchfuzzy(typval_T *argvars, typval_T *rettv)
{
if (argvars[0].v_type != VAR_LIST)
{
emsg(_(e_listreq));
return;
}
if (argvars[0].vval.v_list == NULL)
return;
if (argvars[1].v_type != VAR_STRING
|| argvars[1].vval.v_string == NULL)
{
semsg(_(e_invarg2), tv_get_string(&argvars[1]));
return;
}
if (rettv_list_alloc(rettv) == OK)
match_fuzzy(argvars[0].vval.v_list, tv_get_string(&argvars[1]),
rettv->vval.v_list);
}
#endif

View File

@@ -303,14 +303,12 @@ put_view_curpos(FILE *fd, win_T *wp, char *spaces)
put_view(
FILE *fd,
win_T *wp,
int add_edit, // add ":edit" command to view
unsigned *flagp, // vop_flags or ssop_flags
int current_arg_idx // current argument index of the window, use
// -1 if unknown
#ifdef FEAT_TERMINAL
, hashtab_T *terminal_bufs
#endif
)
int add_edit, // add ":edit" command to view
unsigned *flagp, // vop_flags or ssop_flags
int current_arg_idx, // current argument index of the window,
// use -1 if unknown
hashtab_T *terminal_bufs UNUSED) // already encountered terminal buffers,
// can be NULL
{
win_T *save_curwin;
int f;
@@ -825,9 +823,11 @@ makeopens(
{
if (!ses_do_win(wp))
continue;
if (put_view(fd, wp, wp != edited_win, &ssop_flags, cur_arg_idx
if (put_view(fd, wp, wp != edited_win, &ssop_flags, cur_arg_idx,
#ifdef FEAT_TERMINAL
, &terminal_bufs
&terminal_bufs
#else
NULL
#endif
) == FAIL)
goto fail;
@@ -1102,11 +1102,6 @@ ex_mkrc(exarg_T *eap)
char_u *viewFile = NULL;
unsigned *flagp;
#endif
#ifdef FEAT_TERMINAL
hashtab_T terminal_bufs;
hash_init(&terminal_bufs);
#endif
if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview)
{
@@ -1263,11 +1258,8 @@ ex_mkrc(exarg_T *eap)
}
else
{
failed |= (put_view(fd, curwin, !using_vdir, flagp, -1
#ifdef FEAT_TERMINAL
, &terminal_bufs
#endif
) == FAIL);
failed |= (put_view(fd, curwin, !using_vdir, flagp, -1, NULL)
== FAIL);
}
if (put_line(fd, "let &so = s:so_save | let &siso = s:siso_save")
== FAIL)

View File

@@ -940,7 +940,7 @@ term_write_session(FILE *fd, win_T *wp, hashtab_T *terminal_bufs)
const int bufnr = wp->w_buffer->b_fnum;
term_T *term = wp->w_buffer->b_term;
if (wp->w_buffer->b_nwindows > 1)
if (terminal_bufs != NULL && wp->w_buffer->b_nwindows > 1)
{
// There are multiple views into this terminal buffer. We don't want to
// create the terminal multiple times. If it's the first time, create,
@@ -978,7 +978,7 @@ term_write_session(FILE *fd, win_T *wp, hashtab_T *terminal_bufs)
if (fprintf(fd, "let s:term_buf_%d = bufnr()", bufnr) < 0)
return FAIL;
if (wp->w_buffer->b_nwindows > 1)
if (terminal_bufs != NULL && wp->w_buffer->b_nwindows > 1)
{
char *hash_key = alloc(NUMBUFLEN);

View File

@@ -2554,4 +2554,28 @@ func Test_browsedir()
call assert_fails('call browsedir("open", [])', 'E730:')
endfunc
" Test for matchfuzzy()
func Test_matchfuzzy()
call assert_fails('call matchfuzzy(10, "abc")', 'E714:')
call assert_fails('call matchfuzzy(["abc"], [])', 'E730:')
call assert_equal([], matchfuzzy([], 'abc'))
call assert_equal([], matchfuzzy(['abc'], ''))
call assert_equal(['abc'], matchfuzzy(['abc', 10], 'ac'))
call assert_equal([], matchfuzzy([10, 20], 'ac'))
call assert_equal(['abc'], matchfuzzy(['abc'], 'abc'))
call assert_equal(['crayon', 'camera'], matchfuzzy(['camera', 'crayon'], 'cra'))
call assert_equal(['aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa', 'aba'], matchfuzzy(['aba', 'aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa'], 'aa'))
call assert_equal(['one'], matchfuzzy(['one', 'two'], 'one'))
call assert_equal(['oneTwo', 'onetwo'], matchfuzzy(['onetwo', 'oneTwo'], 'oneTwo'))
call assert_equal(['one_two', 'onetwo'], matchfuzzy(['onetwo', 'one_two'], 'oneTwo'))
call assert_equal(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], matchfuzzy(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], 'aa'))
call assert_equal([], matchfuzzy([repeat('a', 300)], repeat('a', 257)))
%bw!
eval ['somebuf', 'anotherone', 'needle', 'yetanotherone']->map({_, v -> bufadd(v) + bufload(v)})
let l = getbufinfo()->map({_, v -> v.name})->matchfuzzy('ndl')
call assert_equal(1, len(l))
call assert_match('needle', l[0])
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@@ -351,9 +351,8 @@ func Test_mksession_blank_windows()
call delete('Xtest_mks.out')
endfunc
if has('terminal')
func Test_mksession_terminal_shell()
CheckFeature terminal
CheckFeature quickfix
terminal
@@ -374,6 +373,8 @@ func Test_mksession_terminal_shell()
endfunc
func Test_mksession_terminal_no_restore_cmdarg()
CheckFeature terminal
terminal ++norestore
mksession! Xtest_mks.out
let lines = readfile('Xtest_mks.out')
@@ -389,6 +390,8 @@ func Test_mksession_terminal_no_restore_cmdarg()
endfunc
func Test_mksession_terminal_no_restore_funcarg()
CheckFeature terminal
call term_start(&shell, {'norestore': 1})
mksession! Xtest_mks.out
let lines = readfile('Xtest_mks.out')
@@ -404,6 +407,8 @@ func Test_mksession_terminal_no_restore_funcarg()
endfunc
func Test_mksession_terminal_no_restore_func()
CheckFeature terminal
terminal
call term_setrestore(bufnr('%'), 'NONE')
mksession! Xtest_mks.out
@@ -420,6 +425,8 @@ func Test_mksession_terminal_no_restore_func()
endfunc
func Test_mksession_terminal_no_ssop()
CheckFeature terminal
terminal
set sessionoptions-=terminal
mksession! Xtest_mks.out
@@ -437,6 +444,7 @@ func Test_mksession_terminal_no_ssop()
endfunc
func Test_mksession_terminal_restore_other()
CheckFeature terminal
CheckFeature quickfix
terminal
@@ -456,6 +464,8 @@ func Test_mksession_terminal_restore_other()
endfunc
func Test_mksession_terminal_shared_windows()
CheckFeature terminal
terminal
let term_buf = bufnr()
new
@@ -481,7 +491,18 @@ func Test_mksession_terminal_shared_windows()
call delete('Xtest_mks.out')
endfunc
endif " has('terminal')
func Test_mkview_terminal_windows()
CheckFeature terminal
" create two window on the same terminal to check this is handled OK
terminal
let term_buf = bufnr()
exe 'sbuf ' .. term_buf
mkview! Xtestview
call StopShellInTerminal(term_buf)
call delete('Xtestview')
endfunc
" Test :mkview with a file argument.
func Test_mkview_file()

View File

@@ -1,5 +1,6 @@
" Test for options
source shared.vim
source check.vim
source view_util.vim
@@ -587,6 +588,35 @@ func Test_backupskip()
endif
endfor
" Duplicates from environment variables should be filtered out (option has
" P_NODUP). Run this in a separate instance and write v:errors in a file,
" so that we see what happens on startup.
let after =<< trim [CODE]
let bsklist = split(&backupskip, ',')
call assert_equal(uniq(copy(bsklist)), bsklist)
call writefile(['errors:'] + v:errors, 'Xtestout')
qall
[CODE]
call writefile(after, 'Xafter')
let cmd = GetVimProg() . ' --not-a-term -S Xafter --cmd "set enc=utf8"'
let saveenv = {}
for var in ['TMPDIR', 'TMP', 'TEMP']
let saveenv[var] = getenv(var)
call setenv(var, '/duplicate/path')
endfor
exe 'silent !' . cmd
call assert_equal(['errors:'], readfile('Xtestout'))
" restore environment variables
for var in ['TMPDIR', 'TMP', 'TEMP']
call setenv(var, saveenv[var])
endfor
call delete('Xtestout')
call delete('Xafter')
" Duplicates should be filtered out (option has P_NODUP)
let backupskip = &backupskip
set backupskip=

View File

@@ -2314,6 +2314,16 @@ def Test_expr7_list_subscript()
CheckScriptFailure(['vim9script'] + lines, 'E1030:', 3)
enddef
def Test_expr7_dict_subscript()
let lines =<< trim END
vim9script
let l = [#{lnum: 2}, #{lnum: 1}]
let res = l[0].lnum > l[1].lnum
assert_true(res)
END
CheckScriptSuccess(lines)
enddef
def Test_expr7_subscript_linebreak()
let range = range(
3)
@@ -2369,6 +2379,9 @@ def Test_expr7_method_call()
type: '',
module: ''}
], getloclist(0))
let result: bool = get(#{n: 0}, 'n', 0)
assert_equal(false, result)
enddef
func Test_expr7_trailing_fails()

View File

@@ -232,6 +232,36 @@ def Test_global_local_function()
CheckScriptFailure(lines, 'E117:')
enddef
def Test_local_function_shadows_global()
let lines =<< trim END
vim9script
def g:Gfunc(): string
return 'global'
enddef
def AnotherFunc(): number
let Gfunc = function('len')
return Gfunc('testing')
enddef
g:Gfunc()->assert_equal('global')
AnotherFunc()->assert_equal(7)
delfunc g:Gfunc
END
CheckScriptSuccess(lines)
lines =<< trim END
vim9script
def g:Func(): string
return 'global'
enddef
def AnotherFunc()
g:Func = function('len')
enddef
AnotherFunc()
END
CheckScriptFailure(lines, 'E705:')
delfunc g:Func
enddef
func TakesOneArg(arg)
echo a:arg
endfunc

View File

@@ -874,6 +874,15 @@ find_func(char_u *name, int is_global, cctx_T *cctx)
return NULL;
}
/*
* Return TRUE if "ufunc" is a global function.
*/
int
func_is_global(ufunc_T *ufunc)
{
return ufunc->uf_name[0] != K_SPECIAL;
}
/*
* Copy the function name of "fp" to buffer "buf".
* "buf" must be able to hold the function name plus three bytes.
@@ -882,7 +891,7 @@ find_func(char_u *name, int is_global, cctx_T *cctx)
static void
cat_func_name(char_u *buf, ufunc_T *fp)
{
if (fp->uf_name[0] == K_SPECIAL)
if (!func_is_global(fp))
{
STRCPY(buf, "<SNR>");
STRCAT(buf, fp->uf_name + 3);

View File

@@ -750,6 +750,24 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1672,
/**/
1671,
/**/
1670,
/**/
1669,
/**/
1668,
/**/
1667,
/**/
1666,
/**/
1665,
/**/
1664,
/**/
1663,
/**/

View File

@@ -292,12 +292,14 @@ lookup_script(char_u *name, size_t len, int vim9script)
/*
* Check if "p[len]" is already defined, either in script "import_sid" or in
* compilation context "cctx".
* Does not check the global namespace.
* Return FAIL and give an error if it defined.
*/
int
check_defined(char_u *p, size_t len, cctx_T *cctx)
{
int c = p[len];
int c = p[len];
ufunc_T *ufunc = NULL;
p[len] = NUL;
if (lookup_script(p, len, FALSE) == OK
@@ -305,11 +307,16 @@ check_defined(char_u *p, size_t len, cctx_T *cctx)
&& (lookup_local(p, len, cctx) != NULL
|| lookup_arg(p, len, NULL, NULL, NULL, cctx) == OK))
|| find_imported(p, len, cctx) != NULL
|| find_func_even_dead(p, FALSE, cctx) != NULL)
|| (ufunc = find_func_even_dead(p, FALSE, cctx)) != NULL)
{
p[len] = c;
semsg(_(e_name_already_defined_str), p);
return FAIL;
// A local or script-local function can shadow a global function.
if (ufunc == NULL || !func_is_global(ufunc)
|| (p[0] == 'g' && p[1] == ':'))
{
p[len] = c;
semsg(_(e_name_already_defined_str), p);
return FAIL;
}
}
p[len] = c;
return OK;
@@ -2114,10 +2121,16 @@ generate_funcref(cctx_T *cctx, char_u *name)
/*
* Compile a variable name into a load instruction.
* "end" points to just after the name.
* "is_expr" is TRUE when evaluating an expression, might be a funcref.
* When "error" is FALSE do not give an error when not found.
*/
static int
compile_load(char_u **arg, char_u *end_arg, cctx_T *cctx, int error)
compile_load(
char_u **arg,
char_u *end_arg,
cctx_T *cctx,
int is_expr,
int error)
{
type_T *type;
char_u *name = NULL;
@@ -2214,10 +2227,11 @@ compile_load(char_u **arg, char_u *end_arg, cctx_T *cctx, int error)
|| find_imported(name, 0, cctx) != NULL)
res = compile_load_scriptvar(cctx, name, *arg, &end, FALSE);
// When the name starts with an uppercase letter or "x:" it
// can be a user defined function.
// When evaluating an expression and the name starts with an
// uppercase letter or "x:" it can be a user defined function.
// TODO: this is just guessing
if (res == FAIL && (ASCII_ISUPPER(*name) || name[1] == ':'))
if (res == FAIL && is_expr
&& (ASCII_ISUPPER(*name) || name[1] == ':'))
res = generate_funcref(cctx, name);
}
}
@@ -2368,8 +2382,9 @@ compile_call(
}
// If we can find the function by name generate the right call.
// Skip global functions here, a local funcref takes precedence.
ufunc = find_func(name, FALSE, cctx);
if (ufunc != NULL)
if (ufunc != NULL && !func_is_global(ufunc))
{
res = generate_CALL(cctx, ufunc, argcount);
goto theend;
@@ -2380,7 +2395,7 @@ compile_call(
// Not for eome#Func(), it will be loaded later.
p = namebuf;
if (STRNCMP(namebuf, "g:", 2) != 0 && !is_autoload
&& compile_load(&p, namebuf + varlen, cctx, FALSE) == OK)
&& compile_load(&p, namebuf + varlen, cctx, FALSE, FALSE) == OK)
{
garray_T *stack = &cctx->ctx_type_stack;
type_T *type;
@@ -2390,6 +2405,13 @@ compile_call(
goto theend;
}
// If we can find a global function by name generate the right call.
if (ufunc != NULL)
{
res = generate_CALL(cctx, ufunc, argcount);
goto theend;
}
// A global function may be defined only later. Need to figure out at
// runtime. Also handles a FuncRef at runtime.
if (STRNCMP(namebuf, "g:", 2) == 0 || is_autoload)
@@ -3548,7 +3570,7 @@ compile_expr7(
{
if (generate_ppconst(cctx, ppconst) == FAIL)
return FAIL;
r = compile_load(arg, p, cctx, TRUE);
r = compile_load(arg, p, cctx, TRUE, TRUE);
}
if (r == FAIL)
return FAIL;
@@ -5002,6 +5024,11 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
: ((type_T **)stack->ga_data)[stack->ga_len - 1];
if (lvar != NULL && (is_decl || !has_type))
{
if ((stacktype->tt_type == VAR_FUNC
|| stacktype->tt_type == VAR_PARTIAL)
&& var_wrong_func_name(name, TRUE))
goto theend;
if (new_local && !has_type)
{
if (stacktype->tt_type == VAR_VOID)
@@ -5009,12 +5036,6 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx)
emsg(_(e_cannot_use_void_value));
goto theend;
}
else if ((stacktype->tt_type == VAR_FUNC
|| stacktype->tt_type == VAR_PARTIAL)
&& var_wrong_func_name(name, TRUE))
{
goto theend;
}
else
{
// An empty list or dict has a &t_void member,

View File

@@ -2510,11 +2510,23 @@ call_def_function(
|| (tv->v_type == VAR_FUNC
&& ct->ct_type == VAR_PARTIAL)))
{
SOURCING_LNUM = iptr->isn_lnum;
semsg(_(e_expected_str_but_got_str),
vartype_name(ct->ct_type),
vartype_name(tv->v_type));
goto on_error;
if (tv->v_type == VAR_NUMBER && ct->ct_type == VAR_BOOL
&& (tv->vval.v_number == 0
|| tv->vval.v_number == 1))
{
// number 0 is FALSE, number 1 is TRUE
tv->v_type = VAR_BOOL;
tv->vval.v_number = tv->vval.v_number
? VVAL_TRUE : VVAL_FALSE;
}
else
{
SOURCING_LNUM = iptr->isn_lnum;
semsg(_(e_expected_str_but_got_str),
vartype_name(ct->ct_type),
vartype_name(tv->v_type));
goto on_error;
}
}
}
break;