fix(paste): only record a paste when it's from RPC (#30491)

Problem:  When using nvim_paste in a mapping during a macro recording,
          both the mapping and the paste are recorded, causing the paste
          to be performed twice when replaying the macro.
Solution: Only record a paste when it is from RPC.

Unfortunately this means there is no way for a script to make a recorded
paste. A way to enable that can be discussed later if there is need.
This commit is contained in:
zeertzjq 2024-09-24 19:48:40 +08:00 committed by GitHub
parent 3f6bc34e66
commit 052875b9dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 15 additions and 11 deletions

View File

@ -1248,7 +1248,8 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
/// @return /// @return
/// - true: Client may continue pasting. /// - true: Client may continue pasting.
/// - false: Client should cancel the paste. /// - false: Client should cancel the paste.
Boolean nvim_paste(String data, Boolean crlf, Integer phase, Arena *arena, Error *err) Boolean nvim_paste(uint64_t channel_id, String data, Boolean crlf, Integer phase, Arena *arena,
Error *err)
FUNC_API_SINCE(6) FUNC_API_SINCE(6)
FUNC_API_TEXTLOCK_ALLOW_CMDWIN FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{ {
@ -1273,13 +1274,13 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Arena *arena, Error
cancelled = true; cancelled = true;
} }
if (!cancelled && (phase == -1 || phase == 1)) { if (!cancelled && (phase == -1 || phase == 1)) {
paste_store(kFalse, NULL_STRING, crlf); paste_store(channel_id, kFalse, NULL_STRING, crlf);
} }
if (!cancelled) { if (!cancelled) {
paste_store(kNone, data, crlf); paste_store(channel_id, kNone, data, crlf);
} }
if (phase == 3 || phase == (cancelled ? 2 : -1)) { if (phase == 3 || phase == (cancelled ? 2 : -1)) {
paste_store(kTrue, NULL_STRING, crlf); paste_store(channel_id, kTrue, NULL_STRING, crlf);
} }
theend: theend:
; ;

View File

@ -3190,8 +3190,6 @@ bool map_execute_lua(bool may_repeat)
return true; return true;
} }
static bool paste_repeat_active = false; ///< true when paste_repeat() is pasting
/// Wraps pasted text stream with K_PASTE_START and K_PASTE_END, and /// Wraps pasted text stream with K_PASTE_START and K_PASTE_END, and
/// appends to redo buffer and/or record buffer if needed. /// appends to redo buffer and/or record buffer if needed.
/// Escapes all K_SPECIAL and NUL bytes in the content. /// Escapes all K_SPECIAL and NUL bytes in the content.
@ -3200,14 +3198,14 @@ static bool paste_repeat_active = false; ///< true when paste_repeat() is pasti
/// kTrue for the end of a paste /// kTrue for the end of a paste
/// kNone for the content of a paste /// kNone for the content of a paste
/// @param str the content of the paste (only used when state is kNone) /// @param str the content of the paste (only used when state is kNone)
void paste_store(const TriState state, const String str, const bool crlf) void paste_store(const uint64_t channel_id, const TriState state, const String str, const bool crlf)
{ {
if (State & MODE_CMDLINE) { if (State & MODE_CMDLINE) {
return; return;
} }
const bool need_redo = !block_redo; const bool need_redo = !block_redo;
const bool need_record = reg_recording != 0 && !paste_repeat_active; const bool need_record = reg_recording != 0 && !is_internal_call(channel_id);
if (!need_redo && !need_record) { if (!need_redo && !need_record) {
return; return;
@ -3302,12 +3300,10 @@ void paste_repeat(int count)
String str = cbuf_as_string(ga.ga_data, (size_t)ga.ga_len); String str = cbuf_as_string(ga.ga_data, (size_t)ga.ga_len);
Arena arena = ARENA_EMPTY; Arena arena = ARENA_EMPTY;
Error err = ERROR_INIT; Error err = ERROR_INIT;
paste_repeat_active = true;
for (int i = 0; !aborted && i < count; i++) { for (int i = 0; !aborted && i < count; i++) {
nvim_paste(str, false, -1, &arena, &err); nvim_paste(LUA_INTERNAL_CALL, str, false, -1, &arena, &err);
aborted = ERROR_SET(&err); aborted = ERROR_SET(&err);
} }
paste_repeat_active = false;
api_clear_error(&err); api_clear_error(&err);
arena_mem_free(arena_finish(&arena)); arena_mem_free(arena_finish(&arena));
ga_clear(&ga); ga_clear(&ga);

View File

@ -1358,6 +1358,13 @@ describe('API', function()
test_paste_repeat_visual_select(true) test_paste_repeat_visual_select(true)
end) end)
end) end)
it('in a mapping recorded in a macro', function()
command([[nnoremap <F2> <Cmd>call nvim_paste('foo', v:false, -1)<CR>]])
feed('qr<F2>$q')
expect('foo')
feed('@r') -- repeating a macro containing the mapping should only paste once
expect('foofoo')
end)
local function test_paste_cancel_error(is_error) local function test_paste_cancel_error(is_error)
before_each(function() before_each(function()
exec_lua(([[ exec_lua(([[