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

0546068aae
This commit is contained in:
glepnir
2025-05-28 17:10:00 +08:00
committed by GitHub
parent a4f318574a
commit f2373a89d7
13 changed files with 198 additions and 12 deletions

View File

@@ -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-N| CTRL-X CTRL-N next completion
|i_CTRL-X_CTRL-O| CTRL-X CTRL-O omni 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-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-S| CTRL-X CTRL-S spelling suggestions
|i_CTRL-X_CTRL-T| CTRL-X CTRL-T complete identifiers from thesaurus |i_CTRL-X_CTRL-T| CTRL-X CTRL-T complete identifiers from thesaurus
|i_CTRL-X_CTRL-Y| CTRL-X CTRL-Y scroll down |i_CTRL-X_CTRL-Y| CTRL-X CTRL-Y scroll down

View File

@@ -629,6 +629,7 @@ Completion can be done for:
11. omni completion |i_CTRL-X_CTRL-O| 11. omni completion |i_CTRL-X_CTRL-O|
12. Spelling suggestions |i_CTRL-X_s| 12. Spelling suggestions |i_CTRL-X_s|
13. keywords in 'complete' |i_CTRL-N| |i_CTRL-P| 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. 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: > completion, for example: >
:imap <Tab> <C-X><C-V> :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* User defined completion *compl-function*
Completion is done by a function that can be defined by the user with the Completion is done by a function that can be defined by the user with the

View File

@@ -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) 'ignorecase' 'ic' boolean (default off)
global global
Ignore case in search patterns, |cmdline-completion|, when 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'. Also see 'smartcase' and 'tagcase'.
Can be overruled by using "\c" or "\C" in the pattern, see Can be overruled by using "\c" or "\C" in the pattern, see
|/ignorecase|. |/ignorecase|.

View File

@@ -187,6 +187,7 @@ with a certain type of item:
CTRL-X CTRL-D macro definitions (also in included files) CTRL-X CTRL-D macro definitions (also in included files)
CTRL-X CTRL-I current and included files CTRL-X CTRL-I current and included files
CTRL-X CTRL-K words from a dictionary 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-T words from a thesaurus
CTRL-X CTRL-] tags CTRL-X CTRL-] tags
CTRL-X CTRL-V Vim command line CTRL-X CTRL-V Vim command line

View File

@@ -230,6 +230,7 @@ Insert-mode completion. |ins-completion|
|i_CTRL-X_CTRL-D| definitions or macros |i_CTRL-X_CTRL-D| definitions or macros
|i_CTRL-X_CTRL-O| Omni completion: clever completion |i_CTRL-X_CTRL-O| Omni completion: clever completion
specifically for a file type specifically for a file type
|i_CTRL-X_CTRL-R| words from registers
etc. etc.
Long line support. |'wrap'| |'linebreak'| Long line support. |'wrap'| |'linebreak'|

View File

@@ -3186,7 +3186,8 @@ vim.o.iconstring = ""
vim.go.iconstring = vim.o.iconstring vim.go.iconstring = vim.o.iconstring
--- Ignore case in search patterns, `cmdline-completion`, when --- 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'. --- Also see 'smartcase' and 'tagcase'.
--- Can be overruled by using "\c" or "\C" in the pattern, see --- Can be overruled by using "\c" or "\C" in the pattern, see
--- `/ignorecase`. --- `/ignorecase`.

View File

@@ -796,6 +796,10 @@ static int insert_handle_key(InsertState *s)
break; break;
case Ctrl_R: // insert the contents of a register case Ctrl_R: // insert the contents of a register
if (ctrl_x_mode_register() && !ins_compl_active()) {
insert_do_complete(s);
break;
}
ins_reg(); ins_reg();
auto_format(false, true); auto_format(false, true);
s->inserted_space = false; s->inserted_space = false;

View File

@@ -53,6 +53,7 @@
#include "nvim/memory.h" #include "nvim/memory.h"
#include "nvim/message.h" #include "nvim/message.h"
#include "nvim/move.h" #include "nvim/move.h"
#include "nvim/ops.h"
#include "nvim/option.h" #include "nvim/option.h"
#include "nvim/option_defs.h" #include "nvim/option_defs.h"
#include "nvim/option_vars.h" #include "nvim/option_vars.h"
@@ -104,6 +105,7 @@ enum {
CTRL_X_EVAL = 16, ///< for builtin function complete() CTRL_X_EVAL = 16, ///< for builtin function complete()
CTRL_X_CMDLINE_CTRL_X = 17, ///< CTRL-X typed in CTRL_X_CMDLINE CTRL_X_CMDLINE_CTRL_X = 17, ///< CTRL-X typed in CTRL_X_CMDLINE
CTRL_X_BUFNAMES = 18, 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] #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. /// Message for CTRL-X mode, index is ctrl_x_mode.
static char *ctrl_x_msgs[] = { static char *ctrl_x_msgs[] = {
N_(" Keyword completion (^N^P)"), // CTRL_X_NORMAL, ^P/^N compl. 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 NULL, // CTRL_X_SCROLL: depends on state
N_(" Whole line completion (^L^N^P)"), N_(" Whole line completion (^L^N^P)"),
N_(" File name completion (^F^N^P)"), N_(" File name completion (^F^N^P)"),
@@ -128,6 +130,8 @@ static char *ctrl_x_msgs[] = {
N_(" Keyword Local completion (^N^P)"), N_(" Keyword Local completion (^N^P)"),
NULL, // CTRL_X_EVAL doesn't use msg. NULL, // CTRL_X_EVAL doesn't use msg.
N_(" Command-line completion (^V^N^P)"), N_(" Command-line completion (^V^N^P)"),
NULL,
N_(" Register completion (^N^P)"),
}; };
static char *ctrl_x_mode_names[] = { 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" NULL, // CTRL_X_LOCAL_MSG only used in "ctrl_x_msgs"
"eval", "eval",
"cmdline", "cmdline",
NULL, // CTRL_X_BUFNAME
"register",
}; };
/// Structure used to store one match for insert completion. /// 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; 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. /// Whether other than default completion has been selected.
bool ctrl_x_mode_not_default(void) bool ctrl_x_mode_not_default(void)
FUNC_ATTR_PURE FUNC_ATTR_PURE
@@ -514,7 +526,7 @@ bool vim_is_ctrl_x_key(int c)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_WARN_UNUSED_RESULT
{ {
// Always allow ^R - let its results then be checked // 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; 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_N || c == Ctrl_T || c == Ctrl_V
|| c == Ctrl_Q || c == Ctrl_U || c == Ctrl_O || c == Ctrl_Q || c == Ctrl_U || c == Ctrl_O
|| c == Ctrl_S || c == Ctrl_K || c == 's' || c == Ctrl_S || c == Ctrl_K || c == 's'
|| c == Ctrl_Z; || c == Ctrl_Z || c == Ctrl_R;
case CTRL_X_SCROLL: case CTRL_X_SCROLL:
return c == Ctrl_Y || c == Ctrl_E; return c == Ctrl_Y || c == Ctrl_E;
case CTRL_X_WHOLE_LINE: case CTRL_X_WHOLE_LINE:
@@ -564,6 +576,8 @@ bool vim_is_ctrl_x_key(int c)
return (c == Ctrl_P || c == Ctrl_N); return (c == Ctrl_P || c == Ctrl_N);
case CTRL_X_BUFNAMES: case CTRL_X_BUFNAMES:
return (c == Ctrl_P || c == Ctrl_N); 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()"); internal_error("vim_is_ctrl_x_key()");
return false; return false;
@@ -2244,8 +2258,12 @@ static bool set_ctrl_x_mode(const int c)
ctrl_x_mode = CTRL_X_DICTIONARY; ctrl_x_mode = CTRL_X_DICTIONARY;
break; break;
case Ctrl_R: case Ctrl_R:
// Register insertion without exiting CTRL-X mode // When CTRL-R is followed by '=', don't trigger register completion
// Simply allow ^R to happen without affecting ^X mode // This allows expressions like <C-R>=func()<CR> to work normally
if (vpeekc() == '=') {
break;
}
ctrl_x_mode = CTRL_X_REGISTER;
break; break;
case Ctrl_T: case Ctrl_T:
// complete words from a thesaurus // 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; 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". /// get the next set of completion matches for "type".
/// @return true if a new match is found, otherwise false. /// @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) 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: case CTRL_X_BUFNAMES:
get_next_bufname_token(); get_next_bufname_token();
break; break;
case CTRL_X_REGISTER:
get_register_completion();
break;
default: // normal ^P/^N and ^X^L default: // normal ^P/^N and ^X^L
found_new_match = get_next_default_completion(st, ini); 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; return FAIL;
} }
*line_invalid = true; // "line" may have become invalid *line_invalid = true; // "line" may have become invalid
} else if (ctrl_x_mode_register()) {
return get_normal_compl_info(line, startcol, curs_col);
} else { } else {
internal_error("ins_complete()"); internal_error("ins_complete()");
return FAIL; return FAIL;

View File

@@ -4315,7 +4315,8 @@ local options = {
defaults = false, defaults = false,
desc = [=[ desc = [=[
Ignore case in search patterns, |cmdline-completion|, when 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'. Also see 'smartcase' and 'tagcase'.
Can be overruled by using "\c" or "\C" in the pattern, see Can be overruled by using "\c" or "\C" in the pattern, see
|/ignorecase|. |/ignorecase|.

View File

@@ -124,7 +124,7 @@ describe('completion', function()
foo | foo |
^ | ^ |
{1:~ }|*5 {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>') feed('<C-n>')
screen:expect([[ screen:expect([[

View File

@@ -3519,7 +3519,7 @@ describe('float window', function()
| |
{0:~ }|*7 {0:~ }|*7
## grid 3 ## 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 ## grid 4
{1: }| {1: }|
{2:~ }|*2 {2:~ }|*2
@@ -3543,7 +3543,7 @@ describe('float window', function()
{1: } {1:^ } | {1: } {1:^ } |
{2:~ }{0: }{2:~ }{0: }|*2 {2:~ }{0: }{2:~ }{0: }|*2
{0:~ }|*5 {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 end

View File

@@ -815,7 +815,7 @@ describe('ui/ext_messages', function()
^ | ^ |
{1:~ }|*2 {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>') feed('<c-p>')

View File

@@ -3792,4 +3792,85 @@ func Test_complete_match()
delfunc TestComplete delfunc TestComplete
endfunc 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 " vim: shiftwidth=2 sts=2 expandtab nofoldenable