fix: preserve queued keys at picker launch (#2274) (#2618)

Ensure that any keystrokes that are queued at picker launch are processed only
after the picker's mode (`insert` or `normal`) has been chosen, preserving
their intended meaning.

Previously the picker's mode was set by simulating keystrokes via `feedkeys()`.
In the absence of queued keystrokes, this works fine; but if the user is able
to queue keystrokes before the call to `feedkeys()`, those queued keystrokes
are processed before the simulated keystrokes that change the picker's mode.
Because of this unexpected ordering, the user's queued keystrokes may appear to
be ignored or may cause the picker to start in the wrong mode.

For example, consider the below normal-mode mapping:
```vim
:nnoremap <space>ff :Telescope find_files<CR>
```

Upon launching the picker via `<space>ff`, Neovim is already in normal mode.
To switch to insert mode in the picker, Telescope previously used a call to
`feedkeys("A")`, simulating a keypress of `A` to enter insert mode at the end
of the current line.  This `A` will not be processed until all previously
queued user keystrokes have been processed, causing issues.

In real-world use, problems occur when the user types `<space>ff` followed
quickly by characters intended as fuzzy match text.  This can be demonstrated
using `feedkeys()` as shown below.

```vim
:call feedkeys("\<space>ff" . "apple")
```

The user intended to search for `apple`, but the `a` is mis-interpreted as a
request to enter insert mode at end of line, after which `pple` is inserted;
subsequently, Telescope's simulated `A` is then appended, resulting in a search
string of `ppleA`.

Using `:startinsert!` (to enter insert mode as if by `A`) or `:normal! $` (to
enter normal mode and move to end-of-line) avoids interfering with the user's
queued keys.

Fixes #2274.
This commit is contained in:
Michael Henry
2023-07-22 17:27:22 -04:00
committed by GitHub
parent 7bb2fcecdc
commit f78d956901

View File

@@ -430,16 +430,16 @@ function Picker:find()
end end
if vim.tbl_contains({ "insert", "normal" }, self.initial_mode) then if vim.tbl_contains({ "insert", "normal" }, self.initial_mode) then
local mode = vim.fn.mode() -- Note that `feedkeys()` should not be used to change modes, as
local keys -- the user may have queued keystrokes that would be processed first,
-- interfering with desired operation.
if self.initial_mode == "normal" then if self.initial_mode == "normal" then
-- n: A<ESC> makes sure cursor is at always at end of prompt w/o default_text -- Ensure cursor is at always at end of prompt w/o default_text
keys = mode ~= "n" and "<ESC>A<ESC>" or "A<ESC>" vim.cmd "normal! $"
else else
-- always fully retrigger insert mode: required for going from one picker to next -- always fully retrigger insert mode: required for going from one picker to next
keys = mode ~= "n" and "<ESC>A" or "A" vim.cmd "startinsert!"
end end
a.nvim_feedkeys(a.nvim_replace_termcodes(keys, true, false, true), "n", true)
else else
utils.notify( utils.notify(
"pickers.find", "pickers.find",