aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjörn Linse <bjorn.linse@gmail.com>2016-02-29 16:09:28 +0100
committerBjörn Linse <bjorn.linse@gmail.com>2016-02-29 16:09:28 +0100
commit1dd986562f6c10d3d3479aebecf0a1dcb083b5e6 (patch)
tree82286d117a5666fbf7d2d51f94acaf2118e196a4
parent88da85a3cd88838a7779ad27c533e62f890e7b18 (diff)
parente17e5547d79c3a900e1c8267815fe5d6dee27ffc (diff)
downloadrneovim-1dd986562f6c10d3d3479aebecf0a1dcb083b5e6.tar.gz
rneovim-1dd986562f6c10d3d3479aebecf0a1dcb083b5e6.tar.bz2
rneovim-1dd986562f6c10d3d3479aebecf0a1dcb083b5e6.zip
Merge pull request #4304 from bfredl/yank
Add v:event variable and TextYankPost autocommand
-rwxr-xr-xclint.py2
-rw-r--r--runtime/doc/autocmd.txt14
-rw-r--r--runtime/doc/eval.txt16
-rw-r--r--src/nvim/auevents.lua1
-rw-r--r--src/nvim/eval.c72
-rw-r--r--src/nvim/eval.h3
-rw-r--r--src/nvim/ops.c96
-rw-r--r--src/nvim/pos.h6
-rw-r--r--test/functional/autocmd/textyankpost_spec.lua216
-rw-r--r--test/functional/eval/vvar_event_spec.lua15
10 files changed, 413 insertions, 28 deletions
diff --git a/clint.py b/clint.py
index 9f6a35b7f4..fc081dcd14 100755
--- a/clint.py
+++ b/clint.py
@@ -2552,7 +2552,7 @@ def CheckBraces(filename, clean_lines, linenum, error):
# If should always have a brace
for blockstart in ('if', 'while', 'for'):
- if Match(r'\s*{0}[^{{]*$'.format(blockstart), line):
+ if Match(r'\s*{0}(?!\w)[^{{]*$'.format(blockstart), line):
pos = line.find(blockstart)
pos = line.find('(', pos)
if pos > 0:
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index e17281821c..ec5818e16f 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -308,6 +308,8 @@ Name triggered by ~
|InsertCharPre| when a character was typed in Insert mode, before
inserting it
+|TextYankPost| when some text is yanked or deleted
+
|TextChanged| after a change was made to the text in Normal mode
|TextChangedI| after a change was made to the text in Insert mode
@@ -722,6 +724,18 @@ InsertCharPre When a character is typed in Insert mode,
It is not allowed to change the text |textlock|.
The event is not triggered when 'paste' is
set.
+ *TextYankPost*
+TextYankPost Just after a |yank| or |deleting| command, but not
+ if the black hole register |quote_| is used nor
+ for |setreg()|. Pattern must be * because its
+ meaning may change in the future.
+ Sets these |v:event| keys:
+ operator
+ regcontents
+ regname
+ regtype
+ Recursion is ignored.
+ It is not allowed to change the text |textlock|.
*InsertEnter*
InsertEnter Just before starting Insert mode. Also for
Replace mode and Virtual Replace mode. The
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index ad736e9c81..51e2b505f5 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1386,6 +1386,22 @@ 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 of event data for the current |autocommand|. The
+ available keys differ per event type and are specified at the
+ documentation for each |event|. The possible keys are:
+ operator The operation performed. Unlike
+ |v:operator|, it is set also for an Ex
+ mode command. For instance, |:yank| is
+ translated to "|y|".
+ regcontents Text stored in the register as a
+ |readfile()|-style list of lines.
+ regname Requested register (e.g "x" for "xyy)
+ or the empty string for an unnamed
+ operation.
+ regtype Type of register as returned by
+ |getregtype()|.
+
*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/nvim/auevents.lua b/src/nvim/auevents.lua
index aa4a8d8332..8d891effae 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -83,6 +83,7 @@ return {
'TermResponse', -- after setting "v:termresponse"
'TextChanged', -- text was modified
'TextChangedI', -- text was modified in Insert mode
+ 'TextYankPost', -- after a yank or delete was done (y, d, c)
'User', -- user defined autocommand
'VimEnter', -- after starting Vim
'VimLeave', -- before exiting Vim
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 76deedfad0..33b8415336 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -378,6 +378,7 @@ static struct vimvar {
{ VV_NAME("option_type", VAR_STRING), VV_RO },
{ VV_NAME("errors", VAR_LIST), 0 },
{ VV_NAME("msgpack_types", VAR_DICT), VV_RO },
+ { VV_NAME("event", VAR_DICT), VV_RO },
};
/* shorthand */
@@ -545,6 +546,10 @@ void eval_init(void)
set_vim_var_dict(VV_MSGPACK_TYPES, msgpack_types_dict);
set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc());
+
+ dict_T *v_event = dict_alloc();
+ v_event->dv_lock = VAR_FIXED;
+ set_vim_var_dict(VV_EVENT, v_event);
set_vim_var_list(VV_ERRORS, list_alloc());
set_vim_var_nr(VV_SEARCHFORWARD, 1L);
set_vim_var_nr(VV_HLSEARCH, 1L);
@@ -6017,6 +6022,27 @@ static void rettv_dict_alloc(typval_T *rettv)
++d->dv_refcount;
}
+/// Clear all the keys of a Dictionary. "d" remains a valid empty Dictionary.
+///
+/// @param d The Dictionary to clear
+void dict_clear(dict_T *d)
+ FUNC_ATTR_NONNULL_ALL
+{
+ hash_lock(&d->dv_hashtab);
+ assert(d->dv_hashtab.ht_locked > 0);
+
+ size_t todo = d->dv_hashtab.ht_used;
+ for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ dictitem_free(HI2DI(hi));
+ hash_remove(&d->dv_hashtab, hi);
+ todo--;
+ }
+ }
+
+ hash_unlock(&d->dv_hashtab);
+}
+
/*
* Unreference a Dictionary: decrement the reference count and free it when it
@@ -6258,6 +6284,24 @@ int dict_add_list(dict_T *d, char *key, list_T *list)
return OK;
}
+/// Set all existing keys in "dict" as read-only.
+///
+/// This does not protect against adding new keys to the Dictionary.
+///
+/// @param dict The dict whose keys should be frozen
+void dict_set_keys_readonly(dict_T *dict)
+ FUNC_ATTR_NONNULL_ALL
+{
+ size_t todo = dict->dv_hashtab.ht_used;
+ for (hashitem_T *hi = dict->dv_hashtab.ht_array; todo > 0 ; hi++) {
+ if (HASHITEM_EMPTY(hi)) {
+ continue;
+ }
+ todo--;
+ HI2DI(hi)->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX;
+ }
+}
+
/*
* Get the number of items in a Dictionary.
*/
@@ -10579,8 +10623,6 @@ static void f_getregtype(typval_T *argvars, typval_T *rettv)
{
char_u *strregname;
int regname;
- char_u buf[NUMBUFLEN + 2];
- long reglen = 0;
if (argvars[0].v_type != VAR_UNKNOWN) {
strregname = get_tv_string_chk(&argvars[0]);
@@ -10597,18 +10639,13 @@ static void f_getregtype(typval_T *argvars, typval_T *rettv)
if (regname == 0)
regname = '"';
- buf[0] = NUL;
- buf[1] = NUL;
- switch (get_reg_type(regname, &reglen)) {
- case MLINE: buf[0] = 'V'; break;
- case MCHAR: buf[0] = 'v'; break;
- case MBLOCK:
- buf[0] = Ctrl_V;
- sprintf((char *)buf + 1, "%" PRId64, (int64_t)(reglen + 1));
- break;
- }
+ colnr_T reglen = 0;
+ char buf[NUMBUFLEN + 2];
+ char_u reg_type = get_reg_type(regname, &reglen);
+ format_reg_type(reg_type, reglen, buf, ARRAY_SIZE(buf));
+
rettv->v_type = VAR_STRING;
- rettv->vval.v_string = vim_strsave(buf);
+ rettv->vval.v_string = (char_u *)xstrdup(buf);
}
/*
@@ -18162,14 +18199,7 @@ void set_vim_var_dict(int idx, dict_T *val)
if (val != NULL) {
++val->dv_refcount;
// Set readonly
- size_t todo = 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;
- }
+ dict_set_keys_readonly(val);
}
}
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 79a1341d98..f51b0f4921 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -113,7 +113,8 @@ enum {
VV_OPTION_TYPE,
VV_ERRORS,
VV_MSGPACK_TYPES,
- VV_LEN, /* number of v: vars */
+ VV_EVENT,
+ VV_LEN, // number of v: vars
};
/// Maximum number of function arguments
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index b1adc85e1d..ab6e0d2e7d 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -19,6 +19,7 @@
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_getln.h"
+#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/getchar.h"
#include "nvim/indent.h"
@@ -1409,8 +1410,9 @@ int op_delete(oparg_T *oap)
op_yank_reg(oap, false, reg, false);
}
- if(oap->regname == 0) {
+ if (oap->regname == 0) {
set_clipboard(0, reg);
+ yank_do_autocmd(oap, reg);
}
}
@@ -2309,6 +2311,8 @@ bool op_yank(oparg_T *oap, bool message)
yankreg_T *reg = get_yank_register(oap->regname, YREG_YANK);
op_yank_reg(oap, message, reg, is_append_register(oap->regname));
set_clipboard(oap->regname, reg);
+ yank_do_autocmd(oap, reg);
+
return true;
}
@@ -2524,6 +2528,58 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, long y_idx)
*pnew = NUL;
}
+/// Execute autocommands for TextYankPost.
+///
+/// @param oap Operator arguments.
+/// @param reg The yank register used.
+static void yank_do_autocmd(oparg_T *oap, yankreg_T *reg)
+ FUNC_ATTR_NONNULL_ALL
+{
+ static bool recursive = false;
+
+ if (recursive || !has_event(EVENT_TEXTYANKPOST)) {
+ // No autocommand was defined
+ // or we yanked from this autocommand.
+ return;
+ }
+
+ recursive = true;
+
+ // set v:event to a dictionary with information about the yank
+ dict_T *dict = get_vim_var_dict(VV_EVENT);
+
+ // the yanked text
+ list_T *list = list_alloc();
+ for (linenr_T i = 0; i < reg->y_size; i++) {
+ list_append_string(list, reg->y_array[i], -1);
+ }
+ list->lv_lock = VAR_FIXED;
+ dict_add_list(dict, "regcontents", list);
+
+ // the register type
+ char buf[NUMBUFLEN+2];
+ format_reg_type(reg->y_type, reg->y_width, buf, ARRAY_SIZE(buf));
+ dict_add_nr_str(dict, "regtype", 0, (char_u *)buf);
+
+ // name of requested register or the empty string for an unnamed operation.
+ buf[0] = (char)oap->regname;
+ buf[1] = NUL;
+ dict_add_nr_str(dict, "regname", 0, (char_u *)buf);
+
+ // kind of operation (yank/delete/change)
+ buf[0] = get_op_char(oap->op_type);
+ buf[1] = NUL;
+ dict_add_nr_str(dict, "operator", 0, (char_u *)buf);
+
+ dict_set_keys_readonly(dict);
+ textlock++;
+ apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, false, curbuf);
+ textlock--;
+ dict_clear(dict);
+
+ recursive = false;
+}
+
/*
* Put contents of register "regname" into the text.
@@ -4631,7 +4687,7 @@ theend:
* Used for getregtype()
* Returns MAUTO for error.
*/
-char_u get_reg_type(int regname, long *reglen)
+char_u get_reg_type(int regname, colnr_T *reg_width)
{
switch (regname) {
case '%': /* file name */
@@ -4654,13 +4710,45 @@ char_u get_reg_type(int regname, long *reglen)
yankreg_T *reg = get_yank_register(regname, YREG_PASTE);
if (reg->y_array != NULL) {
- if (reglen != NULL && reg->y_type == MBLOCK)
- *reglen = reg->y_width;
+ if (reg_width != NULL && reg->y_type == MBLOCK) {
+ *reg_width = reg->y_width;
+ }
return reg->y_type;
}
return MAUTO;
}
+/// Format the register type as a string.
+///
+/// @param reg_type The register type.
+/// @param reg_width The width, only used if "reg_type" is MBLOCK.
+/// @param[out] buf Buffer to store formatted string. The allocated size should
+/// be at least NUMBUFLEN+2 to always fit the value.
+/// @param buf_len The allocated size of the buffer.
+void format_reg_type(char_u reg_type, colnr_T reg_width,
+ char* buf, size_t buf_len)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(buf_len > 1);
+ switch (reg_type) {
+ case MLINE:
+ buf[0] = 'V';
+ buf[1] = NUL;
+ break;
+ case MCHAR:
+ buf[0] = 'v';
+ buf[1] = NUL;
+ break;
+ case MBLOCK:
+ snprintf(buf, buf_len, CTRL_V_STR "%" PRIdCOLNR, reg_width + 1);
+ break;
+ case MAUTO:
+ buf[0] = NUL;
+ break;
+ }
+}
+
+
/// When `flags` has `kGRegList` return a list with text `s`.
/// Otherwise just return `s`.
///
diff --git a/src/nvim/pos.h b/src/nvim/pos.h
index 7071df51e8..864f3fe866 100644
--- a/src/nvim/pos.h
+++ b/src/nvim/pos.h
@@ -2,7 +2,11 @@
#define NVIM_POS_H
typedef long linenr_T; // line number type
-typedef int colnr_T; // column number type
+
+/// Column number type
+typedef int colnr_T;
+/// Format used to print values which have colnr_T type
+#define PRIdCOLNR "d"
#define MAXLNUM 0x7fffffff // maximum (invalid) line number
#define MAXCOL 0x7fffffff // maximum column number, 31 bits
diff --git a/test/functional/autocmd/textyankpost_spec.lua b/test/functional/autocmd/textyankpost_spec.lua
new file mode 100644
index 0000000000..965b19581a
--- /dev/null
+++ b/test/functional/autocmd/textyankpost_spec.lua
@@ -0,0 +1,216 @@
+local helpers = require('test.functional.helpers')
+local clear, eval, eq, insert = helpers.clear, helpers.eval, helpers.eq, helpers.insert
+local feed, execute, expect, command = helpers.feed, helpers.execute, helpers.expect, helpers.command
+local curbufmeths, funcs, neq = helpers.curbufmeths, helpers.funcs, helpers.neq
+
+describe('TextYankPost', function()
+ before_each(function()
+ clear()
+
+ -- emulate the clipboard so system clipboard isn't affected
+ execute('let &rtp = "test/functional/fixtures,".&rtp')
+
+ execute('let g:count = 0')
+ execute('autocmd TextYankPost * let g:event = copy(v:event)')
+ execute('autocmd TextYankPost * let g:count += 1')
+
+ curbufmeths.set_line_slice(0, -1, true, true, {
+ 'foo\0bar',
+ 'baz text',
+ })
+ end)
+
+ it('is executed after yank and handles register types', function()
+ feed('yy')
+ eq({
+ operator = 'y',
+ regcontents = { 'foo\nbar' },
+ regname = '',
+ regtype = 'V'
+ }, eval('g:event'))
+ eq(1, eval('g:count'))
+
+ -- v:event is cleared after the autocommand is done
+ eq({}, eval('v:event'))
+
+ feed('+yw')
+ eq({
+ operator = 'y',
+ regcontents = { 'baz ' },
+ regname = '',
+ regtype = 'v'
+ }, eval('g:event'))
+ eq(2, eval('g:count'))
+
+ feed('<c-v>eky')
+ eq({
+ operator = 'y',
+ regcontents = { 'foo', 'baz' },
+ regname = '',
+ regtype = "\0223" -- ^V + block width
+ }, eval('g:event'))
+ eq(3, eval('g:count'))
+ end)
+
+ it('makes v:event immutable', function()
+ feed('yy')
+ eq({
+ operator = 'y',
+ regcontents = { 'foo\nbar' },
+ regname = '',
+ regtype = 'V'
+ }, eval('g:event'))
+
+ execute('set debug=msg')
+ -- the regcontents should not be changed without copy.
+ local status, err = pcall(command,'call extend(g:event.regcontents, ["more text"])')
+ eq(status,false)
+ neq(nil, string.find(err, ':E742:'))
+
+ -- can't mutate keys inside the autocommand
+ execute('autocmd! TextYankPost * let v:event.regcontents = 0')
+ status, err = pcall(command,'normal yy')
+ eq(status,false)
+ neq(nil, string.find(err, ':E46:'))
+
+ -- can't add keys inside the autocommand
+ execute('autocmd! TextYankPost * let v:event.mykey = 0')
+ status, err = pcall(command,'normal yy')
+ eq(status,false)
+ neq(nil, string.find(err, ':E742:'))
+ end)
+
+ it('is not invoked recursively', function()
+ execute('autocmd TextYankPost * normal "+yy')
+ feed('yy')
+ eq({
+ operator = 'y',
+ regcontents = { 'foo\nbar' },
+ regname = '',
+ regtype = 'V'
+ }, eval('g:event'))
+ eq(1, eval('g:count'))
+ eq({ 'foo\nbar' }, funcs.getreg('+',1,1))
+ end)
+
+ it('is executed after delete and change', function()
+ feed('dw')
+ eq({
+ operator = 'd',
+ regcontents = { 'foo' },
+ regname = '',
+ regtype = 'v'
+ }, eval('g:event'))
+ eq(1, eval('g:count'))
+
+ feed('dd')
+ eq({
+ operator = 'd',
+ regcontents = { '\nbar' },
+ regname = '',
+ regtype = 'V'
+ }, eval('g:event'))
+ eq(2, eval('g:count'))
+
+ feed('cwspam<esc>')
+ eq({
+ operator = 'c',
+ regcontents = { 'baz' },
+ regname = '',
+ regtype = 'v'
+ }, eval('g:event'))
+ eq(3, eval('g:count'))
+ end)
+
+ it('is not executed after black-hole operation', function()
+ feed('"_dd')
+ eq(0, eval('g:count'))
+
+ feed('"_cwgood<esc>')
+ eq(0, eval('g:count'))
+
+ expect([[
+ good text]])
+ feed('"_yy')
+ eq(0, eval('g:count'))
+
+ execute('delete _')
+ eq(0, eval('g:count'))
+ end)
+
+ it('gives the correct register name', function()
+ feed('$"byiw')
+ eq({
+ operator = 'y',
+ regcontents = { 'bar' },
+ regname = 'b',
+ regtype = 'v'
+ }, eval('g:event'))
+
+ feed('"*yy')
+ eq({
+ operator = 'y',
+ regcontents = { 'foo\nbar' },
+ regname = '*',
+ regtype = 'V'
+ }, eval('g:event'))
+
+ execute("set clipboard=unnamed")
+
+ -- regname still shows the name the user requested
+ feed('yy')
+ eq({
+ operator = 'y',
+ regcontents = { 'foo\nbar' },
+ regname = '',
+ regtype = 'V'
+ }, eval('g:event'))
+
+ feed('"*yy')
+ eq({
+ operator = 'y',
+ regcontents = { 'foo\nbar' },
+ regname = '*',
+ regtype = 'V'
+ }, eval('g:event'))
+ end)
+
+ it('works with Ex commands', function()
+ execute('1delete +')
+ eq({
+ operator = 'd',
+ regcontents = { 'foo\nbar' },
+ regname = '+',
+ regtype = 'V'
+ }, eval('g:event'))
+ eq(1, eval('g:count'))
+
+ execute('yank')
+ eq({
+ operator = 'y',
+ regcontents = { 'baz text' },
+ regname = '',
+ regtype = 'V'
+ }, eval('g:event'))
+ eq(2, eval('g:count'))
+
+ execute('normal yw')
+ eq({
+ operator = 'y',
+ regcontents = { 'baz ' },
+ regname = '',
+ regtype = 'v'
+ }, eval('g:event'))
+ eq(3, eval('g:count'))
+
+ execute('normal! dd')
+ eq({
+ operator = 'd',
+ regcontents = { 'baz text' },
+ regname = '',
+ regtype = 'V'
+ }, eval('g:event'))
+ eq(4, eval('g:count'))
+ end)
+
+end)
diff --git a/test/functional/eval/vvar_event_spec.lua b/test/functional/eval/vvar_event_spec.lua
new file mode 100644
index 0000000000..bbac86524f
--- /dev/null
+++ b/test/functional/eval/vvar_event_spec.lua
@@ -0,0 +1,15 @@
+local helpers = require('test.functional.helpers')
+local clear, eval, eq = helpers.clear, helpers.eval, helpers.eq
+local command = helpers.command
+describe('v:event', function()
+ before_each(clear)
+ it('is empty before any autocommand', function()
+ eq({}, eval('v:event'))
+ end)
+
+ it('is immutable', function()
+ eq(false, pcall(command, 'let v:event = {}'))
+ eq(false, pcall(command, 'let v:event.mykey = {}'))
+ end)
+end)
+