commit f2373a89d78356423a84e9636d6949b5322ecc69
parent a4f318574a3a84b2119e9d613940937891d14f15
Author: glepnir <glephunter@gmail.com>
Date: Wed, 28 May 2025 17:10:00 +0800
vim-patch:9.1.1408: not easily possible to complete from register content (#34198)
Problem: not easily possible to complete from register content
Solution: add register-completion submode using i_CTRL-X_CTRL-R
(glepnir)
closes: vim/vim#17354
https://github.com/vim/vim/commit/0546068aaef2b1a40faa2945ef7eba249739f219
Diffstat:
13 files changed, 198 insertions(+), 12 deletions(-)
diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
@@ -146,6 +146,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
@@ -629,6 +629,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.
@@ -999,6 +1000,20 @@ CTRL-X CTRL-V Guess what kind of item is in front of the cursor and
completion, for example: >
:imap <Tab> <C-X><C-V>
+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
@@ -3345,7 +3345,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 |expr-==|.
+ searching in the tags file, |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/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/vi_diff.txt b/runtime/doc/vi_diff.txt
@@ -230,6 +230,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/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
@@ -3186,7 +3186,8 @@ vim.o.iconstring = ""
vim.go.iconstring = vim.o.iconstring
--- Ignore case in search patterns, `cmdline-completion`, when
---- searching in the tags file, and `expr-==`.
+--- searching in the tags file, `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/src/nvim/edit.c b/src/nvim/edit.c
@@ -796,6 +796,10 @@ static int insert_handle_key(InsertState *s)
break;
case Ctrl_R: // insert the contents of a register
+ if (ctrl_x_mode_register() && !ins_compl_active()) {
+ insert_do_complete(s);
+ break;
+ }
ins_reg();
auto_format(false, true);
s->inserted_space = false;
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
@@ -53,6 +53,7 @@
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
+#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/option_vars.h"
@@ -104,6 +105,7 @@ enum {
CTRL_X_EVAL = 16, ///< for builtin function complete()
CTRL_X_CMDLINE_CTRL_X = 17, ///< CTRL-X typed in CTRL_X_CMDLINE
CTRL_X_BUFNAMES = 18,
+ CTRL_X_REGISTER = 19, ///< complete words from registers
};
#define CTRL_X_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT]
@@ -111,7 +113,7 @@ enum {
/// Message for CTRL-X mode, index is ctrl_x_mode.
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)"),
@@ -128,6 +130,8 @@ 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)"),
+ NULL,
+ N_(" Register completion (^N^P)"),
};
static char *ctrl_x_mode_names[] = {
@@ -149,6 +153,8 @@ static char *ctrl_x_mode_names[] = {
NULL, // CTRL_X_LOCAL_MSG only used in "ctrl_x_msgs"
"eval",
"cmdline",
+ NULL, // CTRL_X_BUFNAME
+ "register",
};
/// Structure used to store one match for insert completion.
@@ -423,6 +429,12 @@ bool ctrl_x_mode_line_or_eval(void)
return ctrl_x_mode == CTRL_X_WHOLE_LINE || ctrl_x_mode == CTRL_X_EVAL;
}
+bool ctrl_x_mode_register(void)
+ FUNC_ATTR_PURE
+{
+ return ctrl_x_mode == CTRL_X_REGISTER;
+}
+
/// Whether other than default completion has been selected.
bool ctrl_x_mode_not_default(void)
FUNC_ATTR_PURE
@@ -514,7 +526,7 @@ bool vim_is_ctrl_x_key(int c)
FUNC_ATTR_WARN_UNUSED_RESULT
{
// 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;
}
@@ -534,7 +546,7 @@ bool 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:
@@ -564,6 +576,8 @@ bool vim_is_ctrl_x_key(int c)
return (c == Ctrl_P || c == Ctrl_N);
case CTRL_X_BUFNAMES:
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;
@@ -2244,8 +2258,12 @@ static bool set_ctrl_x_mode(const 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 <C-R>=func()<CR> to work normally
+ if (vpeekc() == '=') {
+ break;
+ }
+ ctrl_x_mode = CTRL_X_REGISTER;
break;
case Ctrl_T:
// complete words from a thesaurus
@@ -4027,6 +4045,63 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_
return found_new_match;
}
+/// 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)
+{
+ Direction dir = compl_direction;
+
+ for (int i = 0; i < NUM_REGISTERS; i++) {
+ int regname = get_register_name(i);
+ // Skip invalid or black hole register
+ if (!valid_yank_reg(regname, false) || regname == '_') {
+ continue;
+ }
+
+ yankreg_T *reg = copy_register(regname);
+
+ if (reg->y_array == NULL || reg->y_size == 0) {
+ free_register(reg);
+ xfree(reg);
+ continue;
+ }
+
+ for (size_t j = 0; j < reg->y_size; j++) {
+ char *str = reg->y_array[j].data;
+ if (str == NULL) {
+ continue;
+ }
+
+ char *p = str;
+ while (*p != NUL) {
+ p = find_word_start(p);
+ if (*p == NUL) {
+ break;
+ }
+
+ char *word_end = find_word_end(p);
+ int len = (int)(word_end - p);
+
+ // Add the word to the completion list
+ if (len > 0 && (!compl_orig_text.data
+ || (p_ic ? STRNICMP(p, compl_orig_text.data,
+ compl_orig_text.size) == 0
+ : strncmp(p, compl_orig_text.data,
+ compl_orig_text.size) == 0))) {
+ if (ins_compl_add_infercase(p, len, p_ic, NULL,
+ dir, false, 0) == OK) {
+ dir = FORWARD;
+ }
+ }
+ p = word_end;
+ }
+ }
+
+ free_register(reg);
+ xfree(reg);
+ }
+}
+
/// get the next set of completion matches for "type".
/// @return true if a new match is found, otherwise false.
static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_T *ini)
@@ -4071,6 +4146,9 @@ static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_
case CTRL_X_BUFNAMES:
get_next_bufname_token();
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);
@@ -5077,6 +5155,8 @@ static int compl_get_info(char *line, int startcol, colnr_T curs_col, bool *line
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()");
return FAIL;
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
@@ -4315,7 +4315,8 @@ local options = {
defaults = false,
desc = [=[
Ignore case in search patterns, |cmdline-completion|, when
- searching in the tags file, and |expr-==|.
+ searching in the tags file, |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/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua
@@ -124,7 +124,7 @@ describe('completion', function()
foo |
^ |
{1:~ }|*5
- {5:-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)} |
+ {5:-- ^X mode (^]^D^E^F^I^K^L^N^O^P^Rs^U^V^Y)} |
]])
feed('<C-n>')
screen:expect([[
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
@@ -3519,7 +3519,7 @@ describe('float window', function()
|
{0:~ }|*7
## grid 3
- {3:-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)} |
+ {3:-- ^X mode (^]^D^E^F^I^K^L^N^O^P^Rs^U^V^Y)} |
## grid 4
{1: }|
{2:~ }|*2
@@ -3543,7 +3543,7 @@ describe('float window', function()
{1: } {1:^ } |
{2:~ }{0: }{2:~ }{0: }|*2
{0:~ }|*5
- {3:-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)} |
+ {3:-- ^X mode (^]^D^E^F^I^K^L^N^O^P^Rs^U^V^Y)} |
]],
}
end
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
@@ -815,7 +815,7 @@ describe('ui/ext_messages', function()
^ |
{1:~ }|*2
]],
- showmode = { { '-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)', 5, 11 } },
+ showmode = { { '-- ^X mode (^]^D^E^F^I^K^L^N^O^P^Rs^U^V^Y)', 5, 11 } },
}
feed('<c-p>')
diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim
@@ -3792,4 +3792,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\<C-X>\<C-R>\<C-N>\<C-N>\<Esc>", 'tx')
+ call assert_equal("compliment", getline(1))
+
+ inoremap <buffer><F2> <C-R>=GetItems()<CR>
+ call feedkeys("S\<C-X>\<C-R>\<F2>\<ESC>", 'tx')
+ call assert_equal(1, g:result['pum_visible'])
+
+ call setline(1, "app")
+ call cursor(1, 3)
+ call feedkeys("a\<C-X>\<C-R>\<C-N>\<Esc>", '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\<C-X>\<C-R>\<Esc>", '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\<C-X>\<C-R>\<Esc>", '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\<C-X>\<C-R>\<Esc>", '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\<C-X>\<C-R>\<Esc>", '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\<C-X>\<C-R>\<Esc>", 'tx')
+ call assert_equal("black", getline(1))
+
+ let @1 = "recent yank zero"
+ call setline(1, "ze")
+ call cursor(1, 2)
+ call feedkeys("a\<C-X>\<C-R>\<Esc>", '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