summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2017-12-16 18:27:02 +0100
committerBram Moolenaar <Bram@vim.org>2017-12-16 18:27:02 +0100
commit7e1652c63c96585b9e2235c195a3c322b1f11595 (patch)
treeed90a314ef58909b1c9dfbd45422f1a3557de278
parent6621605eb97cf5fbc481282fd4d349a76e168f16 (diff)
patch 8.0.1394: cannot intercept a yank commandv8.0.1394
Problem: Cannot intercept a yank command. Solution: Add the TextYankPost autocommand event. (Philippe Vaucher et al., closes #2333)
-rw-r--r--runtime/doc/autocmd.txt21
-rw-r--r--runtime/doc/eval.txt6
-rw-r--r--src/dict.c36
-rw-r--r--src/eval.c28
-rw-r--r--src/fileio.c11
-rw-r--r--src/ops.c67
-rw-r--r--src/proto/dict.pro3
-rw-r--r--src/proto/eval.pro1
-rw-r--r--src/proto/fileio.pro1
-rw-r--r--src/testdir/test_autocmd.vim39
-rw-r--r--src/version.c2
-rw-r--r--src/vim.h4
12 files changed, 201 insertions, 18 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index 06931dfd32..a47050e0d4 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -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
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 977ff718b2..09a66a6eed 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -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|.
diff --git a/src/dict.c b/src/dict.c
index c13e7a45f5..55069783f2 100644
--- a/src/dict.c
+++ b/src/dict.c
@@ -47,6 +47,16 @@ 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.
@@ -54,13 +64,12 @@ dict_alloc(void)
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) */
diff --git a/src/eval.c b/src/eval.c
index 1cced57ec0..85f607cb77 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -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);
@@ -6633,6 +6635,16 @@ get_vim_var_list(int idx)
}
/*
+ * 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".
*/
void
@@ -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);
}
}
diff --git a/src/fileio.c b/src/fileio.c
index fb49f28f81..ba9ec9ec0a 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -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}
};
@@ -9400,6 +9402,15 @@ has_funcundefined(void)
}
/*
+ * 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.
*/
diff --git a/src/ops.c b/src/ops.c
index 0209334ea4..1ecc67714d 100644
--- a/src/ops.c
+++ b/src/ops.c
@@ -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 */
diff --git a/src/proto/dict.pro b/src/proto/dict.pro
index 2a7626338e..9db43b944b 100644
--- a/src/proto/dict.pro
+++ b/src/proto/dict.pro
@@ -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 : */
diff --git a/src/proto/eval.pro b/src/proto/eval.pro
index 34e87a19e8..e29f3f0993 100644
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -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);
diff --git a/src/proto/fileio.pro b/src/proto/fileio.pro
index 30582d4e14..757963113f 100644
--- a/src/proto/fileio.pro
+++ b/src/proto/fileio.pro
@@ -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);
diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim
index cadc013fb9..bf106f33c7 100644
--- a/src/testdir/test_autocmd.vim
+++ b/src/testdir/test_autocmd.vim
@@ -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
diff --git a/src/version.c b/src/version.c
index 4b18f63ad2..57c286dc1c 100644
--- a/src/version.c
+++ b/src/version.c
@@ -772,6 +772,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1394,
+/**/
1393,
/**/
1392,
diff --git a/src/vim.h b/src/vim.h
index b43c210a68..4765b783b5 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -1339,6 +1339,7 @@ enum auto_event
EVENT_TEXTCHANGEDI, /* text was modified in Insert mode*/
EVENT_CMDUNDEFINED, /* command undefined */
EVENT_OPTIONSET, /* option was set */
+ EVENT_TEXTYANKPOST, /* after some text was yanked */
NUM_EVENTS /* MUST be the last one */
};
@@ -1988,7 +1989,8 @@ typedef int sock_T;
#define VV_TERMU7RESP 83
#define VV_TERMSTYLERESP 84
#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 */
#define VVAL_FALSE 0L