Skip to content

Commit 7e1652c

Browse files
committed
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)
1 parent 6621605 commit 7e1652c

File tree

12 files changed

+201
-18
lines changed

12 files changed

+201
-18
lines changed

runtime/doc/autocmd.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ Name triggered by ~
330330

331331
|TextChanged| after a change was made to the text in Normal mode
332332
|TextChangedI| after a change was made to the text in Insert mode
333+
|TextYankPost| after text is yanked or deleted
333334

334335
|ColorScheme| after loading a color scheme
335336

@@ -956,6 +957,26 @@ TextChangedI After a change was made to the text in the
956957
current buffer in Insert mode.
957958
Not triggered when the popup menu is visible.
958959
Otherwise the same as TextChanged.
960+
|TextYankPost|
961+
TextYankPost After text has been yanked or deleted in the
962+
current buffer. The following values of
963+
|v:event| can be used to determine the operation
964+
that triggered this autocmd:
965+
operator The operation performed.
966+
regcontents Text that was stored in the
967+
register, as a list of lines,
968+
like with: >
969+
getreg(r, 1, 1)
970+
< regname Name of the |register| or
971+
empty string for the unnamed
972+
register.
973+
regtype Type of the register, see
974+
|getregtype()|.
975+
Not triggered when |quote_| is used nor when
976+
called recursively.
977+
It is not allowed to change the buffer text,
978+
see |textlock|.
979+
959980
*User*
960981
User Never executed automatically. To be used for
961982
autocommands that are only executed with

runtime/doc/eval.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,6 +1554,12 @@ v:errors Errors found by assert functions, such as |assert_true()|.
15541554
< If v:errors is set to anything but a list it is made an empty
15551555
list by the assert function.
15561556

1557+
*v:event* *event-variable*
1558+
v:event Dictionary containing information about the current
1559+
|autocommand|. The dictionary is emptied when the |autocommand|
1560+
finishes, please refer to |dict-identity| for how to get an
1561+
independent copy of it.
1562+
15571563
*v:exception* *exception-variable*
15581564
v:exception The value of the exception most recently caught and not
15591565
finished. See also |v:throwpoint| and |throw-variables|.

src/dict.c

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,29 @@ dict_alloc(void)
4747
return d;
4848
}
4949

50+
dict_T *
51+
dict_alloc_lock(int lock)
52+
{
53+
dict_T *d = dict_alloc();
54+
55+
if (d != NULL)
56+
d->dv_lock = lock;
57+
return d;
58+
}
59+
5060
/*
5161
* Allocate an empty dict for a return value.
5262
* Returns OK or FAIL.
5363
*/
5464
int
5565
rettv_dict_alloc(typval_T *rettv)
5666
{
57-
dict_T *d = dict_alloc();
67+
dict_T *d = dict_alloc_lock(0);
5868

5969
if (d == NULL)
6070
return FAIL;
6171

6272
rettv_dict_set(rettv, d);
63-
rettv->v_lock = 0;
6473
return OK;
6574
}
6675

@@ -80,7 +89,7 @@ rettv_dict_set(typval_T *rettv, dict_T *d)
8089
* Free a Dictionary, including all non-container items it contains.
8190
* Ignores the reference count.
8291
*/
83-
static void
92+
void
8493
dict_free_contents(dict_T *d)
8594
{
8695
int todo;
@@ -102,6 +111,8 @@ dict_free_contents(dict_T *d)
102111
--todo;
103112
}
104113
}
114+
115+
/* The hashtab is still locked, it has to be re-initialized anyway */
105116
hash_clear(&d->dv_hashtab);
106117
}
107118

@@ -846,4 +857,23 @@ dict_list(typval_T *argvars, typval_T *rettv, int what)
846857
}
847858
}
848859

860+
/*
861+
* Make each item in the dict readonly (not the value of the item).
862+
*/
863+
void
864+
dict_set_items_ro(dict_T *di)
865+
{
866+
int todo = (int)di->dv_hashtab.ht_used;
867+
hashitem_T *hi;
868+
869+
/* Set readonly */
870+
for (hi = di->dv_hashtab.ht_array; todo > 0 ; ++hi)
871+
{
872+
if (HASHITEM_EMPTY(hi))
873+
continue;
874+
--todo;
875+
HI2DI(hi)->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX;
876+
}
877+
}
878+
849879
#endif /* defined(FEAT_EVAL) */

src/eval.c

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ static struct vimvar
192192
{VV_NAME("termu7resp", VAR_STRING), VV_RO},
193193
{VV_NAME("termstyleresp", VAR_STRING), VV_RO},
194194
{VV_NAME("termblinkresp", VAR_STRING), VV_RO},
195+
{VV_NAME("event", VAR_DICT), VV_RO},
195196
};
196197

197198
/* shorthand */
@@ -319,8 +320,9 @@ eval_init(void)
319320

320321
set_vim_var_nr(VV_SEARCHFORWARD, 1L);
321322
set_vim_var_nr(VV_HLSEARCH, 1L);
322-
set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc());
323+
set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc_lock(VAR_FIXED));
323324
set_vim_var_list(VV_ERRORS, list_alloc());
325+
set_vim_var_dict(VV_EVENT, dict_alloc_lock(VAR_FIXED));
324326

325327
set_vim_var_nr(VV_FALSE, VVAL_FALSE);
326328
set_vim_var_nr(VV_TRUE, VVAL_TRUE);
@@ -6632,6 +6634,16 @@ get_vim_var_list(int idx)
66326634
return vimvars[idx].vv_list;
66336635
}
66346636

6637+
/*
6638+
* Get Dict v: variable value. Caller must take care of reference count when
6639+
* needed.
6640+
*/
6641+
dict_T *
6642+
get_vim_var_dict(int idx)
6643+
{
6644+
return vimvars[idx].vv_dict;
6645+
}
6646+
66356647
/*
66366648
* Set v:char to character "c".
66376649
*/
@@ -6706,25 +6718,13 @@ set_vim_var_list(int idx, list_T *val)
67066718
void
67076719
set_vim_var_dict(int idx, dict_T *val)
67086720
{
6709-
int todo;
6710-
hashitem_T *hi;
6711-
67126721
clear_tv(&vimvars[idx].vv_di.di_tv);
67136722
vimvars[idx].vv_type = VAR_DICT;
67146723
vimvars[idx].vv_dict = val;
67156724
if (val != NULL)
67166725
{
67176726
++val->dv_refcount;
6718-
6719-
/* Set readonly */
6720-
todo = (int)val->dv_hashtab.ht_used;
6721-
for (hi = val->dv_hashtab.ht_array; todo > 0 ; ++hi)
6722-
{
6723-
if (HASHITEM_EMPTY(hi))
6724-
continue;
6725-
--todo;
6726-
HI2DI(hi)->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX;
6727-
}
6727+
dict_set_items_ro(val);
67286728
}
67296729
}
67306730

src/fileio.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6478,6 +6478,7 @@ buf_modname(
64786478
/*
64796479
* Like fgets(), but if the file line is too long, it is truncated and the
64806480
* rest of the line is thrown away. Returns TRUE for end-of-file.
6481+
* If the line is truncated then buf[size - 2] will not be NUL.
64816482
*/
64826483
int
64836484
vim_fgets(char_u *buf, int size, FILE *fp)
@@ -7856,6 +7857,7 @@ static struct event_name
78567857
{"WinEnter", EVENT_WINENTER},
78577858
{"WinLeave", EVENT_WINLEAVE},
78587859
{"VimResized", EVENT_VIMRESIZED},
7860+
{"TextYankPost", EVENT_TEXTYANKPOST},
78597861
{NULL, (event_T)0}
78607862
};
78617863

@@ -9399,6 +9401,15 @@ has_funcundefined(void)
93999401
return (first_autopat[(int)EVENT_FUNCUNDEFINED] != NULL);
94009402
}
94019403

9404+
/*
9405+
* Return TRUE when there is a TextYankPost autocommand defined.
9406+
*/
9407+
int
9408+
has_textyankpost(void)
9409+
{
9410+
return (first_autopat[(int)EVENT_TEXTYANKPOST] != NULL);
9411+
}
9412+
94029413
/*
94039414
* Execute autocommands for "event" and file name "fname".
94049415
* Return TRUE if some commands were executed.

src/ops.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,63 @@ shift_delete_registers()
16451645
y_regs[1].y_array = NULL; /* set register one to empty */
16461646
}
16471647

1648+
static void
1649+
yank_do_autocmd(oparg_T *oap, yankreg_T *reg)
1650+
{
1651+
static int recursive = FALSE;
1652+
dict_T *v_event;
1653+
list_T *list;
1654+
int n;
1655+
char_u buf[NUMBUFLEN + 2];
1656+
long reglen = 0;
1657+
1658+
if (recursive)
1659+
return;
1660+
1661+
v_event = get_vim_var_dict(VV_EVENT);
1662+
1663+
list = list_alloc();
1664+
for (n = 0; n < reg->y_size; n++)
1665+
list_append_string(list, reg->y_array[n], -1);
1666+
list->lv_lock = VAR_FIXED;
1667+
dict_add_list(v_event, "regcontents", list);
1668+
1669+
buf[0] = (char_u)oap->regname;
1670+
buf[1] = NUL;
1671+
dict_add_nr_str(v_event, "regname", 0, buf);
1672+
1673+
buf[0] = get_op_char(oap->op_type);
1674+
buf[1] = get_extra_op_char(oap->op_type);
1675+
buf[2] = NUL;
1676+
dict_add_nr_str(v_event, "operator", 0, buf);
1677+
1678+
buf[0] = NUL;
1679+
buf[1] = NUL;
1680+
switch (get_reg_type(oap->regname, &reglen))
1681+
{
1682+
case MLINE: buf[0] = 'V'; break;
1683+
case MCHAR: buf[0] = 'v'; break;
1684+
case MBLOCK:
1685+
vim_snprintf((char *)buf, sizeof(buf), "%c%ld", Ctrl_V,
1686+
reglen + 1);
1687+
break;
1688+
}
1689+
dict_add_nr_str(v_event, "regtype", 0, buf);
1690+
1691+
/* Lock the dictionary and its keys */
1692+
dict_set_items_ro(v_event);
1693+
1694+
recursive = TRUE;
1695+
textlock++;
1696+
apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, FALSE, curbuf);
1697+
textlock--;
1698+
recursive = FALSE;
1699+
1700+
/* Empty the dictionary, v:event is still valid */
1701+
dict_free_contents(v_event);
1702+
hash_init(&v_event->dv_hashtab);
1703+
}
1704+
16481705
/*
16491706
* Handle a delete operation.
16501707
*
@@ -1798,6 +1855,11 @@ op_delete(oparg_T *oap)
17981855
return FAIL;
17991856
}
18001857
}
1858+
1859+
#ifdef FEAT_AUTOCMD
1860+
if (did_yank && has_textyankpost())
1861+
yank_do_autocmd(oap, y_current);
1862+
#endif
18011863
}
18021864

18031865
/*
@@ -3270,6 +3332,11 @@ op_yank(oparg_T *oap, int deleting, int mess)
32703332
# endif
32713333
#endif
32723334

3335+
#ifdef FEAT_AUTOCMD
3336+
if (!deleting && has_textyankpost())
3337+
yank_do_autocmd(oap, y_current);
3338+
#endif
3339+
32733340
return OK;
32743341

32753342
fail: /* free the allocated lines */

src/proto/dict.pro

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
/* dict.c */
22
dict_T *dict_alloc(void);
3+
dict_T *dict_alloc_lock(int lock);
34
int rettv_dict_alloc(typval_T *rettv);
45
void rettv_dict_set(typval_T *rettv, dict_T *d);
6+
void dict_free_contents(dict_T *d);
57
void dict_unref(dict_T *d);
68
int dict_free_nonref(int copyID);
79
void dict_free_items(int copyID);
@@ -23,4 +25,5 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action);
2325
dictitem_T *dict_lookup(hashitem_T *hi);
2426
int dict_equal(dict_T *d1, dict_T *d2, int ic, int recursive);
2527
void dict_list(typval_T *argvars, typval_T *rettv, int what);
28+
void dict_set_items_ro(dict_T *di);
2629
/* vim: set ft=c : */

src/proto/eval.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ void set_vim_var_nr(int idx, varnumber_T val);
6464
varnumber_T get_vim_var_nr(int idx);
6565
char_u *get_vim_var_str(int idx);
6666
list_T *get_vim_var_list(int idx);
67+
dict_T * get_vim_var_dict(int idx);
6768
void set_vim_var_char(int c);
6869
void set_vcount(long count, long count1, int set_prevcount);
6970
void set_vim_var_string(int idx, char_u *val, int len);

src/proto/fileio.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ int has_textchangedI(void);
5151
int has_insertcharpre(void);
5252
int has_cmdundefined(void);
5353
int has_funcundefined(void);
54+
int has_textyankpost(void);
5455
void block_autocmds(void);
5556
void unblock_autocmds(void);
5657
int is_autocmd_blocked(void);

src/testdir/test_autocmd.vim

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,3 +1124,42 @@ func Test_Filter_noshelltemp()
11241124
let &shelltemp = shelltemp
11251125
bwipe!
11261126
endfunc
1127+
1128+
func Test_TextYankPost()
1129+
enew!
1130+
call setline(1, ['foo'])
1131+
1132+
let g:event = []
1133+
au TextYankPost * let g:event = copy(v:event)
1134+
1135+
call assert_equal({}, v:event)
1136+
call assert_fails('let v:event = {}', 'E46:')
1137+
call assert_fails('let v:event.mykey = 0', 'E742:')
1138+
1139+
norm "ayiw
1140+
call assert_equal(
1141+
\{'regcontents': ['foo'], 'regname': 'a', 'operator': 'y', 'regtype': 'v'},
1142+
\g:event)
1143+
norm y_
1144+
call assert_equal(
1145+
\{'regcontents': ['foo'], 'regname': '', 'operator': 'y', 'regtype': 'V'},
1146+
\g:event)
1147+
call feedkeys("\<C-V>y", 'x')
1148+
call assert_equal(
1149+
\{'regcontents': ['f'], 'regname': '', 'operator': 'y', 'regtype': "\x161"},
1150+
\g:event)
1151+
norm "xciwbar
1152+
call assert_equal(
1153+
\{'regcontents': ['foo'], 'regname': 'x', 'operator': 'c', 'regtype': 'v'},
1154+
\g:event)
1155+
norm "bdiw
1156+
call assert_equal(
1157+
\{'regcontents': ['bar'], 'regname': 'b', 'operator': 'd', 'regtype': 'v'},
1158+
\g:event)
1159+
1160+
call assert_equal({}, v:event)
1161+
1162+
au! TextYankPost
1163+
unlet g:event
1164+
bwipe!
1165+
endfunc

0 commit comments

Comments
 (0)