diff options
author | zeertzjq <zeertzjq@outlook.com> | 2024-09-22 06:02:48 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-22 06:02:48 +0800 |
commit | e697c1b43dfbeab132fee4149157f7abd08c51a0 (patch) | |
tree | 4fb363d573f9606f7314799e03c6ba9e0f5fd0e1 /src | |
parent | 1d815acd78e5b961302985b80d2b625947902386 (diff) | |
download | rneovim-e697c1b43dfbeab132fee4149157f7abd08c51a0.tar.gz rneovim-e697c1b43dfbeab132fee4149157f7abd08c51a0.tar.bz2 rneovim-e697c1b43dfbeab132fee4149157f7abd08c51a0.zip |
fix(paste): improve repeating of pasted text (#30438)
- Fixes 'autoindent' being applied during redo.
- Makes redoing a large paste significantly faster.
- Stores pasted text in the register being recorded.
Fix #28561
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/api/vim.c | 21 | ||||
-rw-r--r-- | src/nvim/edit.c | 4 | ||||
-rw-r--r-- | src/nvim/getchar.c | 156 | ||||
-rw-r--r-- | src/nvim/keycodes.h | 4 | ||||
-rw-r--r-- | src/nvim/normal.c | 7 | ||||
-rw-r--r-- | src/nvim/terminal.c | 4 |
6 files changed, 167 insertions, 29 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index d10ee91042..4b80369654 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1259,30 +1259,19 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Arena *arena, Error draining = true; goto theend; } - if (!(State & (MODE_CMDLINE | MODE_INSERT)) && (phase == -1 || phase == 1)) { - ResetRedobuff(); - AppendCharToRedobuff('a'); // Dot-repeat. + if (phase == -1 || phase == 1) { + paste_store(kFalse, NULL_STRING, crlf); } // vim.paste() decides if client should cancel. Errors do NOT cancel: we // want to drain remaining chunks (rather than divert them to main input). cancel = (rv.type == kObjectTypeBoolean && !rv.data.boolean); - if (!cancel && !(State & MODE_CMDLINE)) { // Dot-repeat. - for (size_t i = 0; i < lines.size; i++) { - String s = lines.items[i].data.string; - assert(s.size <= INT_MAX); - AppendToRedobuffLit(s.data, (int)s.size); - // readfile()-style: "\n" is indicated by presence of N+1 item. - if (i + 1 < lines.size) { - AppendCharToRedobuff(NL); - } - } - } - if (!(State & (MODE_CMDLINE | MODE_INSERT)) && (phase == -1 || phase == 3)) { - AppendCharToRedobuff(ESC); // Dot-repeat. + if (!cancel) { + paste_store(kNone, data, crlf); } theend: if (cancel || phase == -1 || phase == 3) { // End of paste-stream. draining = false; + paste_store(kTrue, NULL_STRING, crlf); } return !cancel; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 13623eaa91..f06dc124f0 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -907,6 +907,10 @@ static int insert_handle_key(InsertState *s) case K_IGNORE: // Something mapped to nothing break; + case K_PASTE_START: + paste_repeat(1); + goto check_pum; + case K_EVENT: // some event state_handle_k_event(); // If CTRL-G U was used apply it to the next typed key. diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index ba0b629896..31f31904e0 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -13,6 +13,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/vim.h" #include "nvim/ascii_defs.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" @@ -308,6 +309,24 @@ static void add_num_buff(buffheader_T *buf, int n) add_buff(buf, number, -1); } +/// Add byte or special key 'c' to buffer "buf". +/// Translates special keys, NUL and K_SPECIAL. +static void add_byte_buff(buffheader_T *buf, int c) +{ + char temp[4]; + if (IS_SPECIAL(c) || c == K_SPECIAL || c == NUL) { + // Translate special key code into three byte sequence. + temp[0] = (char)K_SPECIAL; + temp[1] = (char)K_SECOND(c); + temp[2] = (char)K_THIRD(c); + temp[3] = NUL; + } else { + temp[0] = (char)c; + temp[1] = NUL; + } + add_buff(buf, temp, -1); +} + /// Add character 'c' to buffer "buf". /// Translates special keys, NUL, K_SPECIAL and multibyte characters. static void add_char_buff(buffheader_T *buf, int c) @@ -325,19 +344,7 @@ static void add_char_buff(buffheader_T *buf, int c) if (!IS_SPECIAL(c)) { c = bytes[i]; } - - char temp[4]; - if (IS_SPECIAL(c) || c == K_SPECIAL || c == NUL) { - // Translate special key code into three byte sequence. - temp[0] = (char)K_SPECIAL; - temp[1] = (char)K_SECOND(c); - temp[2] = (char)K_THIRD(c); - temp[3] = NUL; - } else { - temp[0] = (char)c; - temp[1] = NUL; - } - add_buff(buf, temp, -1); + add_byte_buff(buf, c); } } @@ -3182,3 +3189,126 @@ bool map_execute_lua(bool may_repeat) ga_clear(&line_ga); 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 +/// appends to redo buffer and/or record buffer if needed. +/// Escapes all K_SPECIAL and NUL bytes in the content. +/// +/// @param state kFalse for the start of a paste +/// kTrue for the end of a paste +/// kNone for the content of a paste +/// @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) +{ + if (State & MODE_CMDLINE) { + return; + } + + const bool need_redo = !block_redo; + const bool need_record = reg_recording != 0 && !paste_repeat_active; + + if (!need_redo && !need_record) { + return; + } + + if (state != kNone) { + const int c = state == kFalse ? K_PASTE_START : K_PASTE_END; + if (need_redo) { + if (state == kFalse && !(State & MODE_INSERT)) { + ResetRedobuff(); + } + add_char_buff(&redobuff, c); + } + if (need_record) { + add_char_buff(&recordbuff, c); + } + return; + } + + const char *s = str.data; + const char *const str_end = str.data + str.size; + + while (s < str_end) { + const char *start = s; + while (s < str_end && (uint8_t)(*s) != K_SPECIAL && *s != NUL + && *s != NL && !(crlf && *s == CAR)) { + s++; + } + + if (s > start) { + if (need_redo) { + add_buff(&redobuff, start, s - start); + } + if (need_record) { + add_buff(&recordbuff, start, s - start); + } + } + + if (s < str_end) { + int c = (uint8_t)(*s++); + if (crlf && c == CAR) { + if (s < str_end && *s == NL) { + s++; + } + c = NL; + } + if (need_redo) { + add_byte_buff(&redobuff, c); + } + if (need_record) { + add_byte_buff(&recordbuff, c); + } + } + } +} + +/// Gets a paste stored by paste_store() from typeahead and repeats it. +void paste_repeat(int count) +{ + garray_T ga = GA_INIT(1, 32); + bool aborted = false; + + no_mapping++; + + got_int = false; + while (!aborted) { + ga_grow(&ga, 32); + uint8_t c1 = (uint8_t)vgetorpeek(true); + if (c1 == K_SPECIAL) { + c1 = (uint8_t)vgetorpeek(true); + uint8_t c2 = (uint8_t)vgetorpeek(true); + int c = TO_SPECIAL(c1, c2); + if (c == K_PASTE_END) { + break; + } else if (c == K_ZERO) { + ga_append(&ga, NUL); + } else if (c == K_SPECIAL) { + ga_append(&ga, K_SPECIAL); + } else { + ga_append(&ga, K_SPECIAL); + ga_append(&ga, c1); + ga_append(&ga, c2); + } + } else { + ga_append(&ga, c1); + } + aborted = got_int; + } + + no_mapping--; + + String str = cbuf_as_string(ga.ga_data, (size_t)ga.ga_len); + Arena arena = ARENA_EMPTY; + Error err = ERROR_INIT; + paste_repeat_active = true; + for (int i = 0; !aborted && i < count; i++) { + nvim_paste(str, false, -1, &arena, &err); + aborted = ERROR_SET(&err); + } + paste_repeat_active = false; + api_clear_error(&err); + arena_mem_free(arena_finish(&arena)); + ga_clear(&ga); +} diff --git a/src/nvim/keycodes.h b/src/nvim/keycodes.h index 18af3f87d6..5a7ddd4847 100644 --- a/src/nvim/keycodes.h +++ b/src/nvim/keycodes.h @@ -380,6 +380,10 @@ enum key_extra { #define K_KENTER TERMCAP2KEY('K', 'A') // keypad Enter #define K_KPOINT TERMCAP2KEY('K', 'B') // keypad . or , +// Delimits pasted text (to repeat nvim_paste). Internal-only, not sent by UIs. +#define K_PASTE_START TERMCAP2KEY('P', 'S') // paste start +#define K_PASTE_END TERMCAP2KEY('P', 'E') // paste end + #define K_K0 TERMCAP2KEY('K', 'C') // keypad 0 #define K_K1 TERMCAP2KEY('K', 'D') // keypad 1 #define K_K2 TERMCAP2KEY('K', 'E') // keypad 2 diff --git a/src/nvim/normal.c b/src/nvim/normal.c index b9ce891b49..be9987cc7f 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -351,6 +351,7 @@ static const struct nv_cmd { { K_F1, nv_help, NV_NCW, 0 }, { K_XF1, nv_help, NV_NCW, 0 }, { K_SELECT, nv_select, 0, 0 }, + { K_PASTE_START, nv_paste, NV_KEEPREG, 0 }, { K_EVENT, nv_event, NV_KEEPREG, 0 }, { K_COMMAND, nv_colon, 0, 0 }, { K_LUA, nv_colon, 0, 0 }, @@ -6593,6 +6594,12 @@ static void nv_open(cmdarg_T *cap) } } +/// Handles K_PASTE_START, repeats pasted text. +static void nv_paste(cmdarg_T *cap) +{ + paste_repeat(cap->count1); +} + /// Handle an arbitrary event in normal mode static void nv_event(cmdarg_T *cap) { diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index ea3617098b..b916660024 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -748,6 +748,10 @@ static int terminal_execute(VimState *state, int key) } break; + case K_PASTE_START: + paste_repeat(1); + break; + case K_EVENT: // We cannot let an event free the terminal yet. It is still needed. s->term->refcount++; |