mirror of
https://github.com/neovim/neovim.git
synced 2026-04-26 17:24:18 +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: ~
|
See also: ~
|
||||||
• |vim.system()|
|
• |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()*
|
vim.ui.select({items}, {opts}, {on_choice}) *vim.ui.select()*
|
||||||
Prompts the user to pick from a list of items, allowing arbitrary
|
Prompts the user to pick from a list of items, allowing arbitrary
|
||||||
(potentially asynchronous) work until `on_choice`.
|
(potentially asynchronous) work until `on_choice`.
|
||||||
|
|||||||
@@ -466,6 +466,8 @@ UI
|
|||||||
• Cursor shape indicates when it is behind an unfocused floating window.
|
• Cursor shape indicates when it is behind an unfocused floating window.
|
||||||
• Improved LSP signature help rendering.
|
• Improved LSP signature help rendering.
|
||||||
• Multigrid UIs can call nvim_input_mouse with grid 0 to let Nvim decide the grid.
|
• 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
|
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
|
--- @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.o.stl = vim.o.statusline
|
||||||
vim.wo.statusline = vim.o.statusline
|
vim.wo.statusline = vim.o.statusline
|
||||||
vim.wo.stl = vim.wo.statusline
|
vim.wo.stl = vim.wo.statusline
|
||||||
|
|||||||
@@ -306,4 +306,91 @@ function M._get_urls()
|
|||||||
return urls
|
return urls
|
||||||
end
|
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
|
return M
|
||||||
|
|||||||
@@ -8807,6 +8807,7 @@ local options = {
|
|||||||
'%f %h%w%m%r ',
|
'%f %h%w%m%r ',
|
||||||
"%{% v:lua.require('vim._core.util').term_exitcode() %}",
|
"%{% v:lua.require('vim._core.util').term_exitcode() %}",
|
||||||
'%=',
|
'%=',
|
||||||
|
"%{% luaeval('(package.loaded[''vim.ui''] and vim.ui.progress_status()) or '''' ')%}",
|
||||||
"%{% &showcmdloc == 'statusline' ? '%-10.S ' : '' %}",
|
"%{% &showcmdloc == 'statusline' ? '%-10.S ' : '' %}",
|
||||||
"%{% exists('b:keymap_name') ? '<'..b:keymap_name..'> ' : '' %}",
|
"%{% exists('b:keymap_name') ? '<'..b:keymap_name..'> ' : '' %}",
|
||||||
"%{% &busy > 0 ? '◐ ' : '' %}",
|
"%{% &busy > 0 ? '◐ ' : '' %}",
|
||||||
|
|||||||
@@ -944,6 +944,7 @@ describe('default statusline', function()
|
|||||||
screen = Screen.new(60, 16)
|
screen = Screen.new(60, 16)
|
||||||
screen:add_extra_attr_ids {
|
screen:add_extra_attr_ids {
|
||||||
[100] = { foreground = Screen.colors.Magenta1, bold = true },
|
[100] = { foreground = Screen.colors.Magenta1, bold = true },
|
||||||
|
[131] = { foreground = Screen.colors.NvimDarkGreen },
|
||||||
}
|
}
|
||||||
command('set laststatus=2')
|
command('set laststatus=2')
|
||||||
command('set ruler')
|
command('set ruler')
|
||||||
@@ -964,6 +965,7 @@ describe('default statusline', function()
|
|||||||
'%f %h%w%m%r ',
|
'%f %h%w%m%r ',
|
||||||
"%{% v:lua.require('vim._core.util').term_exitcode() %}",
|
"%{% v:lua.require('vim._core.util').term_exitcode() %}",
|
||||||
'%=',
|
'%=',
|
||||||
|
"%{% luaeval('(package.loaded[''vim.ui''] and vim.ui.progress_status()) or '''' ')%}",
|
||||||
"%{% &showcmdloc == 'statusline' ? '%-10.S ' : '' %}",
|
"%{% &showcmdloc == 'statusline' ? '%-10.S ' : '' %}",
|
||||||
"%{% exists('b:keymap_name') ? '<'..b:keymap_name..'> ' : '' %}",
|
"%{% exists('b:keymap_name') ? '<'..b:keymap_name..'> ' : '' %}",
|
||||||
"%{% &busy > 0 ? '◐ ' : '' %}",
|
"%{% &busy > 0 ? '◐ ' : '' %}",
|
||||||
@@ -1040,6 +1042,56 @@ describe('default statusline', function()
|
|||||||
screen:expect({ any = '%[Exit: 9%]' })
|
screen:expect({ any = '%[Exit: 9%]' })
|
||||||
expect_exitcode(9)
|
expect_exitcode(9)
|
||||||
end)
|
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)
|
end)
|
||||||
|
|
||||||
describe("'statusline' in floatwin", function()
|
describe("'statusline' in floatwin", function()
|
||||||
|
|||||||
Reference in New Issue
Block a user