diff options
author | Björn Linse <bjorn.linse@gmail.com> | 2016-02-29 15:28:10 +0100 |
---|---|---|
committer | Björn Linse <bjorn.linse@gmail.com> | 2016-02-29 16:07:50 +0100 |
commit | 2359f6f144206707e2db78f5c3cfd6644f9ffd03 (patch) | |
tree | f9fc24093054aa322ffbe726d8c33b3a2473799c | |
parent | 7ab9ff88e6c7d234c5e9189521da539d20b5bfa7 (diff) | |
download | rneovim-2359f6f144206707e2db78f5c3cfd6644f9ffd03.tar.gz rneovim-2359f6f144206707e2db78f5c3cfd6644f9ffd03.tar.bz2 rneovim-2359f6f144206707e2db78f5c3cfd6644f9ffd03.zip |
TextYankPost: add information to v:event and update tests
-rw-r--r-- | runtime/doc/autocmd.txt | 31 | ||||
-rw-r--r-- | runtime/doc/eval.txt | 14 | ||||
-rw-r--r-- | src/nvim/auevents.lua | 3 | ||||
-rw-r--r-- | src/nvim/ops.c | 82 | ||||
-rw-r--r-- | test/functional/autocmd/text_deletepost.lua | 27 | ||||
-rw-r--r-- | test/functional/autocmd/text_yankpost.lua | 27 | ||||
-rw-r--r-- | test/functional/autocmd/textyankpost_spec.lua | 216 |
7 files changed, 313 insertions, 87 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 5f36079a39..ec5818e16f 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -308,8 +308,7 @@ Name triggered by ~ |InsertCharPre| when a character was typed in Insert mode, before inserting it -|TextDeletePost| when some text is deleted (dw, dd, D) -|TextYankPost| when some text is yanked (yw, yd, Y) +|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 @@ -725,24 +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. - *TextDeletePost* -TextDeletePost Just after a delete (|d|) command is issued. - Not issued if the black hole register - (|quote_|) is used. The affected text can be - accessed by several ways, through @" - (|quotequote| or |v:register|: > - :echo @" - :echo eval('@' . v:register) -< *TextYankPost* -TextYankPost Just after a |yank| (|y|) command is issued. - Not issued if the black hole register - (|quote_|) is used. The affected text can be - accessed by several ways, through @" - (|quotequote| or |v:register|: > - :echo @" - :echo eval('@' . v:register) -< +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 bc3dda97a5..51e2b505f5 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1389,7 +1389,19 @@ v:errors Errors found by assert functions, such as |assert_true()|. *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|. + 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 a36d5232b5..8d891effae 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -83,8 +83,7 @@ return { 'TermResponse', -- after setting "v:termresponse" 'TextChanged', -- text was modified 'TextChangedI', -- text was modified in Insert mode - 'TextDeletePost', -- after a delete command was done (dd, dw, D) - 'TextYankPost', -- after a yank command was done (yy, yw, Y) + '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/ops.c b/src/nvim/ops.c index 42b72cc33b..f4ae8b6082 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1410,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); } } @@ -1586,10 +1587,6 @@ int op_delete(oparg_T *oap) msgmore(curbuf->b_ml.ml_line_count - old_lcount); - textlock++; - apply_autocmds(EVENT_TEXTDELETEPOST, NULL, NULL, false, curbuf); - textlock--; - setmarks: if (oap->motion_type == MBLOCK) { curbuf->b_op_end.lnum = oap->end.lnum; @@ -2314,12 +2311,7 @@ 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); - - if (message) { - textlock++; - apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, false, curbuf); - textlock--; - } + yank_do_autocmd(oap, reg); return true; } @@ -2536,6 +2528,74 @@ 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]; + buf[0] = NUL; + buf[1] = NUL; + switch (reg->y_type) { + case MLINE: + buf[0] = 'V'; + break; + case MCHAR: + buf[0] = 'v'; + break; + case MBLOCK: + buf[0] = Ctrl_V; + snprintf(buf + 1, ARRAY_SIZE(buf) - 1, "%" PRId64, + (int64_t)(reg->y_width + 1)); + break; + case MAUTO: + assert(false); + } + 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. diff --git a/test/functional/autocmd/text_deletepost.lua b/test/functional/autocmd/text_deletepost.lua deleted file mode 100644 index 15c4eafdd4..0000000000 --- a/test/functional/autocmd/text_deletepost.lua +++ /dev/null @@ -1,27 +0,0 @@ -local helpers = require('test.functional.helpers') -local clear, eval, eq = helpers.clear, helpers.eval, helpers.eq -local feed, execute = helpers.feed, helpers.execute - - -describe('TextDeletePost', function() - before_each(function() - clear() - end) - - describe('au TextDeletePost', function() - it('is executed after delete', function() - feed('ifoo<ESC>') - execute('let g:foo = 0') - execute('autocmd! TextDeletePost * let g:foo = 1') - feed('dd') - eq(1, eval('g:foo')) - end) - it('is not executed after yank', function() - feed('ifoo<ESC>') - execute('let g:foo = 0') - execute('autocmd! TextDeletePost * let g:foo = 1') - feed('yy') - eq(0, eval('g:foo')) - end) - end) -end) diff --git a/test/functional/autocmd/text_yankpost.lua b/test/functional/autocmd/text_yankpost.lua deleted file mode 100644 index 67f3735fa2..0000000000 --- a/test/functional/autocmd/text_yankpost.lua +++ /dev/null @@ -1,27 +0,0 @@ -local helpers = require('test.functional.helpers') -local clear, eval, eq = helpers.clear, helpers.eval, helpers.eq -local feed, execute = helpers.feed, helpers.execute - - -describe('TextYankPost', function() - before_each(function() - clear() - end) - - describe('autocmd TextYankPost', function() - it('is executed after yank', function() - feed('ifoo<ESC>') - execute('let g:foo = 0') - execute('autocmd! TextYankPost * let g:foo = 1') - feed('yy') - eq(1, eval('g:foo')) - end) - it('is not executed after delete', function() - feed('ifoo<ESC>') - execute('let g:foo = 0') - execute('autocmd! TextYankPost * let g:foo = 1') - feed('dd') - eq(0, eval('g:foo')) - end) - end) -end) 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) |