diff options
-rw-r--r-- | runtime/doc/autocmd.txt | 2 | ||||
-rw-r--r-- | runtime/doc/eval.txt | 6 | ||||
-rw-r--r-- | src/nvim/edit.c | 19 | ||||
-rw-r--r-- | src/nvim/eval.c | 40 | ||||
-rw-r--r-- | src/nvim/eval.h | 1 | ||||
-rw-r--r-- | src/nvim/macros.h | 3 | ||||
-rw-r--r-- | test/functional/viml/completion_spec.lua | 56 |
7 files changed, 121 insertions, 6 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 9224151cee..af9676272f 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -506,6 +506,8 @@ ColorScheme After loading a color scheme. |:colorscheme| CompleteDone After Insert mode completion is done. Either when something was completed or abandoning completion. |ins-completion| + The |v:completed_item| variable contains the + completed item. *CursorHold* CursorHold When the user doesn't press a key for the time diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index d83d4c0666..81a965d0c6 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1326,6 +1326,12 @@ v:cmdbang Set like v:cmdarg for a file read/write command. When a "!" can only be used in autocommands. For user commands |<bang>| can be used. + *v:completed_item* *completed_item-variable* +v:completed_item + Dictionary containing the most recent |complete-items| after + |CompleteDone|. Empty if the completion failed, or after + leaving and re-entering insert mode. + *v:count* *count-variable* v:count The count given for the last Normal mode command. Can be used to get the count before a mapping. Read-only. Example: > diff --git a/src/nvim/edit.c b/src/nvim/edit.c index ffbe0e0348..31a5c54c20 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -2734,6 +2734,8 @@ static void ins_compl_clear(void) xfree(compl_orig_text); compl_orig_text = NULL; compl_enter_selects = FALSE; + // clear v:completed_item + set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc()); } /* @@ -3815,6 +3817,8 @@ static void ins_compl_delete(void) // TODO: is this sufficient for redrawing? Redrawing everything causes // flicker, thus we can't do that. changed_cline_bef_curs(); + // clear v:completed_item + set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc()); } /* Insert the new text being completed. */ @@ -3825,6 +3829,21 @@ static void ins_compl_insert(void) compl_used_match = FALSE; else compl_used_match = TRUE; + + // Set completed item. + // { word, abbr, menu, kind, info } + dict_T *dict = dict_alloc(); + dict_add_nr_str(dict, "word", 0L, + EMPTY_IF_NULL(compl_shown_match->cp_str)); + dict_add_nr_str(dict, "abbr", 0L, + EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_ABBR])); + dict_add_nr_str(dict, "menu", 0L, + EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_MENU])); + dict_add_nr_str(dict, "kind", 0L, + EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_KIND])); + dict_add_nr_str(dict, "info", 0L, + EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_INFO])); + set_vim_var_dict(VV_COMPLETED_ITEM, dict); } /* diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e7cb830e67..6bd2b23c73 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -426,7 +426,8 @@ static struct vimvar { {VV_NAME("oldfiles", VAR_LIST), 0}, {VV_NAME("windowid", VAR_NUMBER), VV_RO}, {VV_NAME("progpath", VAR_STRING), VV_RO}, - {VV_NAME("command_output", VAR_STRING), 0} + {VV_NAME("command_output", VAR_STRING), 0}, + {VV_NAME("completed_item", VAR_DICT), VV_RO}, }; /* shorthand */ @@ -435,6 +436,7 @@ static struct vimvar { #define vv_float vv_di.di_tv.vval.v_float #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_tv vv_di.di_tv static dictitem_T vimvars_var; /* variable used for v: */ @@ -16281,7 +16283,7 @@ void set_vim_var_nr(int idx, long val) /* * Get number v: variable value. */ -long get_vim_var_nr(int idx) +long get_vim_var_nr(int idx) FUNC_ATTR_PURE { return vimvars[idx].vv_nr; } @@ -16289,7 +16291,7 @@ long get_vim_var_nr(int idx) /* * Get string v: variable value. Uses a static buffer, can only be used once. */ -char_u *get_vim_var_str(int idx) FUNC_ATTR_NONNULL_RET +char_u *get_vim_var_str(int idx) FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET { return get_tv_string(&vimvars[idx].vv_tv); } @@ -16298,11 +16300,18 @@ char_u *get_vim_var_str(int idx) FUNC_ATTR_NONNULL_RET * Get List v: variable value. Caller must take care of reference count when * needed. */ -list_T *get_vim_var_list(int idx) +list_T *get_vim_var_list(int idx) FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET { return vimvars[idx].vv_list; } +/// Get Dictionary v: variable value. Caller must take care of reference count +/// when needed. +dict_T *get_vim_var_dict(int idx) FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET +{ + return vimvars[idx].vv_dict; +} + /* * Set v:char to character "c". */ @@ -16334,8 +16343,7 @@ void set_vcount(long count, long count1, int set_prevcount) /* * Set string v: variable to a copy of "val". */ -void -set_vim_var_string ( +void set_vim_var_string ( int idx, char_u *val, int len /* length of "val" to use or -1 (whole string) */ @@ -16365,6 +16373,26 @@ void set_vim_var_list(int idx, list_T *val) ++val->lv_refcount; } +/// Set Dictionary v: variable to "val". +void set_vim_var_dict(int idx, dict_T *val) FUNC_ATTR_NONNULL_ALL +{ + dict_unref(vimvars[idx].vv_dict); + + // Set readonly + int todo = (int)val->dv_hashtab.ht_used; + for (hashitem_T *hi = val->dv_hashtab.ht_array; todo > 0 ; ++hi) { + if (HASHITEM_EMPTY(hi)) { + continue; + } + + --todo; + HI2DI(hi)->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + } + + vimvars[idx].vv_dict = val; + ++val->dv_refcount; +} + /* * Set v:register if needed. */ diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 61a65df7c6..75e3b247f3 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -64,6 +64,7 @@ enum { VV_WINDOWID, VV_PROGPATH, VV_COMMAND_OUTPUT, + VV_COMPLETED_ITEM, VV_LEN, /* number of v: vars */ }; diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 51542bd3a7..bbbce7ab58 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -63,6 +63,9 @@ # define ASCII_ISALPHA(c) (ASCII_ISUPPER(c) || ASCII_ISLOWER(c)) # define ASCII_ISALNUM(c) (ASCII_ISALPHA(c) || ascii_isdigit(c)) +/* Returns empty string if it is NULL. */ +#define EMPTY_IF_NULL(x) ((x) ? (x) : (char_u *)"") + /* macro version of chartab(). * Only works with values 0-255! * Doesn't work for UTF-8 mode with chars >= 0x80. */ diff --git a/test/functional/viml/completion_spec.lua b/test/functional/viml/completion_spec.lua new file mode 100644 index 0000000000..df4018e707 --- /dev/null +++ b/test/functional/viml/completion_spec.lua @@ -0,0 +1,56 @@ +local helpers = require('test.functional.helpers') +local clear, feed, execute = helpers.clear, helpers.feed, helpers.execute +local eval, eq, neq = helpers.eval, helpers.eq, helpers.neq +local execute, source = helpers.execute, helpers.source + +describe("completion", function() + before_each(function() + clear() + end) + + describe("v:completed_item", function() + it('returns expected dict in normal completion', function() + feed('ifoo<ESC>o<C-x><C-n><ESC>') + eq('foo', eval('getline(2)')) + eq({word = 'foo', abbr = '', menu = '', info = '', kind = ''}, + eval('v:completed_item')) + end) + it('is readonly', function() + feed('ifoo<ESC>o<C-x><C-n><ESC>') + + execute('let v:completed_item.word = "bar"') + neq(nil, string.find(eval('v:errmsg'), '^E46: ')) + execute('let v:errmsg = ""') + + execute('let v:completed_item.abbr = "bar"') + neq(nil, string.find(eval('v:errmsg'), '^E46: ')) + execute('let v:errmsg = ""') + + execute('let v:completed_item.menu = "bar"') + neq(nil, string.find(eval('v:errmsg'), '^E46: ')) + execute('let v:errmsg = ""') + + execute('let v:completed_item.info = "bar"') + neq(nil, string.find(eval('v:errmsg'), '^E46: ')) + execute('let v:errmsg = ""') + + execute('let v:completed_item.kind = "bar"') + neq(nil, string.find(eval('v:errmsg'), '^E46: ')) + execute('let v:errmsg = ""') + end) + it('returns expected dict in omni completion', function() + source([[ + function! TestOmni(findstart, base) abort + return a:findstart ? 0 : [{'word': 'foo', 'abbr': 'bar', + \ 'menu': 'baz', 'info': 'foobar', 'kind': 'foobaz'}] + endfunction + setlocal omnifunc=TestOmni + ]]) + feed('i<C-x><C-o><ESC>') + eq('foo', eval('getline(1)')) + eq({word = 'foo', abbr = 'bar', menu = 'baz', + info = 'foobar', kind = 'foobaz'}, + eval('v:completed_item')) + end) + end) +end) |