Permalink
Browse files

patch 8.0.1394: cannot intercept a yank command

Problem:    Cannot intercept a yank command.
Solution:   Add the TextYankPost autocommand event. (Philippe Vaucher et al.,
            closes #2333)
  • Loading branch information...
brammool committed Dec 16, 2017
1 parent 6621605 commit 7e1652c63c96585b9e2235c195a3c322b1f11595
Showing with 201 additions and 18 deletions.
  1. +21 −0 runtime/doc/autocmd.txt
  2. +6 −0 runtime/doc/eval.txt
  3. +33 −3 src/dict.c
  4. +14 −14 src/eval.c
  5. +11 −0 src/fileio.c
  6. +67 −0 src/ops.c
  7. +3 −0 src/proto/dict.pro
  8. +1 −0 src/proto/eval.pro
  9. +1 −0 src/proto/fileio.pro
  10. +39 −0 src/testdir/test_autocmd.vim
  11. +2 −0 src/version.c
  12. +3 −1 src/vim.h
View
@@ -330,6 +330,7 @@ Name triggered by ~
|TextChanged| after a change was made to the text in Normal mode
|TextChangedI| after a change was made to the text in Insert mode
|TextYankPost| after text is yanked or deleted
|ColorScheme| after loading a color scheme
@@ -956,6 +957,26 @@ TextChangedI After a change was made to the text in the
current buffer in Insert mode.
Not triggered when the popup menu is visible.
Otherwise the same as TextChanged.
|TextYankPost|
TextYankPost After text has been yanked or deleted in the
current buffer. The following values of
|v:event| can be used to determine the operation
that triggered this autocmd:
operator The operation performed.
regcontents Text that was stored in the
register, as a list of lines,
like with: >
getreg(r, 1, 1)
< regname Name of the |register| or
empty string for the unnamed
register.
regtype Type of the register, see
|getregtype()|.
Not triggered when |quote_| is used nor when
called recursively.
It is not allowed to change the buffer text,
see |textlock|.
*User*
User Never executed automatically. To be used for
autocommands that are only executed with
View
@@ -1554,6 +1554,12 @@ v:errors Errors found by assert functions, such as |assert_true()|.
< If v:errors is set to anything but a list it is made an empty
list by the assert function.
*v:event* *event-variable*
v:event Dictionary containing information about the current
|autocommand|. The dictionary is emptied when the |autocommand|
finishes, please refer to |dict-identity| for how to get an
independent copy of it.
*v:exception* *exception-variable*
v:exception The value of the exception most recently caught and not
finished. See also |v:throwpoint| and |throw-variables|.
View
@@ -47,20 +47,29 @@ dict_alloc(void)
return d;
}
dict_T *
dict_alloc_lock(int lock)
{
dict_T *d = dict_alloc();
if (d != NULL)
d->dv_lock = lock;
return d;
}
/*
* Allocate an empty dict for a return value.
* Returns OK or FAIL.
*/
int
rettv_dict_alloc(typval_T *rettv)
{
dict_T *d = dict_alloc();
dict_T *d = dict_alloc_lock(0);
if (d == NULL)
return FAIL;
rettv_dict_set(rettv, d);
rettv->v_lock = 0;
return OK;
}
@@ -80,7 +89,7 @@ rettv_dict_set(typval_T *rettv, dict_T *d)
* Free a Dictionary, including all non-container items it contains.
* Ignores the reference count.
*/
static void
void
dict_free_contents(dict_T *d)
{
int todo;
@@ -102,6 +111,8 @@ dict_free_contents(dict_T *d)
--todo;
}
}
/* The hashtab is still locked, it has to be re-initialized anyway */
hash_clear(&d->dv_hashtab);
}
@@ -846,4 +857,23 @@ dict_list(typval_T *argvars, typval_T *rettv, int what)
}
}
/*
* Make each item in the dict readonly (not the value of the item).
*/
void
dict_set_items_ro(dict_T *di)
{
int todo = (int)di->dv_hashtab.ht_used;
hashitem_T *hi;
/* Set readonly */
for (hi = di->dv_hashtab.ht_array; todo > 0 ; ++hi)
{
if (HASHITEM_EMPTY(hi))
continue;
--todo;
HI2DI(hi)->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX;
}
}
#endif /* defined(FEAT_EVAL) */
View
@@ -192,6 +192,7 @@ static struct vimvar
{VV_NAME("termu7resp", VAR_STRING), VV_RO},
{VV_NAME("termstyleresp", VAR_STRING), VV_RO},
{VV_NAME("termblinkresp", VAR_STRING), VV_RO},
{VV_NAME("event", VAR_DICT), VV_RO},
};
/* shorthand */
@@ -319,8 +320,9 @@ eval_init(void)
set_vim_var_nr(VV_SEARCHFORWARD, 1L);
set_vim_var_nr(VV_HLSEARCH, 1L);
set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc());
set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc_lock(VAR_FIXED));
set_vim_var_list(VV_ERRORS, list_alloc());
set_vim_var_dict(VV_EVENT, dict_alloc_lock(VAR_FIXED));
set_vim_var_nr(VV_FALSE, VVAL_FALSE);
set_vim_var_nr(VV_TRUE, VVAL_TRUE);
@@ -6632,6 +6634,16 @@ get_vim_var_list(int idx)
return vimvars[idx].vv_list;
}
/*
* Get Dict v: variable value. Caller must take care of reference count when
* needed.
*/
dict_T *
get_vim_var_dict(int idx)
{
return vimvars[idx].vv_dict;
}
/*
* Set v:char to character "c".
*/
@@ -6706,25 +6718,13 @@ set_vim_var_list(int idx, list_T *val)
void
set_vim_var_dict(int idx, dict_T *val)
{
int todo;
hashitem_T *hi;
clear_tv(&vimvars[idx].vv_di.di_tv);
vimvars[idx].vv_type = VAR_DICT;
vimvars[idx].vv_dict = val;
if (val != NULL)
{
++val->dv_refcount;
/* Set readonly */
todo = (int)val->dv_hashtab.ht_used;
for (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;
}
dict_set_items_ro(val);
}
}
View
@@ -6478,6 +6478,7 @@ buf_modname(
/*
* Like fgets(), but if the file line is too long, it is truncated and the
* rest of the line is thrown away. Returns TRUE for end-of-file.
* If the line is truncated then buf[size - 2] will not be NUL.
*/
int
vim_fgets(char_u *buf, int size, FILE *fp)
@@ -7856,6 +7857,7 @@ static struct event_name
{"WinEnter", EVENT_WINENTER},
{"WinLeave", EVENT_WINLEAVE},
{"VimResized", EVENT_VIMRESIZED},
{"TextYankPost", EVENT_TEXTYANKPOST},
{NULL, (event_T)0}
};
@@ -9399,6 +9401,15 @@ has_funcundefined(void)
return (first_autopat[(int)EVENT_FUNCUNDEFINED] != NULL);
}
/*
* Return TRUE when there is a TextYankPost autocommand defined.
*/
int
has_textyankpost(void)
{
return (first_autopat[(int)EVENT_TEXTYANKPOST] != NULL);
}
/*
* Execute autocommands for "event" and file name "fname".
* Return TRUE if some commands were executed.
View
@@ -1645,6 +1645,63 @@ shift_delete_registers()
y_regs[1].y_array = NULL; /* set register one to empty */
}
static void
yank_do_autocmd(oparg_T *oap, yankreg_T *reg)
{
static int recursive = FALSE;
dict_T *v_event;
list_T *list;
int n;
char_u buf[NUMBUFLEN + 2];
long reglen = 0;
if (recursive)
return;
v_event = get_vim_var_dict(VV_EVENT);
list = list_alloc();
for (n = 0; n < reg->y_size; n++)
list_append_string(list, reg->y_array[n], -1);
list->lv_lock = VAR_FIXED;
dict_add_list(v_event, "regcontents", list);
buf[0] = (char_u)oap->regname;
buf[1] = NUL;
dict_add_nr_str(v_event, "regname", 0, buf);
buf[0] = get_op_char(oap->op_type);
buf[1] = get_extra_op_char(oap->op_type);
buf[2] = NUL;
dict_add_nr_str(v_event, "operator", 0, buf);
buf[0] = NUL;
buf[1] = NUL;
switch (get_reg_type(oap->regname, &reglen))
{
case MLINE: buf[0] = 'V'; break;
case MCHAR: buf[0] = 'v'; break;
case MBLOCK:
vim_snprintf((char *)buf, sizeof(buf), "%c%ld", Ctrl_V,
reglen + 1);
break;
}
dict_add_nr_str(v_event, "regtype", 0, buf);
/* Lock the dictionary and its keys */
dict_set_items_ro(v_event);
recursive = TRUE;
textlock++;
apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, FALSE, curbuf);
textlock--;
recursive = FALSE;
/* Empty the dictionary, v:event is still valid */
dict_free_contents(v_event);
hash_init(&v_event->dv_hashtab);
}
/*
* Handle a delete operation.
*
@@ -1798,6 +1855,11 @@ op_delete(oparg_T *oap)
return FAIL;
}
}
#ifdef FEAT_AUTOCMD
if (did_yank && has_textyankpost())
yank_do_autocmd(oap, y_current);
#endif
}
/*
@@ -3270,6 +3332,11 @@ op_yank(oparg_T *oap, int deleting, int mess)
# endif
#endif
#ifdef FEAT_AUTOCMD
if (!deleting && has_textyankpost())
yank_do_autocmd(oap, y_current);
#endif
return OK;
fail: /* free the allocated lines */
View
@@ -1,7 +1,9 @@
/* dict.c */
dict_T *dict_alloc(void);
dict_T *dict_alloc_lock(int lock);
int rettv_dict_alloc(typval_T *rettv);
void rettv_dict_set(typval_T *rettv, dict_T *d);
void dict_free_contents(dict_T *d);
void dict_unref(dict_T *d);
int dict_free_nonref(int copyID);
void dict_free_items(int copyID);
@@ -23,4 +25,5 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action);
dictitem_T *dict_lookup(hashitem_T *hi);
int dict_equal(dict_T *d1, dict_T *d2, int ic, int recursive);
void dict_list(typval_T *argvars, typval_T *rettv, int what);
void dict_set_items_ro(dict_T *di);
/* vim: set ft=c : */
View
@@ -64,6 +64,7 @@ void set_vim_var_nr(int idx, varnumber_T val);
varnumber_T get_vim_var_nr(int idx);
char_u *get_vim_var_str(int idx);
list_T *get_vim_var_list(int idx);
dict_T * get_vim_var_dict(int idx);
void set_vim_var_char(int c);
void set_vcount(long count, long count1, int set_prevcount);
void set_vim_var_string(int idx, char_u *val, int len);
View
@@ -51,6 +51,7 @@ int has_textchangedI(void);
int has_insertcharpre(void);
int has_cmdundefined(void);
int has_funcundefined(void);
int has_textyankpost(void);
void block_autocmds(void);
void unblock_autocmds(void);
int is_autocmd_blocked(void);
@@ -1124,3 +1124,42 @@ func Test_Filter_noshelltemp()
let &shelltemp = shelltemp
bwipe!
endfunc
func Test_TextYankPost()
enew!
call setline(1, ['foo'])
let g:event = []
au TextYankPost * let g:event = copy(v:event)
call assert_equal({}, v:event)
call assert_fails('let v:event = {}', 'E46:')
call assert_fails('let v:event.mykey = 0', 'E742:')
norm "ayiw
call assert_equal(
\{'regcontents': ['foo'], 'regname': 'a', 'operator': 'y', 'regtype': 'v'},
\g:event)
norm y_
call assert_equal(
\{'regcontents': ['foo'], 'regname': '', 'operator': 'y', 'regtype': 'V'},
\g:event)
call feedkeys("\<C-V>y", 'x')
call assert_equal(
\{'regcontents': ['f'], 'regname': '', 'operator': 'y', 'regtype': "\x161"},
\g:event)
norm "xciwbar
call assert_equal(
\{'regcontents': ['foo'], 'regname': 'x', 'operator': 'c', 'regtype': 'v'},
\g:event)
norm "bdiw
call assert_equal(
\{'regcontents': ['bar'], 'regname': 'b', 'operator': 'd', 'regtype': 'v'},
\g:event)
call assert_equal({}, v:event)
au! TextYankPost
unlet g:event
bwipe!
endfunc
View
@@ -771,6 +771,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1394,
/**/
1393,
/**/
Oops, something went wrong.

0 comments on commit 7e1652c

Please sign in to comment.