mirror of
https://github.com/neovim/neovim.git
synced 2026-03-31 21:02:11 +00:00
feat(progress): status api, 'statusline' integration #35428
Problem: Default statusline doesn't show progress status. Solution: - Provide `vim.ui.progress_status()`. - Include it in the default 'statusline'. How it works: Status text summarizes "running" progress messages. - If none: returns empty string - If one running item: "title: percent%" - If multiple running items: "Progress: N items avg-percent%"
This commit is contained in:
@@ -5031,6 +5031,13 @@ vim.ui.open({path}, {opt}) *vim.ui.open()*
|
||||
See also: ~
|
||||
• |vim.system()|
|
||||
|
||||
vim.ui.progress_status() *vim.ui.progress_status()*
|
||||
Gets the status of currently running progress messages, in a format
|
||||
convenient for inclusion in 'statusline'.
|
||||
|
||||
Return: ~
|
||||
(`string`) formatted text of progress status for statusline
|
||||
|
||||
vim.ui.select({items}, {opts}, {on_choice}) *vim.ui.select()*
|
||||
Prompts the user to pick from a list of items, allowing arbitrary
|
||||
(potentially asynchronous) work until `on_choice`.
|
||||
|
||||
@@ -466,6 +466,8 @@ UI
|
||||
• Cursor shape indicates when it is behind an unfocused floating window.
|
||||
• Improved LSP signature help rendering.
|
||||
• Multigrid UIs can call nvim_input_mouse with grid 0 to let Nvim decide the grid.
|
||||
• |vim.ui.progress_status()| returns a formatted string of currently
|
||||
running |progress-message|.
|
||||
|
||||
VIMSCRIPT
|
||||
|
||||
|
||||
2
runtime/lua/vim/_meta/options.lua
generated
2
runtime/lua/vim/_meta/options.lua
generated
@@ -6994,7 +6994,7 @@ vim.wo.stc = vim.wo.statuscolumn
|
||||
---
|
||||
---
|
||||
--- @type string
|
||||
vim.o.statusline = "%<%f %h%w%m%r %{% v:lua.require('vim._core.util').term_exitcode() %}%=%{% &showcmdloc == 'statusline' ? '%-10.S ' : '' %}%{% exists('b:keymap_name') ? '<'..b:keymap_name..'> ' : '' %}%{% &busy > 0 ? '◐ ' : '' %}%{% luaeval('(package.loaded[''vim.diagnostic''] and next(vim.diagnostic.count()) and vim.diagnostic.status() .. '' '') or '''' ') %}%{% &ruler ? ( &rulerformat == '' ? '%-14.(%l,%c%V%) %P' : &rulerformat ) : '' %}"
|
||||
vim.o.statusline = "%<%f %h%w%m%r %{% v:lua.require('vim._core.util').term_exitcode() %}%=%{% luaeval('(package.loaded[''vim.ui''] and vim.ui.progress_status()) or '''' ')%}%{% &showcmdloc == 'statusline' ? '%-10.S ' : '' %}%{% exists('b:keymap_name') ? '<'..b:keymap_name..'> ' : '' %}%{% &busy > 0 ? '◐ ' : '' %}%{% luaeval('(package.loaded[''vim.diagnostic''] and next(vim.diagnostic.count()) and vim.diagnostic.status() .. '' '') or '''' ') %}%{% &ruler ? ( &rulerformat == '' ? '%-14.(%l,%c%V%) %P' : &rulerformat ) : '' %}"
|
||||
vim.o.stl = vim.o.statusline
|
||||
vim.wo.statusline = vim.o.statusline
|
||||
vim.wo.stl = vim.wo.statusline
|
||||
|
||||
@@ -306,4 +306,91 @@ function M._get_urls()
|
||||
return urls
|
||||
end
|
||||
|
||||
do
|
||||
---@class ProgressMessage
|
||||
---@field id? number|string ID of the progress message
|
||||
---@field title? string Title of the progress message
|
||||
---@field status string Status: "running" | "success" | "failed" | "cancel"
|
||||
---@field percent? integer Percent complete (0–100)
|
||||
---@private
|
||||
|
||||
--- Cache of active progress messages, keyed by msg_id
|
||||
--- TODO(justinmk): visibility of "stale" (never-finished) Progress. https://github.com/neovim/neovim/pull/35428#discussion_r2942696157
|
||||
---@type table<integer, ProgressMessage>
|
||||
local progress = {}
|
||||
|
||||
-- store progress events
|
||||
local progress_group, progress_autocmd = nil, nil
|
||||
|
||||
---Initialize progress event listeners
|
||||
local function progress_init()
|
||||
progress_group = vim.api.nvim_create_augroup('nvim.ui.progress_status', { clear = true })
|
||||
progress_autocmd = vim.api.nvim_create_autocmd('Progress', {
|
||||
group = progress_group,
|
||||
desc = 'Tracks progress messages for vim.ui.progress_status()',
|
||||
---@param ev {data: {id: integer, title: string, status: string, percent: integer}}
|
||||
callback = function(ev)
|
||||
if not ev.data or not ev.data.id then
|
||||
return
|
||||
end
|
||||
progress[ev.data.id] = {
|
||||
id = ev.data.id,
|
||||
title = ev.data.title,
|
||||
status = ev.data.status,
|
||||
percent = ev.data.percent or 0,
|
||||
}
|
||||
|
||||
-- Clear finished items
|
||||
if
|
||||
ev.data.status == 'success'
|
||||
or ev.data.percent == 100
|
||||
or ev.data.status == 'failed'
|
||||
or ev.data.status == 'cancel'
|
||||
then
|
||||
progress[ev.data.id] = nil
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
---Return statusline text summarizing progress messages.
|
||||
--- - If none: returns empty string
|
||||
--- - If one running item: "title: 42%"
|
||||
--- - If multiple running items: "Progress: N items AVG%"
|
||||
---@param running ProgressMessage[]
|
||||
---@return string
|
||||
local function progress_status_fmt(running)
|
||||
local count = #running
|
||||
if count == 0 then
|
||||
return '' -- nothing to show
|
||||
elseif count == 1 then
|
||||
local progress_item = running[1]
|
||||
if progress_item.title == nil then
|
||||
return string.format('%d%%%% ', progress_item.percent or 0)
|
||||
end
|
||||
return string.format('%s: %d%%%% ', progress_item.title, progress_item.percent or 0)
|
||||
else
|
||||
local sum = 0 ---@type integer
|
||||
for _, progress_item in ipairs(running) do
|
||||
sum = sum + (progress_item.percent or 0)
|
||||
end
|
||||
local avg = math.floor(sum / count)
|
||||
return string.format('Progress: %d items %d%%%% ', count, avg)
|
||||
end
|
||||
end
|
||||
|
||||
--- Gets the status of currently running progress messages, in a format
|
||||
--- convenient for inclusion in 'statusline'.
|
||||
---@return string formatted text of progress status for statusline
|
||||
function M.progress_status()
|
||||
-- Create progress event listener on first call
|
||||
if progress_autocmd == nil then
|
||||
progress_init()
|
||||
end
|
||||
|
||||
local running = vim.tbl_values(progress)
|
||||
return progress_status_fmt(running) or ''
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -8807,6 +8807,7 @@ local options = {
|
||||
'%f %h%w%m%r ',
|
||||
"%{% v:lua.require('vim._core.util').term_exitcode() %}",
|
||||
'%=',
|
||||
"%{% luaeval('(package.loaded[''vim.ui''] and vim.ui.progress_status()) or '''' ')%}",
|
||||
"%{% &showcmdloc == 'statusline' ? '%-10.S ' : '' %}",
|
||||
"%{% exists('b:keymap_name') ? '<'..b:keymap_name..'> ' : '' %}",
|
||||
"%{% &busy > 0 ? '◐ ' : '' %}",
|
||||
|
||||
@@ -944,6 +944,7 @@ describe('default statusline', function()
|
||||
screen = Screen.new(60, 16)
|
||||
screen:add_extra_attr_ids {
|
||||
[100] = { foreground = Screen.colors.Magenta1, bold = true },
|
||||
[131] = { foreground = Screen.colors.NvimDarkGreen },
|
||||
}
|
||||
command('set laststatus=2')
|
||||
command('set ruler')
|
||||
@@ -964,6 +965,7 @@ describe('default statusline', function()
|
||||
'%f %h%w%m%r ',
|
||||
"%{% v:lua.require('vim._core.util').term_exitcode() %}",
|
||||
'%=',
|
||||
"%{% luaeval('(package.loaded[''vim.ui''] and vim.ui.progress_status()) or '''' ')%}",
|
||||
"%{% &showcmdloc == 'statusline' ? '%-10.S ' : '' %}",
|
||||
"%{% exists('b:keymap_name') ? '<'..b:keymap_name..'> ' : '' %}",
|
||||
"%{% &busy > 0 ? '◐ ' : '' %}",
|
||||
@@ -1040,6 +1042,56 @@ describe('default statusline', function()
|
||||
screen:expect({ any = '%[Exit: 9%]' })
|
||||
expect_exitcode(9)
|
||||
end)
|
||||
|
||||
it('shows and updates progress status', function()
|
||||
exec_lua("vim.o.statusline = ''")
|
||||
local function get_progress()
|
||||
return exec_lua(function()
|
||||
local stl_str = vim.ui.progress_status()
|
||||
return vim.api.nvim_eval_statusline(stl_str, {}).str
|
||||
end)
|
||||
end
|
||||
|
||||
eq('', get_progress())
|
||||
---@type integer|string
|
||||
local id1 = api.nvim_echo(
|
||||
{ { 'searching...' } },
|
||||
true,
|
||||
{ kind = 'progress', title = 'test', status = 'running', percent = 10 }
|
||||
)
|
||||
eq('test: 10% ', get_progress())
|
||||
|
||||
api.nvim_echo(
|
||||
{ { 'searching' } },
|
||||
true,
|
||||
{ id = id1, kind = 'progress', percent = 50, status = 'running', title = 'terminal(ripgrep)' }
|
||||
)
|
||||
eq('terminal(ripgrep): 50% ', get_progress())
|
||||
|
||||
api.nvim_echo(
|
||||
{ { 'searching...' } },
|
||||
true,
|
||||
{ kind = 'progress', title = 'second-item', status = 'running', percent = 20 }
|
||||
)
|
||||
eq('Progress: 2 items 35% ', get_progress())
|
||||
|
||||
api.nvim_echo({ { 'searching' } }, true, {
|
||||
id = id1,
|
||||
kind = 'progress',
|
||||
percent = 100,
|
||||
status = 'success',
|
||||
title = 'terminal(ripgrep)',
|
||||
})
|
||||
eq('second-item: 20% ', get_progress())
|
||||
|
||||
exec('redrawstatus')
|
||||
screen:expect([[
|
||||
^ |
|
||||
{1:~ }|*13
|
||||
{3:[No Name] second-item: 20% 0,0-1 All}|
|
||||
{131:terminal(ripgrep)}: {19:100% }searching |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("'statusline' in floatwin", function()
|
||||
|
||||
Reference in New Issue
Block a user