TextYankPost: add information to v:event and update tests

This commit is contained in:
Björn Linse
2016-02-29 15:28:10 +01:00
parent 7ab9ff88e6
commit 2359f6f144
7 changed files with 313 additions and 87 deletions

View File

@@ -308,8 +308,7 @@ Name triggered by ~
|InsertCharPre| when a character was typed in Insert mode, before |InsertCharPre| when a character was typed in Insert mode, before
inserting it inserting it
|TextDeletePost| when some text is deleted (dw, dd, D) |TextYankPost| when some text is yanked or deleted
|TextYankPost| when some text is yanked (yw, yd, Y)
|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
@@ -725,24 +724,18 @@ InsertCharPre When a character is typed in Insert mode,
It is not allowed to change the text |textlock|. It is not allowed to change the text |textlock|.
The event is not triggered when 'paste' is The event is not triggered when 'paste' is
set. 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*
TextYankPost Just after a |yank| (|y|) command is issued. TextYankPost Just after a |yank| or |deleting| command, but not
Not issued if the black hole register if the black hole register |quote_| is used nor
(|quote_|) is used. The affected text can be for |setreg()|. Pattern must be * because its
accessed by several ways, through @" meaning may change in the future.
(|quotequote| or |v:register|: > Sets these |v:event| keys:
:echo @" operator
:echo eval('@' . v:register) regcontents
< regname
regtype
Recursion is ignored.
It is not allowed to change the text |textlock|.
*InsertEnter* *InsertEnter*
InsertEnter Just before starting Insert mode. Also for InsertEnter Just before starting Insert mode. Also for
Replace mode and Virtual Replace mode. The Replace mode and Virtual Replace mode. The

View File

@@ -1389,7 +1389,19 @@ v:errors Errors found by assert functions, such as |assert_true()|.
*v:event* *event-variable* *v:event* *event-variable*
v:event Dictionary of event data for the current |autocommand|. The v:event Dictionary of event data for the current |autocommand|. The
available keys differ per event type and are specified at 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* *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

@@ -83,8 +83,7 @@ return {
'TermResponse', -- after setting "v:termresponse" 'TermResponse', -- after setting "v:termresponse"
'TextChanged', -- text was modified 'TextChanged', -- text was modified
'TextChangedI', -- text was modified in Insert mode 'TextChangedI', -- text was modified in Insert mode
'TextDeletePost', -- after a delete command was done (dd, dw, D) 'TextYankPost', -- after a yank or delete was done (y, d, c)
'TextYankPost', -- after a yank command was done (yy, yw, Y)
'User', -- user defined autocommand 'User', -- user defined autocommand
'VimEnter', -- after starting Vim 'VimEnter', -- after starting Vim
'VimLeave', -- before exiting Vim 'VimLeave', -- before exiting Vim

View File

@@ -1410,8 +1410,9 @@ int op_delete(oparg_T *oap)
op_yank_reg(oap, false, reg, false); op_yank_reg(oap, false, reg, false);
} }
if(oap->regname == 0) { if (oap->regname == 0) {
set_clipboard(0, reg); 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); msgmore(curbuf->b_ml.ml_line_count - old_lcount);
textlock++;
apply_autocmds(EVENT_TEXTDELETEPOST, NULL, NULL, false, curbuf);
textlock--;
setmarks: setmarks:
if (oap->motion_type == MBLOCK) { if (oap->motion_type == MBLOCK) {
curbuf->b_op_end.lnum = oap->end.lnum; 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); yankreg_T *reg = get_yank_register(oap->regname, YREG_YANK);
op_yank_reg(oap, message, reg, is_append_register(oap->regname)); op_yank_reg(oap, message, reg, is_append_register(oap->regname));
set_clipboard(oap->regname, reg); set_clipboard(oap->regname, reg);
yank_do_autocmd(oap, reg);
if (message) {
textlock++;
apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, false, curbuf);
textlock--;
}
return true; return true;
} }
@@ -2536,6 +2528,74 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, long y_idx)
*pnew = NUL; *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. * Put contents of register "regname" into the text.

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)