From 0546068aaef2b1a40faa2945ef7eba249739f219 Mon Sep 17 00:00:00 2001 From: glepnir Date: Mon, 26 May 2025 18:23:27 +0200 Subject: [PATCH] patch 9.1.1408: not easily possible to complete from register content Problem: not easily possible to complete from register content Solution: add register-completion submode using i_CTRL-X_CTRL-R (glepnir) closes: #17354 Signed-off-by: glepnir Signed-off-by: Christian Brabandt --- runtime/doc/index.txt | 3 +- runtime/doc/insert.txt | 18 ++++- runtime/doc/options.txt | 5 +- runtime/doc/tags | 2 + runtime/doc/todo.txt | 3 +- runtime/doc/usr_24.txt | 1 + runtime/doc/version9.txt | 5 +- runtime/doc/vi_diff.txt | 1 + src/edit.c | 2 + src/insexpand.c | 107 ++++++++++++++++++++++++++++-- src/proto/insexpand.pro | 1 + src/testdir/test_ins_complete.vim | 81 ++++++++++++++++++++++ src/version.c | 2 + 13 files changed, 218 insertions(+), 13 deletions(-) diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index 00a09ae145..d03d81effa 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -1,4 +1,4 @@ -*index.txt* For Vim version 9.1. Last change: 2025 May 14 +*index.txt* For Vim version 9.1. Last change: 2025 May 26 VIM REFERENCE MANUAL by Bram Moolenaar @@ -163,6 +163,7 @@ commands in CTRL-X submode *i_CTRL-X_index* |i_CTRL-X_CTRL-N| CTRL-X CTRL-N next completion |i_CTRL-X_CTRL-O| CTRL-X CTRL-O omni completion |i_CTRL-X_CTRL-P| CTRL-X CTRL-P previous completion +|i_CTRL-X_CTRL-R| CTRL-X CTRL-R complete words from registers |i_CTRL-X_CTRL-S| CTRL-X CTRL-S spelling suggestions |i_CTRL-X_CTRL-T| CTRL-X CTRL-T complete identifiers from thesaurus |i_CTRL-X_CTRL-Y| CTRL-X CTRL-Y scroll down diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index f3d92b290b..553183d74d 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -1,4 +1,4 @@ -*insert.txt* For Vim version 9.1. Last change: 2025 May 08 +*insert.txt* For Vim version 9.1. Last change: 2025 May 26 VIM REFERENCE MANUAL by Bram Moolenaar @@ -649,6 +649,7 @@ Completion can be done for: 11. omni completion |i_CTRL-X_CTRL-O| 12. Spelling suggestions |i_CTRL-X_s| 13. keywords in 'complete' |i_CTRL-N| |i_CTRL-P| +14. words from registers |i_CTRL-X_CTRL-R| Additionally, |i_CTRL-X_CTRL-Z| stops completion without changing the text. @@ -1019,6 +1020,21 @@ CTRL-X CTRL-V Guess what kind of item is in front of the cursor and completion, for example: > :imap + +Completing words from registers *compl-register-words* + *i_CTRL-X_CTRL-R* +CTRL-X CTRL-R Guess what kind of item is in front of the cursor from + all registers and find the first match for it. + Further use of CTRL-R (without CTRL-X) will insert the + register content, see |i_CTRL-R|. + 'ignorecase' applies to the matching. + + CTRL-N Search forwards for next match. This match replaces + the previous one. + + CTRL-P Search backwards for previous match. This match + replaces the previous one. + User defined completion *compl-function* Completion is done by a function that can be defined by the user with the diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index e74c5e8a59..f0a7e9eefc 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1,4 +1,4 @@ -*options.txt* For Vim version 9.1. Last change: 2025 May 16 +*options.txt* For Vim version 9.1. Last change: 2025 May 26 VIM REFERENCE MANUAL by Bram Moolenaar @@ -4658,7 +4658,8 @@ A jump table for the options with a short description can be found at |Q_op|. 'ignorecase' 'ic' boolean (default off) global Ignore case in search patterns, |cmdline-completion|, when - searching in the tags file, and non-|Vim9| |expr-==|. + searching in the tags file, non-|Vim9| |expr-==| and for Insert-mode + completion |ins-completion|. Also see 'smartcase' and 'tagcase'. Can be overruled by using "\c" or "\C" in the pattern, see |/ignorecase|. diff --git a/runtime/doc/tags b/runtime/doc/tags index e58621298a..d3c77d17eb 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -6654,6 +6654,7 @@ compl-generic insert.txt /*compl-generic* compl-keyword insert.txt /*compl-keyword* compl-omni insert.txt /*compl-omni* compl-omni-filetypes insert.txt /*compl-omni-filetypes* +compl-register-words insert.txt /*compl-register-words* compl-spelling insert.txt /*compl-spelling* compl-states insert.txt /*compl-states* compl-stop insert.txt /*compl-stop* @@ -8425,6 +8426,7 @@ i_CTRL-X_CTRL-L insert.txt /*i_CTRL-X_CTRL-L* i_CTRL-X_CTRL-N insert.txt /*i_CTRL-X_CTRL-N* i_CTRL-X_CTRL-O insert.txt /*i_CTRL-X_CTRL-O* i_CTRL-X_CTRL-P insert.txt /*i_CTRL-X_CTRL-P* +i_CTRL-X_CTRL-R insert.txt /*i_CTRL-X_CTRL-R* i_CTRL-X_CTRL-S insert.txt /*i_CTRL-X_CTRL-S* i_CTRL-X_CTRL-T insert.txt /*i_CTRL-X_CTRL-T* i_CTRL-X_CTRL-U insert.txt /*i_CTRL-X_CTRL-U* diff --git a/runtime/doc/todo.txt b/runtime/doc/todo.txt index 5223687870..578f5e8fe6 100644 --- a/runtime/doc/todo.txt +++ b/runtime/doc/todo.txt @@ -1,4 +1,4 @@ -*todo.txt* For Vim version 9.1. Last change: 2025 Apr 24 +*todo.txt* For Vim version 9.1. Last change: 2025 May 26 VIM REFERENCE MANUAL by Bram Moolenaar @@ -4764,7 +4764,6 @@ Insert mode completion/expansion: 7 When expanding $HOME/dir with ^X^F keep the $HOME (with an option?). 7 Add CTRL-X command in Insert mode like CTRL-X CTRL-N, that completes WORDS instead of words. -8 Add CTRL-X CTRL-R: complete words from register contents. 8 Add completion of previously inserted texts (like what CTRL-A does). Requires remembering a number of insertions. 8 Add 'f' flag to 'complete': Expand file names. diff --git a/runtime/doc/usr_24.txt b/runtime/doc/usr_24.txt index 72f43f0681..250bd17ea6 100644 --- a/runtime/doc/usr_24.txt +++ b/runtime/doc/usr_24.txt @@ -187,6 +187,7 @@ with a certain type of item: CTRL-X CTRL-D macro definitions (also in included files) CTRL-X CTRL-I current and included files CTRL-X CTRL-K words from a dictionary + CTRL-X CTRL-R words from registers CTRL-X CTRL-T words from a thesaurus CTRL-X CTRL-] tags CTRL-X CTRL-V Vim command line diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index e03deedb8d..662cb17718 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -1,4 +1,4 @@ -*version9.txt* For Vim version 9.1. Last change: 2025 May 16 +*version9.txt* For Vim version 9.1. Last change: 2025 May 26 VIM REFERENCE MANUAL by Bram Moolenaar @@ -41586,6 +41586,9 @@ Support for the |Tuple| data type in Vim script and Vim9 script. Support for a vertical |tabpanel| window similar to the 'tabline'. +New Insert-mode completion: |i_CTRL-X_CTRL-R| to complete words from +registers. + *changed-9.2* Changed~ ------- diff --git a/runtime/doc/vi_diff.txt b/runtime/doc/vi_diff.txt index 46db57a458..ae14968bbe 100644 --- a/runtime/doc/vi_diff.txt +++ b/runtime/doc/vi_diff.txt @@ -338,6 +338,7 @@ Insert-mode completion. |ins-completion| |i_CTRL-X_CTRL-D| definitions or macros |i_CTRL-X_CTRL-O| Omni completion: clever completion specifically for a file type + |i_CTRL-X_CTRL-R| words from registers etc. Long line support. |'wrap'| |'linebreak'| diff --git a/src/edit.c b/src/edit.c index 4d45e8ef7c..366108883e 100644 --- a/src/edit.c +++ b/src/edit.c @@ -929,6 +929,8 @@ doESCkey: break; case Ctrl_R: // insert the contents of a register + if (ctrl_x_mode_register() && !ins_compl_active()) + goto docomplete; ins_reg(); auto_format(FALSE, TRUE); inserted_space = FALSE; diff --git a/src/insexpand.c b/src/insexpand.c index 76fb8f3506..c7d6fd428c 100644 --- a/src/insexpand.c +++ b/src/insexpand.c @@ -38,6 +38,7 @@ # define CTRL_X_LOCAL_MSG 15 // only used in "ctrl_x_msgs" # define CTRL_X_EVAL 16 // for builtin function complete() # define CTRL_X_CMDLINE_CTRL_X 17 // CTRL-X typed in CTRL_X_CMDLINE +# define CTRL_X_REGISTER 18 // complete words from registers # define CTRL_X_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT] @@ -45,7 +46,7 @@ static char *ctrl_x_msgs[] = { N_(" Keyword completion (^N^P)"), // CTRL_X_NORMAL, ^P/^N compl. - N_(" ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"), + N_(" ^X mode (^]^D^E^F^I^K^L^N^O^P^Rs^U^V^Y)"), NULL, // CTRL_X_SCROLL: depends on state N_(" Whole line completion (^L^N^P)"), N_(" File name completion (^F^N^P)"), @@ -62,6 +63,7 @@ static char *ctrl_x_msgs[] = N_(" Keyword Local completion (^N^P)"), NULL, // CTRL_X_EVAL doesn't use msg. N_(" Command-line completion (^V^N^P)"), + N_(" Register completion (^N^P)"), }; #if defined(FEAT_COMPL_FUNC) || defined(FEAT_EVAL) @@ -84,6 +86,7 @@ static char *ctrl_x_mode_names[] = { NULL, // CTRL_X_LOCAL_MSG only used in "ctrl_x_msgs" "eval", "cmdline", + "register", }; #endif @@ -330,6 +333,8 @@ static int ctrl_x_mode_eval(void) { return ctrl_x_mode == CTRL_X_EVAL; } int ctrl_x_mode_line_or_eval(void) { return ctrl_x_mode == CTRL_X_WHOLE_LINE || ctrl_x_mode == CTRL_X_EVAL; } +int ctrl_x_mode_register(void) + { return ctrl_x_mode == CTRL_X_REGISTER; } /* * Whether other than default completion has been selected. @@ -460,7 +465,7 @@ has_compl_option(int dict_opt) vim_is_ctrl_x_key(int c) { // Always allow ^R - let its results then be checked - if (c == Ctrl_R) + if (c == Ctrl_R && ctrl_x_mode != CTRL_X_REGISTER) return TRUE; // Accept and if the popup menu is visible. @@ -479,7 +484,7 @@ vim_is_ctrl_x_key(int c) || c == Ctrl_N || c == Ctrl_T || c == Ctrl_V || c == Ctrl_Q || c == Ctrl_U || c == Ctrl_O || c == Ctrl_S || c == Ctrl_K || c == 's' - || c == Ctrl_Z); + || c == Ctrl_Z || c == Ctrl_R); case CTRL_X_SCROLL: return (c == Ctrl_Y || c == Ctrl_E); case CTRL_X_WHOLE_LINE: @@ -511,6 +516,8 @@ vim_is_ctrl_x_key(int c) return (c == Ctrl_S || c == Ctrl_P || c == Ctrl_N); case CTRL_X_EVAL: return (c == Ctrl_P || c == Ctrl_N); + case CTRL_X_REGISTER: + return (c == Ctrl_R || c == Ctrl_P || c == Ctrl_N); } internal_error("vim_is_ctrl_x_key()"); return FALSE; @@ -2535,7 +2542,7 @@ ins_compl_addfrommatch(void) static int set_ctrl_x_mode(int c) { - int retval = FALSE; + int retval = FALSE; switch (c) { @@ -2563,8 +2570,11 @@ set_ctrl_x_mode(int c) ctrl_x_mode = CTRL_X_DICTIONARY; break; case Ctrl_R: - // Register insertion without exiting CTRL-X mode - // Simply allow ^R to happen without affecting ^X mode + // When CTRL-R is followed by '=', don't trigger register completion + // This allows expressions like =func() to work normally + if (vpeekc() == '=') + break; + ctrl_x_mode = CTRL_X_REGISTER; break; case Ctrl_T: // complete words from a thesaurus @@ -4783,6 +4793,83 @@ expand_cpt_function(callback_T *cb) } #endif +/* + * Get completion matches from register contents. + * Extracts words from all available registers and adds them to the completion list. + */ + static void +get_register_completion(void) +{ + int dir = compl_direction; + yankreg_T *reg = NULL; + void *reg_ptr = NULL; + + for (int i = 0; i < NUM_REGISTERS; i++) + { + int regname = 0; + + if (i == 0) + regname = '"'; // unnamed register + else if (i < 10) + regname = '0' + i; + else if (i == DELETION_REGISTER) + regname = '-'; +#ifdef FEAT_CLIPBOARD + else if (i == STAR_REGISTER) + regname = '*'; + else if (i == PLUS_REGISTER) + regname = '+'; +#endif + else + regname = 'a' + i - 10; + + // Skip invalid or black hole register + if (!valid_yank_reg(regname, FALSE) || regname == '_') + continue; + + reg_ptr = get_register(regname, FALSE); + if (reg_ptr == NULL) + continue; + + reg = (yankreg_T *)reg_ptr; + + for (int j = 0; j < reg->y_size; j++) + { + char_u *str = reg->y_array[j].string; + if (str == NULL) + continue; + + char_u *p = str; + while (*p != NUL) + { + p = find_word_start(p); + if (*p == NUL) + break; + + char_u *word_end = find_word_end(p); + + // Add the word to the completion list + int len = (int)(word_end - p); + if (len > 0 && (!compl_orig_text.string + || (p_ic ? STRNICMP(p, compl_orig_text.string, + compl_orig_text.length) == 0 + : STRNCMP(p, compl_orig_text.string, + compl_orig_text.length) == 0))) + { + if (ins_compl_add_infercase(p, len, p_ic, NULL, + dir, FALSE, 0) == OK) + dir = FORWARD; + } + + p = word_end; + } + } + + // Free the register copy + put_register(regname, reg_ptr); + } +} + /* * get the next set of completion matches for "type". * Returns TRUE if a new match is found. Otherwise returns FALSE. @@ -4838,6 +4925,10 @@ get_next_completion_match(int type, ins_compl_next_state_T *st, pos_T *ini) get_next_spell_completion(st->first_match_pos.lnum); break; + case CTRL_X_REGISTER: + get_register_completion(); + break; + default: // normal ^P/^N and ^X^L found_new_match = get_next_default_completion(st, ini); if (found_new_match == FAIL && st->ins_buf == curbuf) @@ -6125,6 +6216,10 @@ compl_get_info(char_u *line, int startcol, colnr_T curs_col, int *line_invalid) return FAIL; *line_invalid = TRUE; // "line" may have become invalid } + else if (ctrl_x_mode_register()) + { + return get_normal_compl_info(line, startcol, curs_col); + } else { internal_error("ins_complete()"); diff --git a/src/proto/insexpand.pro b/src/proto/insexpand.pro index e9ff62661b..8965aca4c7 100644 --- a/src/proto/insexpand.pro +++ b/src/proto/insexpand.pro @@ -17,6 +17,7 @@ int ctrl_x_mode_spell(void); int ctrl_x_mode_line_or_eval(void); int ctrl_x_mode_not_default(void); int ctrl_x_mode_not_defined_yet(void); +int ctrl_x_mode_register(void); int compl_status_adding(void); int compl_status_sol(void); int compl_status_local(void); diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim index 8bce0e4573..2a2df51770 100644 --- a/src/testdir/test_ins_complete.vim +++ b/src/testdir/test_ins_complete.vim @@ -4521,4 +4521,85 @@ func Test_complete_match() delfunc TestComplete endfunc +func Test_register_completion() + let @a = "completion test apple application" + let @b = "banana behavior better best" + let @c = "complete completion compliment computer" + let g:save_reg = '' + func GetItems() + let g:result = complete_info(['pum_visible']) + endfunc + + new + call setline(1, "comp") + call cursor(1, 4) + call feedkeys("a\\\\\", 'tx') + call assert_equal("compliment", getline(1)) + + inoremap =GetItems() + call feedkeys("S\\\\", 'tx') + call assert_equal(1, g:result['pum_visible']) + + call setline(1, "app") + call cursor(1, 3) + call feedkeys("a\\\\", 'tx') + call assert_equal("application", getline(1)) + + " Test completion with case differences + set ignorecase + let @e = "TestCase UPPERCASE lowercase" + call setline(1, "testc") + call cursor(1, 5) + call feedkeys("a\\\", 'tx') + call assert_equal("TestCase", getline(1)) + + " Test clipboard registers if available + if has('clipboard_working') + let g:save_reg = getreg('*') + call setreg('*', "clipboard selection unique words") + call setline(1, "uni") + call cursor(1, 3) + call feedkeys("a\\\", 'tx') + call assert_equal("unique", getline(1)) + call setreg('*', g:save_reg) + + let g:save_reg = getreg('+') + call setreg('+', "system clipboard special content") + call setline(1, "spe") + call cursor(1, 3) + call feedkeys("a\\\", 'tx') + call assert_equal("special", getline(1)) + call setreg('+', g:save_reg) + + call setreg('*', g:save_reg) + call setreg('a', "normal register") + call setreg('*', "clipboard mixed content") + call setline(1, "mix") + call cursor(1, 3) + call feedkeys("a\\\", 'tx') + call assert_equal("mixed", getline(1)) + call setreg('*', g:save_reg) + endif + + " Test black hole register should be skipped + let @_ = "blackhole content should not appear" + call setline(1, "black") + call cursor(1, 5) + call feedkeys("a\\\", 'tx') + call assert_equal("black", getline(1)) + + let @1 = "recent yank zero" + call setline(1, "ze") + call cursor(1, 2) + call feedkeys("a\\\", 'tx') + call assert_equal("zero", getline(1)) + + " Clean up + bwipe! + delfunc GetItems + unlet g:result + unlet g:save_reg + set ignorecase& +endfunc + " vim: shiftwidth=2 sts=2 expandtab nofoldenable diff --git a/src/version.c b/src/version.c index 637f178a03..ceedc5f7e3 100644 --- a/src/version.c +++ b/src/version.c @@ -709,6 +709,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1408, /**/ 1407, /**/ -- 2.47.2