mirror of
https://github.com/vim/vim.git
synced 2026-05-28 00:21:37 +02:00
patch 9.2.0470: No way to hook into put commands
Problem: No way to hook into put commands
(yochem)
Solution: Introduce TextPutPre and TextPutPost autocommands
(Foxe Chen).
fixes: #18701
closes: #20144
Signed-off-by: Foxe Chen <chen.foxe@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
committed by
Christian Brabandt
parent
e3d9929109
commit
e0781bd5bf
@@ -195,6 +195,8 @@ static keyvalue_T event_tab[NUM_EVENTS] = {
|
||||
KEYVALUE_ENTRY(-EVENT_TEXTCHANGEDI, "TextChangedI"),
|
||||
KEYVALUE_ENTRY(-EVENT_TEXTCHANGEDP, "TextChangedP"),
|
||||
KEYVALUE_ENTRY(-EVENT_TEXTCHANGEDT, "TextChangedT"),
|
||||
KEYVALUE_ENTRY(-EVENT_TEXTPUTPOST, "TextPutPost"),
|
||||
KEYVALUE_ENTRY(-EVENT_TEXTPUTPRE, "TextPutPre"),
|
||||
KEYVALUE_ENTRY(-EVENT_TEXTYANKPOST, "TextYankPost"),
|
||||
KEYVALUE_ENTRY(EVENT_USER, "User"),
|
||||
KEYVALUE_ENTRY(EVENT_VIMENTER, "VimEnter"),
|
||||
@@ -2023,6 +2025,25 @@ has_cmdundefined(void)
|
||||
}
|
||||
|
||||
#if defined(FEAT_EVAL)
|
||||
|
||||
/*
|
||||
* Return TRUE when there is a TextPutPost autocommand defined.
|
||||
*/
|
||||
int
|
||||
has_textputpost(void)
|
||||
{
|
||||
return (first_autopat[(int)EVENT_TEXTPUTPOST] != NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return TRUE when there is a TextPutPre autocommand defined.
|
||||
*/
|
||||
int
|
||||
has_textputpre(void)
|
||||
{
|
||||
return (first_autopat[(int)EVENT_TEXTPUTPRE] != NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return TRUE when there is a TextYankPost autocommand defined.
|
||||
*/
|
||||
|
||||
@@ -714,6 +714,11 @@ stuffRedoReadbuff(char_u *s)
|
||||
void
|
||||
stuffReadbuffLen(char_u *s, long len)
|
||||
{
|
||||
#ifdef FEAT_EVAL
|
||||
if (add_last_insert == 1) // Only add if this is the first call, for
|
||||
// recursive calls, ignore.
|
||||
ga_concat_len(&last_insert_ga, s, (size_t)len);
|
||||
#endif
|
||||
add_buff(&readbuf1, s, len);
|
||||
}
|
||||
|
||||
|
||||
@@ -2165,3 +2165,11 @@ EXTERN bool inside_redraw_on_start_cb INIT(= false);
|
||||
|
||||
// If greater than zero, then silence the W23/W24 warning.
|
||||
EXTERN int silence_w23_w24_msg INIT( = 0);
|
||||
|
||||
#ifdef FEAT_EVAL
|
||||
// Used by TextPutPost/TextPutPre autocommands for the '.' register. If
|
||||
// "add_last_insert" is == 1, then "stuff_inserted" will add the last inserted
|
||||
// text to "last_insert_ga".
|
||||
EXTERN garray_T last_insert_ga INIT5(0, 0, 1, 64, NULL);
|
||||
EXTERN int add_last_insert INIT(= 0);
|
||||
#endif
|
||||
|
||||
@@ -29,6 +29,8 @@ int has_textchangedP(void);
|
||||
int has_insertcharpre(void);
|
||||
int has_keyinputpre(void);
|
||||
int has_cmdundefined(void);
|
||||
int has_textputpost(void);
|
||||
int has_textputpre(void);
|
||||
int has_textyankpost(void);
|
||||
int has_completechanged(void);
|
||||
int has_modechanged(void);
|
||||
|
||||
+154
-24
@@ -1081,6 +1081,36 @@ shift_delete_registers(void)
|
||||
}
|
||||
|
||||
#if defined(FEAT_EVAL)
|
||||
static void
|
||||
add_regtype_to_dict(int regname, dict_T *dict, char_u *buf, int bufsize)
|
||||
{
|
||||
size_t buflen;
|
||||
long reglen;
|
||||
// Register type
|
||||
switch (get_reg_type(regname, ®len))
|
||||
{
|
||||
case MLINE:
|
||||
buf[0] = 'V';
|
||||
buf[1] = NUL;
|
||||
buflen = 1;
|
||||
break;
|
||||
case MCHAR:
|
||||
buf[0] = 'v';
|
||||
buf[1] = NUL;
|
||||
buflen = 1;
|
||||
break;
|
||||
case MBLOCK:
|
||||
buflen = vim_snprintf_safelen((char *)buf, bufsize,
|
||||
"%c%ld", Ctrl_V, reglen + 1);
|
||||
break;
|
||||
default:
|
||||
buf[0] = NUL;
|
||||
buflen = 0;
|
||||
break;
|
||||
}
|
||||
(void)dict_add_string_len(dict, "regtype", buf, (int)buflen);
|
||||
}
|
||||
|
||||
void
|
||||
yank_do_autocmd(oparg_T *oap, yankreg_T *reg)
|
||||
{
|
||||
@@ -1090,7 +1120,6 @@ yank_do_autocmd(oparg_T *oap, yankreg_T *reg)
|
||||
int n;
|
||||
char_u buf[NUMBUFLEN + 2];
|
||||
size_t buflen;
|
||||
long reglen = 0;
|
||||
save_v_event_T save_v_event;
|
||||
|
||||
if (recursive)
|
||||
@@ -1124,29 +1153,7 @@ yank_do_autocmd(oparg_T *oap, yankreg_T *reg)
|
||||
buflen = (buf[0] == NUL) ? 0 : (buf[1] == NUL) ? 1 : 2;
|
||||
(void)dict_add_string_len(v_event, "operator", buf, (int)buflen);
|
||||
|
||||
// register type
|
||||
switch (get_reg_type(oap->regname, ®len))
|
||||
{
|
||||
case MLINE:
|
||||
buf[0] = 'V';
|
||||
buf[1] = NUL;
|
||||
buflen = 1;
|
||||
break;
|
||||
case MCHAR:
|
||||
buf[0] = 'v';
|
||||
buf[1] = NUL;
|
||||
buflen = 1;
|
||||
break;
|
||||
case MBLOCK:
|
||||
buflen = vim_snprintf_safelen((char *)buf, sizeof(buf),
|
||||
"%c%ld", Ctrl_V, reglen + 1);
|
||||
break;
|
||||
default:
|
||||
buf[0] = NUL;
|
||||
buflen = 0;
|
||||
break;
|
||||
}
|
||||
(void)dict_add_string_len(v_event, "regtype", buf, (int)buflen);
|
||||
add_regtype_to_dict(oap->regname, v_event, buf, sizeof(buf));
|
||||
|
||||
// selection type - visual or not
|
||||
(void)dict_add_bool(v_event, "visual", oap->is_VIsual);
|
||||
@@ -1163,6 +1170,85 @@ yank_do_autocmd(oparg_T *oap, yankreg_T *reg)
|
||||
// Empty the dictionary, v:event is still valid
|
||||
restore_v_event(v_event, &save_v_event);
|
||||
}
|
||||
|
||||
static void
|
||||
put_do_autocmd(
|
||||
int regname,
|
||||
yankreg_T *reg, // May be NULL, if special register
|
||||
string_T *insert, // Not NULL if special register, except '.'
|
||||
bool post, // If Post or Pre
|
||||
int dir) // BACKWARD for 'P', FORWARD for 'p'
|
||||
{
|
||||
static bool recursive = false;
|
||||
dict_T *v_event;
|
||||
list_T *list;
|
||||
int n;
|
||||
char_u buf[NUMBUFLEN + 2];
|
||||
size_t buflen;
|
||||
save_v_event_T save_v_event;
|
||||
|
||||
if (recursive || regname == '_')
|
||||
return;
|
||||
|
||||
v_event = get_v_event(&save_v_event);
|
||||
|
||||
list = list_alloc();
|
||||
if (list == NULL)
|
||||
return;
|
||||
|
||||
if (regname == '.')
|
||||
{
|
||||
if (last_insert_ga.ga_data != NULL)
|
||||
// Get the last inserted text to place in "regcontents"
|
||||
list_append_string(list, last_insert_ga.ga_data,
|
||||
(int)last_insert_ga.ga_len);
|
||||
}
|
||||
else if (insert != NULL)
|
||||
{
|
||||
list_append_string(list, insert->string, (int)insert->length);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(reg != NULL);
|
||||
for (n = 0; n < reg->y_size; n++)
|
||||
list_append_string(list, reg->y_array[n].string,
|
||||
(int)reg->y_array[n].length);
|
||||
}
|
||||
|
||||
list->lv_lock = VAR_FIXED;
|
||||
(void)dict_add_list(v_event, "regcontents", list);
|
||||
|
||||
// register name or empty string for unnamed operation
|
||||
buf[0] = (char_u)regname;
|
||||
buf[1] = NUL;
|
||||
buflen = (buf[0] == NUL) ? 0 : 1;
|
||||
(void)dict_add_string_len(v_event, "regname", buf, (int)buflen);
|
||||
|
||||
// kind of operation (P, p)
|
||||
buf[0] = dir == BACKWARD ? 'P' : 'p';
|
||||
buf[1] = NUL;
|
||||
buflen = 1;
|
||||
(void)dict_add_string_len(v_event, "operator", buf, (int)buflen);
|
||||
|
||||
add_regtype_to_dict(regname, v_event, buf, sizeof(buf));
|
||||
|
||||
(void)dict_add_bool(v_event, "visual", VIsual_active);
|
||||
|
||||
// Lock the dictionary and its keys
|
||||
dict_set_items_ro(v_event);
|
||||
|
||||
recursive = true;
|
||||
textlock++;
|
||||
if (post)
|
||||
apply_autocmds(EVENT_TEXTPUTPOST, NULL, NULL, FALSE, curbuf);
|
||||
else
|
||||
apply_autocmds(EVENT_TEXTPUTPRE, NULL, NULL, FALSE, curbuf);
|
||||
textlock--;
|
||||
recursive = false;
|
||||
|
||||
// Empty the dictionary, v:event is still valid
|
||||
restore_v_event(v_event, &save_v_event);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -1648,8 +1734,28 @@ do_put(
|
||||
{
|
||||
if (VIsual_active)
|
||||
stuffcharReadbuff(VIsual_mode);
|
||||
|
||||
#ifdef FEAT_EVAL
|
||||
if (has_textputpre() || has_textputpost())
|
||||
add_last_insert++;
|
||||
#endif
|
||||
|
||||
(void)stuff_inserted((dir == FORWARD ? (count == -1 ? 'o' : 'a') :
|
||||
(count == -1 ? 'O' : 'i')), count, FALSE);
|
||||
|
||||
#ifdef FEAT_EVAL
|
||||
// Since the text is not inserted into the buffer immediately, just call
|
||||
// TextPutPost after TextPutPre.
|
||||
if (has_textputpre())
|
||||
put_do_autocmd('.', NULL, NULL, false, dir);
|
||||
|
||||
if (has_textputpost())
|
||||
put_do_autocmd('.', NULL, NULL, true, dir);
|
||||
|
||||
if (--add_last_insert == 0)
|
||||
ga_clear(&last_insert_ga);
|
||||
#endif
|
||||
|
||||
// Putting the text is done later, so can't really move the cursor to
|
||||
// the next character. Use "l" to simulate it.
|
||||
if ((flags & PUT_CURSEND) && gchar_cursor() != NUL)
|
||||
@@ -1733,9 +1839,22 @@ do_put(
|
||||
y_size = 1; // use fake one-line yank register
|
||||
y_array = &insert_string;
|
||||
}
|
||||
#ifdef FEAT_EVAL
|
||||
if (has_textputpre())
|
||||
put_do_autocmd(regname, NULL, &insert_string, false, dir);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef FEAT_EVAL
|
||||
if (has_textputpre())
|
||||
{
|
||||
// Make sure to call this before we set the variables, as setreg()
|
||||
// may be called and invalidate them.
|
||||
get_yank_register(regname, FALSE);
|
||||
put_do_autocmd(regname, y_current, NULL, false, dir);
|
||||
}
|
||||
#endif
|
||||
get_yank_register(regname, FALSE);
|
||||
|
||||
y_type = y_current->y_type;
|
||||
@@ -2401,6 +2520,17 @@ end:
|
||||
curbuf->b_op_start = orig_start;
|
||||
curbuf->b_op_end = orig_end;
|
||||
}
|
||||
|
||||
#ifdef FEAT_EVAL
|
||||
if (has_textputpost())
|
||||
{
|
||||
if (insert_string.string == NULL)
|
||||
put_do_autocmd(regname, y_current, NULL, true, dir);
|
||||
else
|
||||
put_do_autocmd(regname, NULL, &insert_string, true, dir);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (allocated)
|
||||
vim_free(insert_string.string);
|
||||
if (regname == '=')
|
||||
|
||||
@@ -5967,4 +5967,129 @@ func Test_autocmd_add_secure()
|
||||
call assert_fails('sandbox call autocmd_delete([{"event": "BufRead"}])', 'E48:')
|
||||
endfunc
|
||||
|
||||
func Test_TextPutX()
|
||||
enew!
|
||||
|
||||
let g:pre_event = []
|
||||
let g:post_event = []
|
||||
au TextPutPre * let g:pre_event = copy(v:event)
|
||||
au TextPutPost * let g:post_event = copy(v:event)
|
||||
|
||||
call setreg('a', ['foo'], 'v')
|
||||
norm "ap
|
||||
call assert_equal(
|
||||
\ #{regcontents: ['foo'], regname: 'a', operator: 'p',
|
||||
\ visual: v:false, regtype: 'v'},
|
||||
\ g:pre_event)
|
||||
call assert_equal(g:pre_event, g:post_event)
|
||||
|
||||
call setreg('', ['hello'], 'V')
|
||||
norm P
|
||||
call assert_equal(
|
||||
\ #{regcontents: ['hello'], regname: '', operator: 'P',
|
||||
\ visual: v:false, regtype: 'V'},
|
||||
\ g:pre_event)
|
||||
call assert_equal(g:pre_event, g:post_event)
|
||||
|
||||
call setreg('', ['maybe', 'true'], 'V')
|
||||
norm Vp
|
||||
call assert_equal(
|
||||
\ #{regcontents: ['maybe', 'true'], regname: '', operator: 'P',
|
||||
\ regtype: 'V', visual: v:true},
|
||||
\ g:pre_event)
|
||||
call assert_equal(g:pre_event, g:post_event)
|
||||
call assert_equal({}, v:event)
|
||||
|
||||
call feedkeys("iinserted text\<CR>below\<Esc>", 'x')
|
||||
norm ".p
|
||||
call assert_equal(
|
||||
\ #{regcontents: ["inserted text\nbelow"], regname: '.',
|
||||
\ operator: 'p', regtype: 'v', visual: v:false},
|
||||
\ g:pre_event)
|
||||
call assert_equal(g:pre_event, g:post_event)
|
||||
|
||||
call feedkeys("\"=201\<CR>p", 'x')
|
||||
call assert_equal(
|
||||
\ #{regcontents: ["201"], regname: '=',
|
||||
\ operator: 'p', regtype: 'v', visual: v:false},
|
||||
\ g:pre_event)
|
||||
call assert_equal(g:pre_event, g:post_event)
|
||||
|
||||
vsplit some.txt
|
||||
wincmd l
|
||||
norm "#p
|
||||
call assert_equal(
|
||||
\ #{regcontents: ["some.txt"], regname: '#',
|
||||
\ operator: 'p', regtype: 'v', visual: v:false},
|
||||
\ g:pre_event)
|
||||
call assert_equal(g:pre_event, g:post_event)
|
||||
wincmd h
|
||||
bw!
|
||||
|
||||
if has('clipboard_working')
|
||||
let @+ = 'clipboard'
|
||||
|
||||
norm "+p
|
||||
call assert_equal(
|
||||
\ #{regcontents: ["clipboard"], regname: '+',
|
||||
\ operator: 'p', regtype: 'v', visual: v:false},
|
||||
\ g:pre_event)
|
||||
call assert_equal(g:pre_event, g:post_event)
|
||||
endif
|
||||
|
||||
%delete " Clear buffer
|
||||
|
||||
au! TextPutPre
|
||||
au! TextPutPost
|
||||
let g:pre_event = []
|
||||
let g:post_event = []
|
||||
|
||||
au TextPutPre * call setreg(v:event['regname'],
|
||||
\ getreg('', 0, v:true) + ['!']) " Unnamed register should be same as regname
|
||||
|
||||
call setreg('', ['hello', 'world'])
|
||||
norm p
|
||||
call assert_equal(['', 'hello', 'world', '!'], getline(1, '$'))
|
||||
|
||||
au! TextPutPre
|
||||
|
||||
" Test that special registers cannot be modified
|
||||
%delete
|
||||
au TextPutPre * call setreg('=', '"modified"') | let g:pre_event = copy(v:event)
|
||||
|
||||
" Set up the expression register to evaluate to a known value.
|
||||
call feedkeys("\"=\"original\"\<CR>p", 'x')
|
||||
|
||||
" The original value is what got put, not the modified one.
|
||||
call assert_equal(['original'], getline(1, '$'))
|
||||
|
||||
" v:event still reports the original value.
|
||||
call assert_equal(['original'], g:pre_event['regcontents'])
|
||||
|
||||
au! TextPutPre
|
||||
let g:pre_event = []
|
||||
|
||||
" Test that recursive ". register calls have the same contents for post and
|
||||
" pre
|
||||
au TextPutPre * put . | let g:pre_event = copy(v:event)
|
||||
au TextPutPost * let g:post_event = copy(v:event)
|
||||
|
||||
call feedkeys("iinserted\<Esc>", 'x')
|
||||
norm! ".p
|
||||
|
||||
call assert_equal(
|
||||
\ #{regcontents: ["inserted"], regname: '.',
|
||||
\ operator: 'p', regtype: 'v', visual: v:false},
|
||||
\ g:pre_event)
|
||||
call assert_equal(g:pre_event, g:post_event)
|
||||
|
||||
au! TextPutPre
|
||||
au! TextPutPost
|
||||
|
||||
unlet g:post_event
|
||||
unlet g:pre_event
|
||||
bwipe!
|
||||
|
||||
endfunc
|
||||
|
||||
" vim: shiftwidth=2 sts=2 expandtab
|
||||
|
||||
@@ -729,6 +729,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
470,
|
||||
/**/
|
||||
469,
|
||||
/**/
|
||||
|
||||
@@ -1484,6 +1484,8 @@ enum auto_event
|
||||
EVENT_TEXTCHANGEDI, // text was modified in Insert mode
|
||||
EVENT_TEXTCHANGEDP, // TextChangedI with popup menu visible
|
||||
EVENT_TEXTCHANGEDT, // text was modified in Terminal mode
|
||||
EVENT_TEXTPUTPOST, // after some text was put
|
||||
EVENT_TEXTPUTPRE, // before some text was put
|
||||
EVENT_TEXTYANKPOST, // after some text was yanked
|
||||
EVENT_USER, // user defined autocommand
|
||||
EVENT_VIMENTER, // after starting Vim
|
||||
|
||||
Reference in New Issue
Block a user