1
0
forked from aniani/vim

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)
This commit is contained in:
Bram Moolenaar
2017-12-16 18:27:02 +01:00
parent 6621605eb9
commit 7e1652c63c
12 changed files with 201 additions and 18 deletions

View File

@@ -330,6 +330,7 @@ Name triggered by ~
|TextChanged| after a change was made to the text in Normal mode |TextChanged| after a change was made to the text in Normal mode
|TextChangedI| after a change was made to the text in Insert 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 |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. current buffer in Insert mode.
Not triggered when the popup menu is visible. Not triggered when the popup menu is visible.
Otherwise the same as TextChanged. 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*
User Never executed automatically. To be used for User Never executed automatically. To be used for
autocommands that are only executed with autocommands that are only executed with

View File

@@ -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 < If v:errors is set to anything but a list it is made an empty
list by the assert function. 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* *exception-variable*
v:exception The value of the exception most recently caught and not v:exception The value of the exception most recently caught and not
finished. See also |v:throwpoint| and |throw-variables|. finished. See also |v:throwpoint| and |throw-variables|.

View File

@@ -47,6 +47,16 @@ dict_alloc(void)
return d; 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. * Allocate an empty dict for a return value.
* Returns OK or FAIL. * Returns OK or FAIL.
@@ -54,13 +64,12 @@ dict_alloc(void)
int int
rettv_dict_alloc(typval_T *rettv) rettv_dict_alloc(typval_T *rettv)
{ {
dict_T *d = dict_alloc(); dict_T *d = dict_alloc_lock(0);
if (d == NULL) if (d == NULL)
return FAIL; return FAIL;
rettv_dict_set(rettv, d); rettv_dict_set(rettv, d);
rettv->v_lock = 0;
return OK; 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. * Free a Dictionary, including all non-container items it contains.
* Ignores the reference count. * Ignores the reference count.
*/ */
static void void
dict_free_contents(dict_T *d) dict_free_contents(dict_T *d)
{ {
int todo; int todo;
@@ -102,6 +111,8 @@ dict_free_contents(dict_T *d)
--todo; --todo;
} }
} }
/* The hashtab is still locked, it has to be re-initialized anyway */
hash_clear(&d->dv_hashtab); 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) */ #endif /* defined(FEAT_EVAL) */

View File

@@ -192,6 +192,7 @@ static struct vimvar
{VV_NAME("termu7resp", VAR_STRING), VV_RO}, {VV_NAME("termu7resp", VAR_STRING), VV_RO},
{VV_NAME("termstyleresp", VAR_STRING), VV_RO}, {VV_NAME("termstyleresp", VAR_STRING), VV_RO},
{VV_NAME("termblinkresp", VAR_STRING), VV_RO}, {VV_NAME("termblinkresp", VAR_STRING), VV_RO},
{VV_NAME("event", VAR_DICT), VV_RO},
}; };
/* shorthand */ /* shorthand */
@@ -319,8 +320,9 @@ eval_init(void)
set_vim_var_nr(VV_SEARCHFORWARD, 1L); set_vim_var_nr(VV_SEARCHFORWARD, 1L);
set_vim_var_nr(VV_HLSEARCH, 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_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_FALSE, VVAL_FALSE);
set_vim_var_nr(VV_TRUE, VVAL_TRUE); set_vim_var_nr(VV_TRUE, VVAL_TRUE);
@@ -6632,6 +6634,16 @@ get_vim_var_list(int idx)
return vimvars[idx].vv_list; 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". * Set v:char to character "c".
*/ */
@@ -6706,25 +6718,13 @@ set_vim_var_list(int idx, list_T *val)
void void
set_vim_var_dict(int idx, dict_T *val) set_vim_var_dict(int idx, dict_T *val)
{ {
int todo;
hashitem_T *hi;
clear_tv(&vimvars[idx].vv_di.di_tv); clear_tv(&vimvars[idx].vv_di.di_tv);
vimvars[idx].vv_type = VAR_DICT; vimvars[idx].vv_type = VAR_DICT;
vimvars[idx].vv_dict = val; vimvars[idx].vv_dict = val;
if (val != NULL) if (val != NULL)
{ {
++val->dv_refcount; ++val->dv_refcount;
dict_set_items_ro(val);
/* 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;
}
} }
} }

View File

@@ -6478,6 +6478,7 @@ buf_modname(
/* /*
* Like fgets(), but if the file line is too long, it is truncated and the * 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. * 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 int
vim_fgets(char_u *buf, int size, FILE *fp) vim_fgets(char_u *buf, int size, FILE *fp)
@@ -7856,6 +7857,7 @@ static struct event_name
{"WinEnter", EVENT_WINENTER}, {"WinEnter", EVENT_WINENTER},
{"WinLeave", EVENT_WINLEAVE}, {"WinLeave", EVENT_WINLEAVE},
{"VimResized", EVENT_VIMRESIZED}, {"VimResized", EVENT_VIMRESIZED},
{"TextYankPost", EVENT_TEXTYANKPOST},
{NULL, (event_T)0} {NULL, (event_T)0}
}; };
@@ -9399,6 +9401,15 @@ has_funcundefined(void)
return (first_autopat[(int)EVENT_FUNCUNDEFINED] != NULL); 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". * Execute autocommands for "event" and file name "fname".
* Return TRUE if some commands were executed. * Return TRUE if some commands were executed.

View File

@@ -1645,6 +1645,63 @@ shift_delete_registers()
y_regs[1].y_array = NULL; /* set register one to empty */ 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. * Handle a delete operation.
* *
@@ -1798,6 +1855,11 @@ op_delete(oparg_T *oap)
return FAIL; 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
#endif #endif
#ifdef FEAT_AUTOCMD
if (!deleting && has_textyankpost())
yank_do_autocmd(oap, y_current);
#endif
return OK; return OK;
fail: /* free the allocated lines */ fail: /* free the allocated lines */

View File

@@ -1,7 +1,9 @@
/* dict.c */ /* dict.c */
dict_T *dict_alloc(void); dict_T *dict_alloc(void);
dict_T *dict_alloc_lock(int lock);
int rettv_dict_alloc(typval_T *rettv); int rettv_dict_alloc(typval_T *rettv);
void rettv_dict_set(typval_T *rettv, dict_T *d); void rettv_dict_set(typval_T *rettv, dict_T *d);
void dict_free_contents(dict_T *d);
void dict_unref(dict_T *d); void dict_unref(dict_T *d);
int dict_free_nonref(int copyID); int dict_free_nonref(int copyID);
void dict_free_items(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); dictitem_T *dict_lookup(hashitem_T *hi);
int dict_equal(dict_T *d1, dict_T *d2, int ic, int recursive); 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_list(typval_T *argvars, typval_T *rettv, int what);
void dict_set_items_ro(dict_T *di);
/* vim: set ft=c : */ /* vim: set ft=c : */

View File

@@ -64,6 +64,7 @@ void set_vim_var_nr(int idx, varnumber_T val);
varnumber_T get_vim_var_nr(int idx); varnumber_T get_vim_var_nr(int idx);
char_u *get_vim_var_str(int idx); char_u *get_vim_var_str(int idx);
list_T *get_vim_var_list(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_vim_var_char(int c);
void set_vcount(long count, long count1, int set_prevcount); void set_vcount(long count, long count1, int set_prevcount);
void set_vim_var_string(int idx, char_u *val, int len); void set_vim_var_string(int idx, char_u *val, int len);

View File

@@ -51,6 +51,7 @@ int has_textchangedI(void);
int has_insertcharpre(void); int has_insertcharpre(void);
int has_cmdundefined(void); int has_cmdundefined(void);
int has_funcundefined(void); int has_funcundefined(void);
int has_textyankpost(void);
void block_autocmds(void); void block_autocmds(void);
void unblock_autocmds(void); void unblock_autocmds(void);
int is_autocmd_blocked(void); int is_autocmd_blocked(void);

View File

@@ -1124,3 +1124,42 @@ func Test_Filter_noshelltemp()
let &shelltemp = shelltemp let &shelltemp = shelltemp
bwipe! bwipe!
endfunc 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 File

@@ -771,6 +771,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1394,
/**/ /**/
1393, 1393,
/**/ /**/

View File

@@ -1339,6 +1339,7 @@ enum auto_event
EVENT_TEXTCHANGEDI, /* text was modified in Insert mode*/ EVENT_TEXTCHANGEDI, /* text was modified in Insert mode*/
EVENT_CMDUNDEFINED, /* command undefined */ EVENT_CMDUNDEFINED, /* command undefined */
EVENT_OPTIONSET, /* option was set */ EVENT_OPTIONSET, /* option was set */
EVENT_TEXTYANKPOST, /* after some text was yanked */
NUM_EVENTS /* MUST be the last one */ NUM_EVENTS /* MUST be the last one */
}; };
@@ -1988,7 +1989,8 @@ typedef int sock_T;
#define VV_TERMU7RESP 83 #define VV_TERMU7RESP 83
#define VV_TERMSTYLERESP 84 #define VV_TERMSTYLERESP 84
#define VV_TERMBLINKRESP 85 #define VV_TERMBLINKRESP 85
#define VV_LEN 86 /* number of v: vars */ #define VV_EVENT 86
#define VV_LEN 87 /* number of v: vars */
/* used for v_number in VAR_SPECIAL */ /* used for v_number in VAR_SPECIAL */
#define VVAL_FALSE 0L #define VVAL_FALSE 0L