Merge pull request #5529 from brcolow/vim-7.4.1559

Port partial patches from vim
This commit is contained in:
James McCoy
2016-12-12 10:53:33 -05:00
committed by GitHub
23 changed files with 1747 additions and 449 deletions

View File

@@ -49,6 +49,9 @@ String A NUL terminated string of 8-bit unsigned characters (bytes).
Funcref A reference to a function |Funcref|.
Example: function("strlen")
It can be bound to a dictionary and arguments, it then works
like a Partial.
Example: function("Callback", [arg], myDict)
List An ordered sequence of items |List|.
Example: [1, 2, ['a', 'b']]
@@ -139,6 +142,40 @@ The name of the referenced function can be obtained with |string()|. >
You can use |call()| to invoke a Funcref and use a list variable for the
arguments: >
:let r = call(Fn, mylist)
<
*Partial*
A Funcref optionally binds a Dictionary and/or arguments. This is also called
a Partial. This is created by passing the Dictionary and/or arguments to
function(). When calling the function the Dictionary and/or arguments will be
passed to the function. Example: >
let Cb = function('Callback', ['foo'], myDict)
call Cb()
This will invoke the function as if using: >
call myDict.Callback('foo')
Note that binding a function to a Dictionary also happens when the function is
a member of the Dictionary: >
let myDict.myFunction = MyFunction
call myDict.myFunction()
Here MyFunction() will get myDict passed as "self". This happens when the
"myFunction" member is accessed. When assigning "myFunction" to otherDict
and calling it, it will be bound to otherDict: >
let otherDict.myFunction = myDict.myFunction
call otherDict.myFunction()
Now "self" will be "otherDict". But when the dictionary was bound explicitly
this won't happen: >
let myDict.myFunction = function(MyFunction, myDict)
let otherDict.myFunction = myDict.myFunction
call otherDict.myFunction()
Here "self" will be "myDict", because it was bound explitly.
1.3 Lists ~
@@ -1913,10 +1950,12 @@ foldlevel({lnum}) Number fold level at {lnum}
foldtext() String line displayed for closed fold
foldtextresult({lnum}) String text for closed fold at {lnum}
foreground() Number bring the Vim window to the foreground
function({name}) Funcref reference to function {name}
function({name} [, {arglist}] [, {dict}])
Funcref reference to function {name}
garbagecollect([{atexit}]) none free memory, breaking cyclic references
get({list}, {idx} [, {def}]) any get item {idx} from {list} or {def}
get({dict}, {key} [, {def}]) any get item {key} from {dict} or {def}
get({func}, {what}) any get property of funcref/partial {func}
getbufline({expr}, {lnum} [, {end}])
List lines {lnum} to {end} of buffer {expr}
getbufvar({expr}, {varname} [, {def}])
@@ -3483,10 +3522,46 @@ foreground() Move the Vim window to the foreground. Useful when sent from
{only in the Win32 GUI and console version}
function({name}) *function()* *E700*
*function()* *E700* *E922* *E929*
function({name} [, {arglist}] [, {dict}])
Return a |Funcref| variable that refers to function {name}.
{name} can be a user defined function or an internal function.
When {arglist} or {dict} is present this creates a partial.
That mans the argument list and/or the dictionary is stored in
the Funcref and will be used when the Funcref is called.
The arguments are passed to the function in front of other
arguments. Example: >
func Callback(arg1, arg2, name)
...
let Func = function('Callback', ['one', 'two'])
...
call Func('name')
< Invokes the function as with: >
call Callback('one', 'two', 'name')
< The Dictionary is only useful when calling a "dict" function.
In that case the {dict} is passed in as "self". Example: >
function Callback() dict
echo "called for " . self.name
endfunction
...
let context = {"name": "example"}
let Func = function('Callback', context)
...
call Func() " will echo: called for example
< The argument list and the Dictionary can be combined: >
function Callback(arg1, count) dict
...
let context = {"name": "example"}
let Func = function('Callback', ['one'], context)
...
call Func(500)
< Invokes the function as with: >
call context.Callback('one', 500)
garbagecollect([{atexit}]) *garbagecollect()*
Cleanup unused |Lists| and |Dictionaries| that have circular
@@ -3510,6 +3585,13 @@ get({dict}, {key} [, {default}])
Get item with key {key} from |Dictionary| {dict}. When this
item is not available return {default}. Return zero when
{default} is omitted.
get({func}, {what})
Get item {what} from Funcref {func}. Possible values for
{what} are:
'name' The function name
'func' The function
'dict' The dictionary
'args' The list with arguments
*getbufline()*
getbufline({expr}, {lnum} [, {end}])

View File

@@ -235,7 +235,7 @@ Additional differences:
These legacy Vim features may be implemented in the future, but they are not
planned for the current milestone.
- vim.bindeval() (new feature in Vim 7.4 Python interface)
- |if_py|: vim.bindeval() and vim.Function() are not supported
- |if_lua|
- |if_perl|
- |if_mzscheme|

View File

@@ -360,6 +360,9 @@ void set_option_to(void *to, int type, String name, Object value, Error *err)
#define TYPVAL_ENCODE_CONV_FUNC(fun) \
TYPVAL_ENCODE_CONV_NIL()
#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \
TYPVAL_ENCODE_CONV_NIL()
#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \
kv_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = 0, .size = 0 })))
@@ -482,6 +485,7 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, object, EncodedData *const, edata)
#undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT
#undef TYPVAL_ENCODE_CONV_FUNC
#undef TYPVAL_ENCODE_CONV_PARTIAL
#undef TYPVAL_ENCODE_CONV_EMPTY_LIST
#undef TYPVAL_ENCODE_CONV_LIST_START
#undef TYPVAL_ENCODE_CONV_EMPTY_DICT
@@ -653,7 +657,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
if (!object_to_vim(item, &li->li_tv, err)) {
// cleanup
listitem_free(li);
list_free(list, true);
list_free(list);
return false;
}
@@ -677,7 +681,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
api_set_error(err, Validation,
_("Empty dictionary keys aren't allowed"));
// cleanup
dict_free(dict, true);
dict_free(dict);
return false;
}
@@ -686,7 +690,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
if (!object_to_vim(item.value, &di->di_tv, err)) {
// cleanup
dictitem_free(di);
dict_free(dict, true);
dict_free(dict);
return false;
}

View File

@@ -225,7 +225,7 @@ Object nvim_call_function(String fname, Array args, Error *err)
&rettv, (int) args.size, vim_args,
curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy,
true,
NULL);
NULL, NULL);
if (r == FAIL) {
api_set_error(err, Exception, _("Error calling function."));
}

File diff suppressed because it is too large Load Diff

View File

@@ -103,7 +103,7 @@ return {
foldtext={},
foldtextresult={args=1},
foreground={},
['function']={args=1},
['function']={args={1, 3}},
garbagecollect={args={0, 1}},
get={args={2, 3}},
getbufline={args={2, 3}},

View File

@@ -315,6 +315,60 @@ int encode_read_from_list(ListReaderState *const state, char *const buf,
ga_append(gap, ')'); \
} while (0)
#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \
do { \
int i; \
ga_concat(gap, "function("); \
if (&pt->pt_name != NULL) { \
size_t len; \
char_u *p; \
len = 3; \
len += STRLEN(pt->pt_name); \
for (p = pt->pt_name; *p != NUL; mb_ptr_adv(p)) { \
if (*p == '\'') { \
len++; \
} \
} \
char_u *r, *s; \
s = r = xmalloc(len); \
if (r != NULL) { \
*r++ = '\''; \
for (p = pt->pt_name; *p != NUL; ) { \
if (*p == '\'') { \
*r++ = '\''; \
} \
MB_COPY_CHAR(p, r); \
} \
*r++ = '\''; \
*r++ = NUL; \
} \
ga_concat(gap, s); \
xfree(s); \
} \
if (pt->pt_argc > 0) { \
ga_concat(gap, ", ["); \
for (i = 0; i < pt->pt_argc; i++) { \
if (i > 0) { \
ga_concat(gap, ", "); \
} \
char *tofree = encode_tv2string(&pt->pt_argv[i], NULL); \
ga_concat(gap, tofree); \
xfree(tofree); \
} \
ga_append(gap, ']'); \
} \
if (pt->pt_dict != NULL) { \
typval_T dtv; \
ga_concat(gap, ", "); \
dtv.v_type = VAR_DICT; \
dtv.vval.v_dict = pt->pt_dict; \
char *tofree = encode_tv2string(&dtv, NULL); \
ga_concat(gap, tofree); \
xfree(tofree); \
} \
ga_append(gap, ')'); \
} while (0)
#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \
ga_concat(gap, "[]")
@@ -661,6 +715,12 @@ static inline int convert_to_json_string(garray_T *const gap,
"attempt to dump function reference"), \
mpstack, objname)
#undef TYPVAL_ENCODE_CONV_PARTIAL
#define TYPVAL_ENCODE_CONV_PARTIAL(pt) \
return conv_error(_("E474: Error while dumping %s, %s: " \
"attempt to dump partial"), \
mpstack, objname)
/// Check whether given key can be used in json_encode()
///
/// @param[in] tv Key to check.
@@ -718,6 +778,7 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, json, garray_T *const, gap)
#undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT
#undef TYPVAL_ENCODE_CONV_FUNC
#undef TYPVAL_ENCODE_CONV_PARTIAL
#undef TYPVAL_ENCODE_CONV_EMPTY_LIST
#undef TYPVAL_ENCODE_CONV_LIST_START
#undef TYPVAL_ENCODE_CONV_EMPTY_DICT
@@ -846,6 +907,11 @@ char *encode_tv2json(typval_T *tv, size_t *len)
"attempt to dump function reference"), \
mpstack, objname)
#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \
return conv_error(_("E5004: Error while dumping %s, %s: " \
"attempt to dump partial"), \
mpstack, objname)
#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \
msgpack_pack_array(packer, 0)
@@ -902,6 +968,7 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(, msgpack, msgpack_packer *const, packer)
#undef TYPVAL_ENCODE_CONV_NUMBER
#undef TYPVAL_ENCODE_CONV_FLOAT
#undef TYPVAL_ENCODE_CONV_FUNC
#undef TYPVAL_ENCODE_CONV_PARTIAL
#undef TYPVAL_ENCODE_CONV_EMPTY_LIST
#undef TYPVAL_ENCODE_CONV_LIST_START
#undef TYPVAL_ENCODE_CONV_EMPTY_DICT

View File

@@ -69,6 +69,11 @@
///
/// @param fun Function name.
/// @def TYPVAL_ENCODE_CONV_PARTIAL
/// @brief Macros used to convert a partial
///
/// @param pt Partial name.
/// @def TYPVAL_ENCODE_CONV_EMPTY_LIST
/// @brief Macros used to convert an empty list
///
@@ -248,6 +253,10 @@ static int name##_convert_one_value(firstargtype firstargname, \
TYPVAL_ENCODE_CONV_FUNC(tv->vval.v_string); \
break; \
} \
case VAR_PARTIAL: { \
TYPVAL_ENCODE_CONV_PARTIAL(tv->vval.v_partial); \
break; \
} \
case VAR_LIST: { \
if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { \
TYPVAL_ENCODE_CONV_EMPTY_LIST(); \

View File

@@ -15,6 +15,7 @@ typedef double float_T;
typedef struct listvar_S list_T;
typedef struct dictvar_S dict_T;
typedef struct partial_S partial_T;
/// Special variable values
typedef enum {
@@ -35,12 +36,13 @@ typedef enum {
VAR_UNKNOWN = 0, ///< Unknown (unspecified) value.
VAR_NUMBER, ///< Number, .v_number is used.
VAR_STRING, ///< String, .v_string is used.
VAR_FUNC, ///< Function referene, .v_string is used for function name.
VAR_FUNC, ///< Function reference, .v_string is used as function name.
VAR_LIST, ///< List, .v_list is used.
VAR_DICT, ///< Dictionary, .v_dict is used.
VAR_FLOAT, ///< Floating-point value, .v_float is used.
VAR_SPECIAL, ///< Special value (true, false, null), .v_special
///< is used.
VAR_PARTIAL, ///< Partial, .v_partial is used.
} VarType;
/// Structure that holds an internal variable value
@@ -54,6 +56,7 @@ typedef struct {
char_u *v_string; ///< String, for VAR_STRING and VAR_FUNC, can be NULL.
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.
} vval; ///< Actual value.
} typval_T;
@@ -144,6 +147,16 @@ struct dictvar_S {
QUEUE watchers; ///< Dictionary key watchers set by user code.
};
struct partial_S {
int pt_refcount; ///< Reference count.
char_u *pt_name; ///< Function name.
bool pt_auto; ///< when true the partial was created for using
///< dict.member in handle_subscript().
int pt_argc; ///< Number of arguments.
typval_T *pt_argv; ///< Arguments in allocated array.
dict_T *pt_dict; ///< Dict for "self".
};
// structure used for explicit stack while garbage collecting hash tables
typedef struct ht_stack_S {
hashtab_T *ht;

View File

@@ -1235,6 +1235,8 @@ EXTERN FILE *time_fd INIT(= NULL); /* where to write startup timing */
EXTERN int ignored;
EXTERN char *ignoredp;
EXTERN bool in_free_unref_items INIT(= false);
// If a msgpack-rpc channel should be started over stdin/stdout
EXTERN bool embedded_mode INIT(= false);

View File

@@ -2466,7 +2466,7 @@ do_mouse (
(int) strlen(tab_page_click_defs[mouse_col].func),
&rettv, ARRAY_SIZE(argv), argv,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&doesrange, true, NULL);
&doesrange, true, NULL, NULL);
clear_tv(&rettv);
break;
}

View File

@@ -790,7 +790,7 @@ do_tag (
vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag);
set_errorlist(curwin, list, ' ', IObuff);
list_free(list, TRUE);
list_free(list);
xfree(fname);
xfree(cmd);

View File

@@ -14,6 +14,7 @@ source test_matchadd_conceal_utf8.vim
source test_menu.vim
source test_messages.vim
source test_options.vim
source test_partial.vim
source test_popup.vim
source test_regexp_utf8.vim
source test_statusline.vim

View File

@@ -15,6 +15,28 @@ func Test_version()
call assert_false(has('patch-9.9.1'))
endfunc
func Test_equal()
let base = {}
func base.method()
return 1
endfunc
func base.other() dict
return 1
endfunc
let instance = copy(base)
call assert_true(base.method == instance.method)
call assert_true([base.method] == [instance.method])
call assert_true(base.other == instance.other)
call assert_true([base.other] == [instance.other])
call assert_false(base.method == base.other)
call assert_false([base.method] == [base.other])
call assert_false(base.method == instance.other)
call assert_false([base.method] == [instance.other])
call assert_fails('echo base.method > instance.method')
endfunc
func Test_strgetchar()
call assert_equal(char2nr('a'), strgetchar('axb', 0))
call assert_equal(char2nr('x'), strgetchar('axb', 1))

View File

@@ -0,0 +1,330 @@
" Test binding arguments to a Funcref.
func MyFunc(arg1, arg2, arg3)
return a:arg1 . '/' . a:arg2 . '/' . a:arg3
endfunc
func MySort(up, one, two)
if a:one == a:two
return 0
endif
if a:up
return a:one > a:two ? 1 : -1
endif
return a:one < a:two ? 1 : -1
endfunc
func Test_partial_args()
let Cb = function('MyFunc', ["foo", "bar"])
call Cb("zzz")
call assert_equal("foo/bar/xxx", Cb("xxx"))
call assert_equal("foo/bar/yyy", call(Cb, ["yyy"]))
let Cb2 = function(Cb)
call assert_equal("foo/bar/zzz", Cb2("zzz"))
let Cb3 = function(Cb, ["www"])
call assert_equal("foo/bar/www", Cb3())
let Cb = function('MyFunc', [])
call assert_equal("a/b/c", Cb("a", "b", "c"))
let Cb2 = function(Cb, [])
call assert_equal("a/b/d", Cb2("a", "b", "d"))
let Cb3 = function(Cb, ["a", "b"])
call assert_equal("a/b/e", Cb3("e"))
let Sort = function('MySort', [1])
call assert_equal([1, 2, 3], sort([3, 1, 2], Sort))
let Sort = function('MySort', [0])
call assert_equal([3, 2, 1], sort([3, 1, 2], Sort))
endfunc
func MyDictFunc(arg1, arg2) dict
return self.name . '/' . a:arg1 . '/' . a:arg2
endfunc
func Test_partial_dict()
let dict = {'name': 'hello'}
let Cb = function('MyDictFunc', ["foo", "bar"], dict)
call assert_equal("hello/foo/bar", Cb())
call assert_fails('Cb("xxx")', 'E492:')
let Cb = function('MyDictFunc', [], dict)
call assert_equal("hello/ttt/xxx", Cb("ttt", "xxx"))
call assert_fails('Cb("yyy")', 'E492:')
let Cb = function('MyDictFunc', ["foo"], dict)
call assert_equal("hello/foo/xxx", Cb("xxx"))
call assert_fails('Cb()', 'E492:')
let Cb = function('MyDictFunc', dict)
call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy"))
call assert_fails('Cb("fff")', 'E492:')
let dict = {"tr": function('tr', ['hello', 'h', 'H'])}
call assert_equal("Hello", dict.tr())
endfunc
func Test_partial_implicit()
let dict = {'name': 'foo'}
func dict.MyFunc(arg) dict
return self.name . '/' . a:arg
endfunc
call assert_equal('foo/bar', dict.MyFunc('bar'))
call assert_fails('let func = dict.MyFunc', 'E704:')
let Func = dict.MyFunc
call assert_equal('foo/aaa', Func('aaa'))
let Func = function(dict.MyFunc, ['bbb'])
call assert_equal('foo/bbb', Func())
endfunc
fun InnerCall(funcref)
return a:funcref
endfu
fun OuterCall()
let opt = { 'func' : function('sin') }
call InnerCall(opt.func)
endfu
func Test_function_in_dict()
call OuterCall()
endfunc
function! s:cache_clear() dict
return self.name
endfunction
func Test_script_function_in_dict()
let s:obj = {'name': 'foo'}
let s:obj2 = {'name': 'bar'}
let s:obj['clear'] = function('s:cache_clear')
call assert_equal('foo', s:obj.clear())
let F = s:obj.clear
call assert_equal('foo', F())
call assert_equal('foo', call(s:obj.clear, [], s:obj))
call assert_equal('bar', call(s:obj.clear, [], s:obj2))
let s:obj2['clear'] = function('s:cache_clear')
call assert_equal('bar', s:obj2.clear())
let B = s:obj2.clear
call assert_equal('bar', B())
endfunc
function! s:cache_arg(arg) dict
let s:result = self.name . '/' . a:arg
return s:result
endfunction
func Test_script_function_in_dict_arg()
let s:obj = {'name': 'foo'}
let s:obj['clear'] = function('s:cache_arg')
call assert_equal('foo/bar', s:obj.clear('bar'))
let F = s:obj.clear
let s:result = ''
call assert_equal('foo/bar', F('bar'))
call assert_equal('foo/bar', s:result)
let s:obj['clear'] = function('s:cache_arg', ['bar'])
call assert_equal('foo/bar', s:obj.clear())
let s:result = ''
call s:obj.clear()
call assert_equal('foo/bar', s:result)
let F = s:obj.clear
call assert_equal('foo/bar', F())
let s:result = ''
call F()
call assert_equal('foo/bar', s:result)
call assert_equal('foo/bar', call(s:obj.clear, [], s:obj))
endfunc
func Test_partial_exists()
let F = function('MyFunc')
call assert_true(exists('*F'))
let lF = [F]
call assert_true(exists('*lF[0]'))
let F = function('MyFunc', ['arg'])
call assert_true(exists('*F'))
let lF = [F]
call assert_true(exists('*lF[0]'))
endfunc
func Test_partial_string()
let F = function('MyFunc')
call assert_equal("function('MyFunc')", string(F))
let F = function('MyFunc', ['foo'])
call assert_equal("function('MyFunc', ['foo'])", string(F))
let F = function('MyFunc', ['foo', 'bar'])
call assert_equal("function('MyFunc', ['foo', 'bar'])", string(F))
let d = {'one': 1}
let F = function('MyFunc', d)
call assert_equal("function('MyFunc', {'one': 1})", string(F))
let F = function('MyFunc', ['foo'], d)
call assert_equal("function('MyFunc', ['foo'], {'one': 1})", string(F))
endfunc
func Test_func_unref()
let obj = {}
function! obj.func() abort
endfunction
let funcnumber = matchstr(string(obj.func), '^function(''\zs.\{-}\ze''')
call assert_true(exists('*{' . funcnumber . '}'))
unlet obj
call assert_false(exists('*{' . funcnumber . '}'))
endfunc
func Test_redefine_dict_func()
let d = {}
function d.test4()
endfunction
let d.test4 = d.test4
try
function! d.test4(name)
endfunction
catch
call assert_true(v:errmsg, v:exception)
endtry
endfunc
" This caused double free on exit if EXITFREE is defined.
func Test_cyclic_list_arg()
let l = []
let Pt = function('string', [l])
call add(l, Pt)
unlet l
unlet Pt
endfunc
" This caused double free on exit if EXITFREE is defined.
func Test_cyclic_dict_arg()
let d = {}
let Pt = function('string', [d])
let d.Pt = Pt
unlet d
unlet Pt
endfunc
func Ignored(job1, job2, status)
endfunc
" func Test_cycle_partial_job()
" let job = job_start('echo')
" call job_setoptions(job, {'exit_cb': function('Ignored', [job])})
" unlet job
" endfunc
" func Test_ref_job_partial_dict()
" let g:ref_job = job_start('echo')
" let d = {'a': 'b'}
" call job_setoptions(g:ref_job, {'exit_cb': function('string', [], d)})
" endfunc
func Test_auto_partial_rebind()
let dict1 = {'name': 'dict1'}
func! dict1.f1()
return self.name
endfunc
let dict1.f2 = function(dict1.f1, dict1)
call assert_equal('dict1', dict1.f1())
call assert_equal('dict1', dict1['f1']())
call assert_equal('dict1', dict1.f2())
call assert_equal('dict1', dict1['f2']())
let dict2 = {'name': 'dict2'}
let dict2.f1 = dict1.f1
let dict2.f2 = dict1.f2
call assert_equal('dict2', dict2.f1())
call assert_equal('dict2', dict2['f1']())
call assert_equal('dict1', dict2.f2())
call assert_equal('dict1', dict2['f2']())
endfunc
func Test_get_partial_items()
let dict = {'name': 'hello'}
let args = ["foo", "bar"]
let Func = function('MyDictFunc')
let Cb = function('MyDictFunc', args, dict)
call assert_equal(Func, get(Cb, 'func'))
call assert_equal('MyDictFunc', get(Cb, 'name'))
call assert_equal(args, get(Cb, 'args'))
call assert_equal(dict, get(Cb, 'dict'))
call assert_fails('call get(Cb, "xxx")', 'E475:')
call assert_equal(Func, get(Func, 'func'))
call assert_equal('MyDictFunc', get(Func, 'name'))
call assert_equal([], get(Func, 'args'))
call assert_true(empty( get(Func, 'dict')))
endfunc
func Test_compare_partials()
let d1 = {}
let d2 = {}
function d1.f1() dict
endfunction
function d1.f2() dict
endfunction
let F1 = get(d1, 'f1')
let F2 = get(d1, 'f2')
let F1d1 = function(F1, d1)
let F2d1 = function(F2, d2)
let F1d1a1 = function(F1d1, [1])
let F1d1a12 = function(F1d1, [1, 2])
let F1a1 = function(F1, [1])
let F1a2 = function(F1, [2])
let F1d2 = function(F1, d2)
let d3 = {'f1': F1, 'f2': F2}
let F1d3 = function(F1, d3)
let F1ad1 = function(F1, [d1])
let F1ad3 = function(F1, [d3])
call assert_match('^function(''\d\+'')$', string(F1)) " Not a partial
call assert_match('^function(''\d\+'')$', string(F2)) " Not a partial
call assert_match('^function(''\d\+'', {.*})$', string(F1d1)) " A partial
call assert_match('^function(''\d\+'', {.*})$', string(F2d1)) " A partial
call assert_match('^function(''\d\+'', \[.*\])$', string(F1a1)) " No dict
" !=
let X = F1
call assert_false(F1 != X) " same function
let X = F1d1
call assert_false(F1d1 != X) " same partial
let X = F1d1a1
call assert_false(F1d1a1 != X) " same partial
let X = F1a1
call assert_false(F1a1 != X) " same partial
call assert_true(F1 != F2) " Different functions
call assert_true(F1 != F1d1) " Partial /= non-partial
call assert_true(F1d1a1 != F1d1a12) " Different number of arguments
call assert_true(F1a1 != F1d1a12) " One has no dict
call assert_true(F1a1 != F1a2) " Different arguments
call assert_true(F1d2 != F1d1) " Different dictionaries
call assert_false(F1d1 != F1d3) " Equal dictionaries, even though d1 isnot d3
" isnot, option 1
call assert_true(F1 isnot# F2) " Different functions
call assert_true(F1 isnot# F1d1) " Partial /= non-partial
call assert_true(F1d1 isnot# F1d3) " d1 isnot d3, even though d1 == d3
call assert_true(F1a1 isnot# F1d1a12) " One has no dict
call assert_true(F1a1 isnot# F1a2) " Different number of arguments
call assert_true(F1ad1 isnot# F1ad3) " In arguments d1 isnot d3
" isnot, option 2
call assert_true(F1 isnot# F2) " Different functions
call assert_true(F1 isnot# F1d1) " Partial /= non-partial
call assert_true(d1.f1 isnot# d1.f1) " handle_subscript creates new partial each time
endfunc

View File

@@ -8,6 +8,10 @@ func MyHandler(timer)
let s:val += 1
endfunc
func MyHandlerWithLists(lists, timer)
let x = string(a:lists)
endfunc
func Test_oneshot()
let s:val = 0
let timer = timer_start(50, 'MyHandler')
@@ -30,3 +34,22 @@ func Test_repeat_many()
call assert_true(s:val > 1)
call assert_true(s:val < 5)
endfunc
func Test_with_partial_callback()
let s:val = 0
let s:meow = {}
function s:meow.bite(...)
let s:val += 1
endfunction
call timer_start(50, s:meow.bite)
sleep 200m
call assert_equal(1, s:val)
endfunc
func Test_retain_partial()
call timer_start(100, function('MyHandlerWithLists', [['a']]))
call garbagecollect()
sleep 200m
endfunc
" vim: ts=2 sw=0 et

View File

@@ -943,6 +943,7 @@ func Test_type()
call assert_equal(0, type(0))
call assert_equal(1, type(""))
call assert_equal(2, type(function("tr")))
call assert_equal(2, type(function("tr", [8])))
call assert_equal(3, type([]))
call assert_equal(4, type({}))
call assert_equal(5, type(0.0))
@@ -983,6 +984,86 @@ func Test_skip()
endfunc
"-------------------------------------------------------------------------------
" Test 93: :echo and string() {{{1
"-------------------------------------------------------------------------------
func Test_echo_and_string()
" String
let a = 'foo bar'
redir => result
echo a
echo string(a)
redir END
let l = split(result, "\n")
call assert_equal(["foo bar",
\ "'foo bar'"], l)
" Float
if has('float')
let a = -1.2e0
redir => result
echo a
echo string(a)
redir END
let l = split(result, "\n")
call assert_equal(["-1.2",
\ "-1.2"], l)
endif
" Funcref
redir => result
echo function('string')
echo string(function('string'))
redir END
let l = split(result, "\n")
call assert_equal(["string",
\ "function('string')"], l)
" Empty dictionaries in a list
let a = {}
redir => result
echo [a, a, a]
echo string([a, a, a])
redir END
let l = split(result, "\n")
call assert_equal(["[{}, {}, {}]",
\ "[{}, {}, {}]"], l)
" Empty dictionaries in a dictionary
let a = {}
let b = {"a": a, "b": a}
redir => result
echo b
echo string(b)
redir END
let l = split(result, "\n")
call assert_equal(["{'a': {}, 'b': {}}",
\ "{'a': {}, 'b': {}}"], l)
" Empty lists in a list
let a = []
redir => result
echo [a, a, a]
echo string([a, a, a])
redir END
let l = split(result, "\n")
call assert_equal(["[[], [], []]",
\ "[[], [], []]"], l)
" Empty lists in a dictionary
let a = []
let b = {"a": a, "b": a}
redir => result
echo b
echo string(b)
redir END
let l = split(result, "\n")
call assert_equal(["{'a': [], 'b': []}",
\ "{'a': [], 'b': []}"], l)
endfunc
"-------------------------------------------------------------------------------
" Modelines {{{1
" vim: ts=8 sw=4 tw=80 fdm=marker

View File

@@ -565,7 +565,7 @@ static int included_patches[] = {
// 1878 NA
// 1877 NA
// 1876,
// 1875,
1875,
// 1874 NA
// 1873 NA
// 1872 NA
@@ -578,9 +578,9 @@ static int included_patches[] = {
// 1865 NA
// 1864 NA
// 1863 NA
// 1862,
// 1862 NA
// 1861,
// 1860 NA
1860,
// 1859 NA
// 1858 NA
// 1857 NA
@@ -598,15 +598,14 @@ static int included_patches[] = {
// 1845 NA
// 1844,
// 1843 NA
// 1842,
1842,
// 1841,
1840,
// 1839,
// 1838,
// 1837,
// 1836,
1836,
1835,
// 1834,
1833,
1832,
1831,
@@ -711,7 +710,7 @@ static int included_patches[] = {
1734,
// 1733 NA
1732,
// 1731,
// 1731 NA
1730,
// 1729 NA
1728,
@@ -722,12 +721,12 @@ static int included_patches[] = {
1723,
// 1722 NA
// 1721 NA
// 1720,
// 1719,
// 1718,
// 1720 NA
1719,
1718,
// 1717 NA
1716,
// 1715,
1715,
1714,
// 1713 NA
1712,
@@ -797,14 +796,14 @@ static int included_patches[] = {
1648,
1647,
// 1646 NA
// 1645,
// 1644,
1645,
// 1644 NA
1643,
1642,
1641,
1640,
// 1639,
// 1638,
1639,
1638,
// 1637 NA
// 1636 NA
// 1635 NA
@@ -834,10 +833,10 @@ static int included_patches[] = {
// 1611 NA
// 1610 NA
// 1609 NA
// 1608,
// 1607,
// 1606,
// 1605,
1608,
1607,
1606,
1605,
1604,
1603,
// 1602 NA
@@ -852,20 +851,20 @@ static int included_patches[] = {
// 1593 NA
1592,
1591,
// 1590,
// 1589,
1590,
1589,
1588,
// 1587 NA
// 1586,
// 1585,
1586,
1585,
// 1584 NA
// 1583 NA
// 1582,
// 1581,
// 1580,
1582,
1581,
1580,
// 1579 NA
1578,
// 1577,
1577,
1576,
// 1575 NA
1574,
@@ -878,12 +877,12 @@ static int included_patches[] = {
1567,
// 1566 NA
1565,
// 1564,
// 1563,
1564,
1563,
// 1562 NA
// 1561 NA
// 1560 NA
// 1559,
1559,
1558,
1557,
// 1556 NA

View File

@@ -0,0 +1,27 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, eq, next_msg, nvim, source = helpers.clear, helpers.eq,
helpers.next_message, helpers.nvim, helpers.source
if helpers.pending_win32(pending) then return end
describe('jobs with partials', function()
local channel
before_each(function()
clear()
channel = nvim('get_api_info')[1]
nvim('set_var', 'channel', channel)
end)
it('works correctly', function()
source([[
function PrintArgs(a1, a2, id, data, event)
call rpcnotify(g:channel, '1', a:a1, a:a2, a:data, a:event)
endfunction
let Callback = function('PrintArgs', ["foo", "bar"])
let g:job_opts = {'on_stdout': Callback}
call jobstart(['echo'], g:job_opts)
]])
eq({'notification', '1', {'foo', 'bar', {'', ''}, 'stdout'}}, next_msg())
end)
end)

View File

@@ -18,7 +18,7 @@ describe('jobs', function()
channel = nvim('get_api_info')[1]
nvim('set_var', 'channel', channel)
source([[
function! s:OnEvent(id, data, event)
function! s:OnEvent(id, data, event) dict
let userdata = get(self, 'user')
call rpcnotify(g:channel, a:event, userdata, a:data)
endfunction
@@ -265,9 +265,12 @@ describe('jobs', function()
eq({'notification', 'exit', {45, 10}}, next_msg())
end)
it('cannot redefine callbacks being used by a job', function()
it('can redefine callbacks being used by a job', function()
local screen = Screen.new()
screen:attach()
screen:set_default_attr_ids({
[1] = {bold=true, foreground=Screen.colors.Blue},
})
local script = [[
function! g:JobHandler(job_id, data, event)
endfunction
@@ -283,20 +286,20 @@ describe('jobs', function()
feed(':function! g:JobHandler(job_id, data, event)<cr>')
feed(':endfunction<cr>')
screen:expect([[
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
:function! g:JobHandler(job_id, data, event) |
: :endfunction |
E127: Cannot redefine function JobHandler: It is in u|
se |
Press ENTER or type command to continue^ |
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
|
]])
end)
@@ -317,7 +320,7 @@ describe('jobs', function()
source([[
let g:dict = {'id': 10}
let g:exits = 0
function g:dict.on_exit(id, code)
function g:dict.on_exit(id, code, event)
if a:code != 5
throw 'Error!'
endif
@@ -365,7 +368,7 @@ describe('jobs', function()
eq({'notification', 'wait', {{-2}}}, next_msg())
end)
it('can be called recursively', function()
pending('can be called recursively', function()
source([[
let g:opts = {}
let g:counter = 0

View File

@@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each)
local clear, nvim, source = helpers.clear, helpers.nvim, helpers.source
local eq, next_msg = helpers.eq, helpers.next_message
local exc_exec = helpers.exc_exec
local command = helpers.command
describe('dictionary change notifications', function()
@@ -229,11 +230,9 @@ describe('dictionary change notifications', function()
exc_exec('call dictwatcherdel(g:, "invalid_key", "g:Watcher2")'))
end)
it("fails to add/remove if the callback doesn't exist", function()
eq("Vim(call):Function g:InvalidCb doesn't exist",
exc_exec('call dictwatcheradd(g:, "key", "g:InvalidCb")'))
eq("Vim(call):Function g:InvalidCb doesn't exist",
exc_exec('call dictwatcherdel(g:, "key", "g:InvalidCb")'))
it("does not fail to add/remove if the callback doesn't exist", function()
command('call dictwatcheradd(g:, "key", "g:InvalidCb")')
command('call dictwatcherdel(g:, "key", "g:InvalidCb")')
end)
it('fails with empty keys', function()
@@ -243,15 +242,18 @@ describe('dictionary change notifications', function()
exc_exec('call dictwatcherdel(g:, "", "g:Watcher1")'))
end)
it('fails to replace a watcher function', function()
it('does not fail to replace a watcher function', function()
source([[
function! g:ReplaceWatcher2()
function! g:Watcher2()
function! g:Watcher2(dict, key, value)
call rpcnotify(g:channel, '2b', a:key, a:value)
endfunction
endfunction
]])
eq("Vim(function):E127: Cannot redefine function Watcher2: It is in use",
exc_exec('call g:ReplaceWatcher2()'))
command('call g:ReplaceWatcher2()')
command('let g:key = "value"')
eq({'notification', '2b', {'key', {old = 'v2', new = 'value'}}}, next_msg())
end)
end)
end)

View File

@@ -274,17 +274,13 @@ describe('list and dictionary types', function()
let dict.data = [1,2,3]
call dict.func("len: ")
let x = dict.func("again: ")
try
let Fn = dict.func
call Fn('xxx')
catch
$put =v:exception[:15]
endtry]])
let Fn = dict.func
call Fn('xxx')]])
expect([[
len: 3
again: 3
Vim(call):E725: ]])
xxx3]])
end)
it('Function in script-local List or Dict', function()

View File

@@ -167,7 +167,7 @@ describe('terminal buffer', function()
local tbuf = eval('bufnr("%")')
source([[
function! SplitWindow()
function! SplitWindow(id, data, event)
new
call feedkeys("iabc\<Esc>")
endfunction