mirror of
https://github.com/neovim/neovim.git
synced 2026-07-19 15:41:32 +00:00
feat(ui): use vim.ui.select for :oldfiles, :recover
Problem:
followup to 55ceb314ca #39478
`:oldfiles` and swapfile `:recover` do not delegate to `vim.ui.select`.
Solution:
- Delegate to `vim.ui.select`.
- Fix a long-standing `recover_names` bug where `concat_fnames(dir_name,
files[i], true)` produced malformed `<dir>//<dir>/<file>` paths (also
fixes `swapfilelist()`).
This commit is contained in:
@@ -11,6 +11,19 @@ local N_ = vim.fn.gettext
|
||||
|
||||
local M = {}
|
||||
|
||||
--- Apply the `:filter[!] /pattern/` modifier to a single message. See also `message_filtered()`.
|
||||
---
|
||||
--- @param filter vim.api.keyset.cmd_mods_filter ":filter" mod.
|
||||
--- @param msg string Message to test.
|
||||
--- @return boolean # True if `msg` should be skipped (not displayed).
|
||||
function M.filter(filter, msg)
|
||||
if not filter or filter.pattern == '' then
|
||||
return false
|
||||
end
|
||||
local match = vim.regex(filter.pattern):match_str(msg) ~= nil
|
||||
return match == filter.force
|
||||
end
|
||||
|
||||
--- @param msg string
|
||||
local function echo_err(msg)
|
||||
api.nvim_echo({ { msg } }, true, { err = true })
|
||||
@@ -266,4 +279,43 @@ function M.ex_uptime()
|
||||
api.nvim_echo({ { N_('Up %s'):format(uptime_display) } }, true, {})
|
||||
end
|
||||
|
||||
--- `:oldfiles` and `:browse oldfiles`. Lists v:oldfiles (plain `:oldfiles`) or shows (async)
|
||||
--- vim.ui.select() picker (`:browse oldfiles`) and edits the chosen file.
|
||||
--- @param eap vim._core.ExCmdArgs
|
||||
function M.ex_oldfiles(eap)
|
||||
local files = vim.v.oldfiles
|
||||
if not files or #files == 0 then
|
||||
api.nvim_echo({ { N_('No old files') } }, false, {})
|
||||
return
|
||||
end
|
||||
|
||||
if eap.smods.browse then
|
||||
vim.ui.select(files, {
|
||||
prompt = N_('Select an oldfile:'),
|
||||
kind = 'oldfiles',
|
||||
}, function(_, idx)
|
||||
if idx then
|
||||
api.nvim_cmd({
|
||||
cmd = 'edit',
|
||||
args = { vim.fn.expand(files[idx]) },
|
||||
magic = { file = false, bar = true }, -- May contain '%' (e.g. swapfiles), don't expand.
|
||||
}, {})
|
||||
end
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
-- `:oldfiles`: list the entries. Honor `:filter /pat/[!]` per entry.
|
||||
local lines = {} ---@type [string][]
|
||||
for i, f in ipairs(files) do
|
||||
if not M.filter(eap.smods.filter, f) then
|
||||
lines[#lines + 1] = { ('%d: %s\n'):format(i, f) }
|
||||
end
|
||||
end
|
||||
if #lines == 0 then
|
||||
return
|
||||
end
|
||||
api.nvim_echo(lines, false, {})
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -10,13 +10,12 @@ 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`).
|
||||
|
||||
--- Called from `spell_suggest()` (`z=`) to let the user pick from `items` via
|
||||
--- |vim.ui.select()|.
|
||||
--- Implements `spell_suggest()` (`z=`) vim.ui.select().
|
||||
---
|
||||
--- @param items vim._core.spell.Suggestion[]
|
||||
--- @param bad string The misspelled word being replaced.
|
||||
--- @return integer? # 1-based index of the chosen suggestion, or nil if cancelled.
|
||||
function M.suggest_select(items, bad)
|
||||
--- @return integer? # Selected item (1-indexed), or nil if cancelled.
|
||||
function M.select_suggest(items, bad)
|
||||
return select_blocking(items, {
|
||||
prompt = N_('Change "%s" to:'):format(bad),
|
||||
kind = 'spell',
|
||||
|
||||
86
runtime/lua/vim/_core/swapfile.lua
Normal file
86
runtime/lua/vim/_core/swapfile.lua
Normal file
@@ -0,0 +1,86 @@
|
||||
local api = vim.api
|
||||
local N_ = vim.fn.gettext
|
||||
|
||||
local M = {}
|
||||
|
||||
--- Renders a swap file as a multi-line block:
|
||||
--- ```
|
||||
--- %home%foo%bar%README.md.swl
|
||||
--- dated: Thu Apr 23 17:25:52 2026
|
||||
--- file name: ~foo/bar/README.md
|
||||
--- modified: no
|
||||
--- user name: justin host name: minime
|
||||
--- process ID: 10521 (STILL RUNNING)
|
||||
--- ```
|
||||
--- @param path string
|
||||
--- @return string
|
||||
local function format_swap(path)
|
||||
local info = vim.fn.swapinfo(path)
|
||||
local mtime = info.mtime and vim.fn.strftime('%a %b %d %H:%M:%S %Y', info.mtime) or '?'
|
||||
local lines = {
|
||||
vim.fs.basename(path),
|
||||
(' dated: %s'):format(mtime),
|
||||
}
|
||||
if info.error then
|
||||
lines[#lines + 1] = (' [%s]'):format(info.error)
|
||||
else
|
||||
lines[#lines + 1] = (' file name: %s'):format(
|
||||
info.fname == '' and '[No Name]' or info.fname
|
||||
)
|
||||
lines[#lines + 1] = (' modified: %s'):format(info.dirty == 1 and 'YES' or 'no')
|
||||
if info.user ~= '' or info.host ~= '' then
|
||||
local parts = {} ---@type string[]
|
||||
if info.user ~= '' then
|
||||
parts[#parts + 1] = ('user name: %s'):format(info.user)
|
||||
end
|
||||
if info.host ~= '' then
|
||||
parts[#parts + 1] = ('host name: %s'):format(info.host)
|
||||
end
|
||||
lines[#lines + 1] = (' %s'):format(table.concat(parts, ' '))
|
||||
end
|
||||
if info.pid > 0 then
|
||||
lines[#lines + 1] = (' process ID: %d (STILL RUNNING)'):format(info.pid)
|
||||
end
|
||||
end
|
||||
return table.concat(lines, '\n')
|
||||
end
|
||||
|
||||
--- Implements `:recover` (when there are multiple swap files): let the user pick via vim.ui.select().
|
||||
---
|
||||
--- async: returns immediately, then schedules `:recover {path}` on the chosen swapfile.
|
||||
---
|
||||
--- @param items string[] List of swapfile paths.
|
||||
function M.select_swap(items)
|
||||
vim.ui.select(items, {
|
||||
prompt = N_('Enter number of swap file to use (q or empty cancels):'),
|
||||
kind = 'swap',
|
||||
format_item = format_swap,
|
||||
}, function(_, idx)
|
||||
if not idx then
|
||||
return
|
||||
end
|
||||
-- Queue ":recover! <swapfile>" as user input, so the recursive recovery runs via the normal
|
||||
-- input-dispatch loop. Using vim.schedule + vim.cmd can hang bc of "Press ENTER".
|
||||
vim.fn.feedkeys(
|
||||
vim.keycode(('<Cmd>recover! %s<CR>'):format(vim.fn.fnameescape(items[idx]))),
|
||||
'in'
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
--- Implements `nvim -r` (no arg): list every swapfile found in 'directory'.
|
||||
---
|
||||
--- @param items string[] List of swapfile paths.
|
||||
function M.list_swaps(items)
|
||||
local lines = { { N_('Swap files found:') .. '\n' } } ---@type [string][]
|
||||
if #items == 0 then
|
||||
lines[#lines + 1] = { ' ' .. N_('-- none --') }
|
||||
else
|
||||
for i, path in ipairs(items) do
|
||||
lines[#lines + 1] = { ('%d. %s\n'):format(i, format_swap(path)) }
|
||||
end
|
||||
end
|
||||
api.nvim_echo(lines, false, {})
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -11,12 +11,11 @@ local M = {}
|
||||
--- @field extra? string
|
||||
--- @field cur boolean True if this is the currently-active tagstack match.
|
||||
|
||||
--- Called from `do_tag()` (`:tselect`, ambiguous `:tag`, etc.) to let the user
|
||||
--- pick from `matches` via |vim.ui.select()|.
|
||||
--- Implements `do_tag()` (`:tselect`, ambiguous `:tag`, …) via vim.ui.select().
|
||||
---
|
||||
--- @param items vim._core.tag.Match[] One per matching tag.
|
||||
--- @return integer? # 1-based index of the chosen tag, or nil if cancelled.
|
||||
function M.select(items)
|
||||
--- @return integer? # Selected item (1-indexed), or nil if cancelled.
|
||||
function M.select_tag(items)
|
||||
local taglen = 18
|
||||
for _, m in ipairs(items) do
|
||||
taglen = math.max(taglen, vim.fn.strdisplaywidth(m.tag) + 2)
|
||||
|
||||
Reference in New Issue
Block a user