fix(ui): z=, tselect with async vim.ui.select

Problem:
After 55ceb31,  z= and tselect don't work if `vim.ui.select` is an async
provider (especially terminal buffers).

Solution:
Drop the `vim.wait()` approach, use an async approach.

fix #39506
This commit is contained in:
Justin M. Keyes
2026-04-30 02:23:28 +02:00
parent 18d7dd485b
commit 7c4845ff46
11 changed files with 211 additions and 249 deletions

View File

@@ -6,7 +6,8 @@ local uv = vim.uv
local N_ = vim.fn.gettext
--- Parsed ex command arguments for builtin commands, passed from C via `nlua_call_excmd`.
--- Inherits fields from user command args: args, bang, line1, line2, range, count, reg, smods.
--- Inherits fields from user command args: name, args, bang, line1, line2, range, count, reg, smods.
--- Note: For builtin commands `name` is the canonical command name.
--- @class vim._core.ExCmdArgs : vim.api.keyset.create_user_command.command_args
local M = {}

View File

@@ -1,4 +1,3 @@
local select_blocking = require('vim._core.ui').select_blocking
local N_ = vim.fn.gettext
local M = {}
@@ -10,13 +9,15 @@ local M = {}
--- @field altscore? integer Secondary score (only set when 'spellsuggest' contains "double" or "best").
--- @field salscore? boolean True if the score came from sound-alike comparison (only set alongside `altscore`).
--- Implements `spell_suggest()` (`z=`) vim.ui.select().
--- Implements `spell_suggest()` (`z=`) via vim.ui.select().
---
--- async: returns immediately, the chosen suggestion is applied later
--- by re-running `:normal! [idx]z=` from `on_choice`.
---
--- @param items vim._core.spell.Suggestion[]
--- @param bad string The misspelled word being replaced.
--- @return integer? # Selected item (1-indexed), or nil if cancelled.
function M.select_suggest(items, bad)
return select_blocking(items, {
vim.ui.select(items, {
prompt = N_('Change "%s" to:'):format(bad),
kind = 'spell',
format_item = function(s)
@@ -29,7 +30,14 @@ function M.select_suggest(items, bad)
end
return ('"%s"%s%s'):format(s.word, extra, score)
end,
})
}, function(_, idx)
if not idx then
return
end
-- Queue ":normal! [idx]z=" as user input, so the recursive spell_suggest runs via the normal
-- input-dispatch loop. Using vim.schedule + vim.cmd can hang bc of "Press ENTER".
vim.fn.feedkeys(vim.keycode(('<Cmd>normal! %dz=<CR>'):format(idx)), 'in')
end)
end
return M

View File

@@ -1,4 +1,3 @@
local select_blocking = require('vim._core.ui').select_blocking
local N_ = vim.fn.gettext
local M = {}
@@ -13,31 +12,47 @@ local M = {}
--- Implements `do_tag()` (`:tselect`, ambiguous `:tag`, …) via vim.ui.select().
---
--- @param items vim._core.tag.Match[] One per matching tag.
--- @return integer? # Selected item (1-indexed), or nil if cancelled.
function M.select_tag(items)
--- async: returns immediately, the chosen tag is applied later by re-running
--- `:[mods] [idx]tag {tagname}` (or `stag`) from `on_choice`.
---
--- @param eap vim._core.ExCmdArgs Original :tselect/:stselect/… invocation.
--- @param extra { items: vim._core.tag.Match[], tagname: string }
function M.select_tag(eap, extra)
local items, tagname = extra.items, extra.tagname
-- :stag/:stselect/:stjump need a split when re-invoked.
local stag = eap.name:sub(1, 1) == 's'
-- `eap.mods` is the raw modifier string (e.g. ":vert silent").
local mods_str = eap.mods ~= '' and (eap.mods .. ' ') or ''
local taglen = 18
for _, m in ipairs(items) do
taglen = math.max(taglen, vim.fn.strdisplaywidth(m.tag) + 2)
end
return select_blocking(items, {
vim.ui.select(items, {
prompt = N_('Type number and <Enter> (q or empty cancels):'),
kind = 'tag',
format_item = function(m)
local marker = m.cur and '>' or ' '
local kind = m.kind or ''
local extra = m.extra and (' ' .. m.extra) or ''
return ('%s %s %-4s %-' .. taglen .. 's %s%s'):format(
marker,
m.pri,
kind,
m.tag,
m.file,
extra
m.extra and (' ' .. m.extra) or ''
)
end,
})
}, function(_, idx)
if not idx then
return
end
-- Queue ":[mods] [idx](s)tag {tagname}" as user input, so the recursive do_tag runs via the
-- normal input-dispatch loop. Using vim.schedule + vim.cmd can hang bc of "Press ENTER".
local cmd = stag and 'stag' or 'tag'
vim.fn.feedkeys(vim.keycode(('<Cmd>%s%d%s %s<CR>'):format(mods_str, idx, cmd, tagname)), 'in')
end)
end
return M