docs(extui): rename box->msg, more->pager, prompt->dialog

Includes breaking changes to the `opts` layout. "box" doesn't really
describe anything other than a floating window so was an unwanted synonym.
This commit is contained in:
Luuk van Baal
2025-06-10 17:12:17 +02:00
committed by luukvbaal
parent 76f76fb083
commit 9ec6c19c67
5 changed files with 140 additions and 142 deletions

View File

@@ -2683,12 +2683,10 @@ To enable the experimental UI (default opts shown): >lua
require('vim._extui').enable({ require('vim._extui').enable({
enable = true, -- Whether to enable or disable the UI. enable = true, -- Whether to enable or disable the UI.
msg = { -- Options related to the message module. msg = { -- Options related to the message module.
---@type 'box'|'cmd' Type of window used to place messages, either in the ---@type 'cmd'|'msg' Where to place regular messages, either in the
---cmdline or in a separate message box window with ephemeral messages. ---cmdline or in a separate ephemeral message window.
pos = 'cmd', target = 'cmd',
box = { -- Options related to the message box window. timeout = 4000, -- Time a message is visible in the message window.
timeout = 4000, -- Time a message is visible.
},
}, },
}) })
< <
@@ -2696,15 +2694,15 @@ To enable the experimental UI (default opts shown): >lua
There are four separate window types used by this interface: There are four separate window types used by this interface:
• "cmd": The cmdline window; also used for 'showcmd', 'showmode', 'ruler', and • "cmd": The cmdline window; also used for 'showcmd', 'showmode', 'ruler', and
messages if 'cmdheight' > 0. messages if 'cmdheight' > 0.
• "box": The message box window; used for messages when 'cmdheight' == 0. • "msg": The message window; used for messages when 'cmdheight' == 0.
• "more": The more-prompt window; used for |:messages| and certain messages • "pager": The pager window; used for |:messages| and certain messages that
that should be shown in full. should be shown in full.
• "prompt": The cmdline prompt window; used for prompt messages that expect • "dialog": The dialog window; used for prompt messages that expect user
user input. input.
These four windows are assigned the "cmdline", "msgbox", "msgmore" and These four windows are assigned the "cmd", "msg", "pager" and "dialog"
"msgprompt" 'filetype' respectively. Use a |FileType| autocommand to configure 'filetype' respectively. Use a |FileType| autocommand to configure any local
any local options for these windows and their respective buffers. options for these windows and their respective buffers.
Rather than a |hit-enter-prompt|, messages shown in the cmdline area that do Rather than a |hit-enter-prompt|, messages shown in the cmdline area that do
not fit are appended with a `[+x]` "spill" indicator, where `x` indicates the not fit are appended with a `[+x]` "spill" indicator, where `x` indicates the

View File

@@ -8,12 +8,10 @@
---require('vim._extui').enable({ ---require('vim._extui').enable({
--- enable = true, -- Whether to enable or disable the UI. --- enable = true, -- Whether to enable or disable the UI.
--- msg = { -- Options related to the message module. --- msg = { -- Options related to the message module.
--- ---@type 'box'|'cmd' Type of window used to place messages, either in the --- ---@type 'cmd'|'msg' Where to place regular messages, either in the
--- ---cmdline or in a separate message box window with ephemeral messages. --- ---cmdline or in a separate ephemeral message window.
--- pos = 'cmd', --- target = 'cmd',
--- box = { -- Options related to the message box window. --- timeout = 4000, -- Time a message is visible in the message window.
--- timeout = 4000, -- Time a message is visible.
--- },
--- }, --- },
---}) ---})
---``` ---```
@@ -21,15 +19,14 @@
---There are four separate window types used by this interface: ---There are four separate window types used by this interface:
---- "cmd": The cmdline window; also used for 'showcmd', 'showmode', 'ruler', and ---- "cmd": The cmdline window; also used for 'showcmd', 'showmode', 'ruler', and
--- messages if 'cmdheight' > 0. --- messages if 'cmdheight' > 0.
---- "box": The message box window; used for messages when 'cmdheight' == 0. ---- "msg": The message window; used for messages when 'cmdheight' == 0.
---- "more": The more-prompt window; used for |:messages| and certain messages ---- "pager": The pager window; used for |:messages| and certain messages
--- that should be shown in full. --- that should be shown in full.
---- "prompt": The cmdline prompt window; used for prompt messages that expect ---- "dialog": The dialog window; used for prompt messages that expect user input.
--- user input.
--- ---
---These four windows are assigned the "cmdline", "msgbox", "msgmore" and ---These four windows are assigned the "cmd", "msg", "pager" and "dialog"
---"msgprompt" 'filetype' respectively. Use a |FileType| autocommand to configure ---'filetype' respectively. Use a |FileType| autocommand to configure any local
---any local options for these windows and their respective buffers. ---options for these windows and their respective buffers.
--- ---
---Rather than a |hit-enter-prompt|, messages shown in the cmdline area that do ---Rather than a |hit-enter-prompt|, messages shown in the cmdline area that do
---not fit are appended with a `[+x]` "spill" indicator, where `x` indicates the ---not fit are appended with a `[+x]` "spill" indicator, where `x` indicates the
@@ -59,6 +56,10 @@ local scheduled_ui_callback = vim.schedule_wrap(ui_callback)
---@nodoc ---@nodoc
function M.enable(opts) function M.enable(opts)
vim.validate('opts', opts, 'table', true) vim.validate('opts', opts, 'table', true)
if opts.msg then
vim.validate('opts.msg.pos', opts.msg.pos, 'nil', true, 'nil: "pos" moved to opts.target')
vim.validate('opts.msg.box', opts.msg.box, 'nil', true, 'nil: "timeout" moved to opts.msg')
end
ext.cfg = vim.tbl_deep_extend('keep', opts, ext.cfg) ext.cfg = vim.tbl_deep_extend('keep', opts, ext.cfg)
if ext.cfg.enable == false then if ext.cfg.enable == false then
@@ -84,13 +85,13 @@ function M.enable(opts)
-- Use MsgArea and hide search highlighting in the cmdline window. -- Use MsgArea and hide search highlighting in the cmdline window.
-- TODO: Add new highlight group/namespaces for other windows? It is -- TODO: Add new highlight group/namespaces for other windows? It is
-- not clear if MsgArea is wanted in the box, more and prompt windows. -- not clear if MsgArea is wanted in the msg, pager and dialog windows.
api.nvim_set_hl(ext.ns, 'Normal', { link = 'MsgArea' }) api.nvim_set_hl(ext.ns, 'Normal', { link = 'MsgArea' })
api.nvim_set_hl(ext.ns, 'Search', { link = 'MsgArea' }) api.nvim_set_hl(ext.ns, 'Search', { link = 'MsgArea' })
api.nvim_set_hl(ext.ns, 'CurSearch', { link = 'MsgArea' }) api.nvim_set_hl(ext.ns, 'CurSearch', { link = 'MsgArea' })
api.nvim_set_hl(ext.ns, 'IncSearch', { link = 'MsgArea' }) api.nvim_set_hl(ext.ns, 'IncSearch', { link = 'MsgArea' })
-- The visibility and appearance of the cmdline and message box window is -- The visibility and appearance of the cmdline and message window is
-- dependent on some option values. Reconfigure windows when option value -- dependent on some option values. Reconfigure windows when option value
-- has changed and after VimEnter when the user configured value is known. -- has changed and after VimEnter when the user configured value is known.
-- TODO: Reconsider what is needed when this module is enabled by default early in startup. -- TODO: Reconsider what is needed when this module is enabled by default early in startup.
@@ -101,7 +102,7 @@ function M.enable(opts)
api.nvim_win_set_config(ext.wins.cmd, cfg) api.nvim_win_set_config(ext.wins.cmd, cfg)
-- Change message position when 'cmdheight' was or becomes 0. -- Change message position when 'cmdheight' was or becomes 0.
if value == 0 or ext.cmdheight == 0 then if value == 0 or ext.cmdheight == 0 then
ext.cfg.msg.pos = value == 0 and 'box' or ext.cmdheight == 0 and 'cmd' ext.cfg.msg.target = value == 0 and 'msg' or 'cmd'
ext.msg.prev_msg = '' ext.msg.prev_msg = ''
end end
ext.cmdheight = value ext.cmdheight = value

View File

@@ -4,7 +4,7 @@ local api, fn = vim.api, vim.fn
local M = { local M = {
highlighter = nil, ---@type vim.treesitter.highlighter? highlighter = nil, ---@type vim.treesitter.highlighter?
indent = 0, -- Current indent for block event. indent = 0, -- Current indent for block event.
prompt = false, -- Whether a prompt is active; messages are placed in the 'prompt' window. prompt = false, -- Whether a prompt is active; messages are placed in the 'dialog' window.
row = 0, -- Current row in the cmdline buffer, > 0 for block events. row = 0, -- Current row in the cmdline buffer, > 0 for block events.
level = -1, -- Current cmdline level, 0 when inactive, -1 one loop iteration after closing. level = -1, -- Current cmdline level, 0 when inactive, -1 one loop iteration after closing.
} }
@@ -69,7 +69,7 @@ function M.cmdline_show(content, pos, firstc, prompt, indent, level, hl_id)
M.cmdline_pos(pos) M.cmdline_pos(pos)
-- Clear message cmdline state; should not be shown during, and reset after cmdline. -- Clear message cmdline state; should not be shown during, and reset after cmdline.
if ext.cfg.msg.pos == 'cmd' and ext.msg.cmd.msg_row ~= -1 then if ext.cfg.msg.target == 'cmd' and ext.msg.cmd.msg_row ~= -1 then
ext.msg.prev_msg, ext.msg.dupe, ext.msg.cmd.msg_row = '', 0, -1 ext.msg.prev_msg, ext.msg.dupe, ext.msg.cmd.msg_row = '', 0, -1
api.nvim_buf_clear_namespace(ext.bufs.cmd, ext.ns, 0, -1) api.nvim_buf_clear_namespace(ext.bufs.cmd, ext.ns, 0, -1)
ext.msg.virt.msg = { {}, {} } ext.msg.virt.msg = { {}, {} }
@@ -129,7 +129,8 @@ function M.cmdline_hide(_, abort)
-- loop iteration. E.g. when a non-choice confirm button is pressed. -- loop iteration. E.g. when a non-choice confirm button is pressed.
if was_prompt and not M.prompt then if was_prompt and not M.prompt then
api.nvim_buf_set_lines(ext.bufs.cmd, 0, -1, false, {}) api.nvim_buf_set_lines(ext.bufs.cmd, 0, -1, false, {})
api.nvim_win_set_config(ext.wins.prompt, { hide = true }) api.nvim_buf_set_lines(ext.bufs.dialog, 0, -1, false, {})
api.nvim_win_set_config(ext.wins.dialog, { hide = true })
end end
-- Messages emitted as a result of a typed command are treated specially: -- Messages emitted as a result of a typed command are treated specially:
-- remember if the cmdline was used this event loop iteration. -- remember if the cmdline was used this event loop iteration.

View File

@@ -3,13 +3,13 @@ local ext = require('vim._extui.shared')
---@class vim._extui.messages ---@class vim._extui.messages
local M = { local M = {
-- Message box window. Used for regular messages with 'cmdheight' == 0 or, -- Message window. Used for regular messages with 'cmdheight' == 0 or,
-- cfg.msg.pos == 'box'. Also used for verbose messages regardless of -- cfg.msg.target == 'msg'. Also used for verbose messages regardless of
-- cfg.msg.pos. Automatically resizes to the text dimensions up to a point, -- cfg.msg.target. Automatically resizes to the text dimensions up to a point,
-- at which point only the most recent messages will fit and be shown. -- at which point only the most recent messages will fit and be shown.
-- A timer is started for each message whose callback will remove the message -- A timer is started for each message whose callback will remove the message
-- from the window again. -- from the window again.
box = { msg = {
count = 0, -- Number of messages currently in the message window. count = 0, -- Number of messages currently in the message window.
width = 1, -- Current width of the message window. width = 1, -- Current width of the message window.
timer = nil, ---@type uv.uv_timer_t Timer that removes the most recent message. timer = nil, ---@type uv.uv_timer_t Timer that removes the most recent message.
@@ -36,12 +36,12 @@ local M = {
}, },
} }
function M.box:close() function M.msg:close()
self.width, M.virt.msg = 1, { {}, {} } self.width, M.virt.msg = 1, { {}, {} }
M.prev_msg = ext.cfg.msg.pos == 'box' and '' or M.prev_msg M.prev_msg = ext.cfg.msg.target == 'msg' and '' or M.prev_msg
api.nvim_buf_clear_namespace(ext.bufs.box, -1, 0, -1) api.nvim_buf_clear_namespace(ext.bufs.msg, -1, 0, -1)
if api.nvim_win_is_valid(ext.wins.box) then if api.nvim_win_is_valid(ext.wins.msg) then
api.nvim_win_set_config(ext.wins.box, { hide = true }) api.nvim_win_set_config(ext.wins.msg, { hide = true })
end end
end end
@@ -49,20 +49,20 @@ end
--- ---
---@param buf integer Buffer the message was written to. ---@param buf integer Buffer the message was written to.
---@param len integer Number of rows that should be removed. ---@param len integer Number of rows that should be removed.
function M.box:start_timer(buf, len) function M.msg:start_timer(buf, len)
self.timer = vim.defer_fn(function() self.timer = vim.defer_fn(function()
if self.count == 0 or not api.nvim_buf_is_valid(buf) then if self.count == 0 or not api.nvim_buf_is_valid(buf) then
return -- Messages moved to more or buffer was closed. return -- Messages moved to pager or buffer was closed.
end end
api.nvim_buf_set_lines(buf, 0, len, false, {}) api.nvim_buf_set_lines(buf, 0, len, false, {})
self.count = self.count - 1 self.count = self.count - 1
-- Resize or hide message box for removed message. -- Resize or hide message window for removed message.
if self.count > 0 then if self.count > 0 then
M.set_pos('box') M.set_pos('msg')
else else
self:close() self:close()
end end
end, ext.cfg.msg.box.timeout) end, ext.cfg.msg.timeout)
end end
--- Place or delete a virtual text mark in the cmdline or message window. --- Place or delete a virtual text mark in the cmdline or message window.
@@ -88,11 +88,11 @@ local function set_virttext(type)
M.virt.ids[type] = nil M.virt.ids[type] = nil
M.cmd.last_col = type == 'last' and o.columns or M.cmd.last_col M.cmd.last_col = type == 'last' and o.columns or M.cmd.last_col
elseif #chunks > 0 then elseif #chunks > 0 then
local tar = type == 'msg' and ext.cfg.msg.pos or 'cmd' local tar = type == 'msg' and ext.cfg.msg.target or 'cmd'
local win = ext.wins[tar] local win = ext.wins[tar]
local max = api.nvim_win_get_height(win) local max = api.nvim_win_get_height(win)
local erow = tar == 'cmd' and M.cmd.msg_row or nil local erow = tar == 'cmd' and M.cmd.msg_row or nil
local srow = tar == 'box' and fn.line('w0', ext.wins.box) - 1 or nil local srow = tar == 'msg' and fn.line('w0', ext.wins.msg) - 1 or nil
local h = api.nvim_win_text_height(win, { start_row = srow, end_row = erow, max_height = max }) local h = api.nvim_win_text_height(win, { start_row = srow, end_row = erow, max_height = max })
local row = h.end_row ---@type integer local row = h.end_row ---@type integer
local col = fn.virtcol2col(win, row + 1, h.end_vcol) local col = fn.virtcol2col(win, row + 1, h.end_vcol)
@@ -102,17 +102,17 @@ local function set_virttext(type)
-- Calculate at which column to place the virt_text such that it is at the end -- Calculate at which column to place the virt_text such that it is at the end
-- of the last visible message line, overlapping the message text if necessary, -- of the last visible message line, overlapping the message text if necessary,
-- but not overlapping the 'last' virt_text. -- but not overlapping the 'last' virt_text.
local offset = tar ~= 'box' and 0 local offset = tar ~= 'msg' and 0
or api.nvim_win_get_position(win)[2] + (api.nvim_win_get_config(win).border and 1 or 0) or api.nvim_win_get_position(win)[2] + (api.nvim_win_get_config(win).border and 1 or 0)
-- Check if adding the virt_text on this line will exceed the current 'box' width. -- Check if adding the virt_text on this line will exceed the current window width.
local boxwidth = math.max(M.box.width, math.min(o.columns, scol - offset + width)) local maxwidth = math.max(M.msg.width, math.min(o.columns, scol - offset + width))
if tar == 'box' and api.nvim_win_get_width(win) < boxwidth then if tar == 'msg' and api.nvim_win_get_width(win) < maxwidth then
api.nvim_win_set_width(win, boxwidth) api.nvim_win_set_width(win, maxwidth)
M.box.width = boxwidth M.msg.width = maxwidth
end end
local mwidth = tar == 'box' and M.box.width or M.cmd.last_col local mwidth = tar == 'msg' and M.msg.width or M.cmd.last_col
if scol - offset + width > mwidth then if scol - offset + width > mwidth then
col = fn.virtcol2col(win, row + 1, h.end_vcol - (scol - offset + width - mwidth)) col = fn.virtcol2col(win, row + 1, h.end_vcol - (scol - offset + width - mwidth))
end end
@@ -174,45 +174,45 @@ end
-- We need to keep track of the current message column to be able to -- We need to keep track of the current message column to be able to
-- append or overwrite messages for :echon or carriage returns. -- append or overwrite messages for :echon or carriage returns.
local col, will_more, hlopts = 0, false, { undo_restore = false, invalidate = true, priority = 1 } local col, will_pager, hlopts = 0, false, { undo_restore = false, invalidate = true, priority = 1 }
--- Move message to more buffer, appending if window was already open. --- Move message to pager, appending if window was already open.
local function msg_to_more(tar) local function msg_to_pager(tar)
if will_more then if will_pager then
return return
end end
will_more, M.prev_msg = true, '' will_pager, M.prev_msg = true, ''
vim.schedule(function() vim.schedule(function()
local hidden = api.nvim_win_get_config(ext.wins.more).hide local hidden = api.nvim_win_get_config(ext.wins.pager).hide
local marks = api.nvim_buf_get_extmarks(ext.bufs[tar], -1, 0, -1, { details = true }) local marks = api.nvim_buf_get_extmarks(ext.bufs[tar], -1, 0, -1, { details = true })
local lines = api.nvim_buf_get_lines(ext.bufs[tar], 0, -1, false) local lines = api.nvim_buf_get_lines(ext.bufs[tar], 0, -1, false)
api.nvim_buf_set_lines(ext.bufs.more, hidden and 0 or -1, -1, false, lines) api.nvim_buf_set_lines(ext.bufs.pager, hidden and 0 or -1, -1, false, lines)
local rows = api.nvim_buf_line_count(ext.bufs.more) - #lines local rows = api.nvim_buf_line_count(ext.bufs.pager) - #lines
api.nvim_buf_set_lines(ext.bufs[tar], 0, -1, false, {}) api.nvim_buf_set_lines(ext.bufs[tar], 0, -1, false, {})
for _, mark in ipairs(marks) do for _, mark in ipairs(marks) do
hlopts.end_col, hlopts.hl_group = mark[4].end_col, mark[4].hl_group hlopts.end_col, hlopts.hl_group = mark[4].end_col, mark[4].hl_group
api.nvim_buf_set_extmark(ext.bufs.more, ext.ns, mark[2] + rows, mark[3], hlopts) api.nvim_buf_set_extmark(ext.bufs.pager, ext.ns, mark[2] + rows, mark[3], hlopts)
end end
M.box:close() M.msg:close()
M.set_pos('more') M.set_pos('pager')
if not hidden then if not hidden then
api.nvim_command('norm! G') api.nvim_command('norm! G')
end end
M[tar].count, col, will_more = 0, 0, false M[tar].count, col, will_pager = 0, 0, false
end) end)
end end
---@param tar 'box'|'cmd'|'more'|'prompt' ---@param tar 'cmd'|'dialog'|'msg'|'pager'
---@param content MsgContent ---@param content MsgContent
---@param replace_last boolean ---@param replace_last boolean
---@param append boolean ---@param append boolean
---@param more boolean? If true, route messages that exceed the target window to more window. ---@param pager boolean? If true, route messages that exceed the target window to the pager.
function M.show_msg(tar, content, replace_last, append, more) function M.show_msg(tar, content, replace_last, append, pager)
local msg, restart, cr, dupe, count = '', false, false, 0, 0 local msg, restart, cr, dupe, count = '', false, false, 0, 0
append = append and col > 0 append = append and col > 0
if M[tar] then -- tar == 'box'|'cmd' if M[tar] then -- tar == 'cmd'|'msg'
if tar == ext.cfg.msg.pos then if tar == ext.cfg.msg.target then
-- Save the concatenated message to identify repeated messages. -- Save the concatenated message to identify repeated messages.
for _, chunk in ipairs(content) do for _, chunk in ipairs(content) do
msg = msg .. chunk[2] msg = msg .. chunk[2]
@@ -233,10 +233,10 @@ function M.show_msg(tar, content, replace_last, append, more)
local line_count = api.nvim_buf_line_count(ext.bufs[tar]) local line_count = api.nvim_buf_line_count(ext.bufs[tar])
---@type integer Start row after last line in the target buffer, unless ---@type integer Start row after last line in the target buffer, unless
---this is the first message, or in case of a repeated or replaced message. ---this is the first message, or in case of a repeated or replaced message.
local row = M[tar] and count <= 1 and not will_more and (tar == 'cmd' and ext.cmd.row or 0) local row = M[tar] and count <= 1 and not will_pager and (tar == 'cmd' and ext.cmd.row or 0)
or line_count - ((replace_last or restart or cr or append) and 1 or 0) or line_count - ((replace_last or restart or cr or append) and 1 or 0)
local curline = (cr or append) and api.nvim_buf_get_lines(ext.bufs[tar], row, row + 1, false)[1] local curline = (cr or append) and api.nvim_buf_get_lines(ext.bufs[tar], row, row + 1, false)[1]
local start_row, width = row, M.box.width local start_row, width = row, M.msg.width
col = append and not cr and math.min(col, #curline) or 0 col = append and not cr and math.min(col, #curline) or 0
-- Accumulate to be inserted and highlighted message chunks for a non-repeated message. -- Accumulate to be inserted and highlighted message chunks for a non-repeated message.
@@ -254,7 +254,7 @@ function M.show_msg(tar, content, replace_last, append, more)
api.nvim_buf_set_text(ext.bufs[tar], row, col, row, ecol, { repl }) api.nvim_buf_set_text(ext.bufs[tar], row, col, row, ecol, { repl })
end end
curline = api.nvim_buf_get_lines(ext.bufs[tar], row, row + 1, false)[1] curline = api.nvim_buf_get_lines(ext.bufs[tar], row, row + 1, false)[1]
width = tar == 'box' and math.max(width, api.nvim_strwidth(curline)) or 0 width = tar == 'msg' and math.max(width, api.nvim_strwidth(curline)) or 0
if chunk[3] > 0 then if chunk[3] > 0 then
hlopts.end_col, hlopts.hl_group = end_col, chunk[3] hlopts.end_col, hlopts.hl_group = end_col, chunk[3]
@@ -269,22 +269,22 @@ function M.show_msg(tar, content, replace_last, append, more)
end end
end end
if tar == 'box' then if tar == 'msg' then
api.nvim_win_set_width(ext.wins.box, width) api.nvim_win_set_width(ext.wins.msg, width)
local h = api.nvim_win_text_height(ext.wins.box, { start_row = start_row }) local h = api.nvim_win_text_height(ext.wins.msg, { start_row = start_row })
if more and h.all > 1 then if pager and h.all > 1 then
msg_to_more(tar) msg_to_pager(tar)
return return
end end
M.set_pos('box') M.set_pos('msg')
M.box.width = width M.msg.width = width
if restart then if restart then
M.box.timer:stop() M.msg.timer:stop()
M.box.timer:set_repeat(4000) M.msg.timer:set_repeat(4000)
M.box.timer:again() M.msg.timer:again()
else else
M.box:start_timer(ext.bufs.box, row - start_row + 1) M.msg:start_timer(ext.bufs.msg, row - start_row + 1)
end end
elseif tar == 'cmd' and dupe == 0 then elseif tar == 'cmd' and dupe == 0 then
fn.clearmatches(ext.wins.cmd) -- Clear matchparen highlights. fn.clearmatches(ext.wins.cmd) -- Clear matchparen highlights.
@@ -296,8 +296,9 @@ function M.show_msg(tar, content, replace_last, append, more)
api.nvim__redraw({ flush = true, cursor = true, win = ext.wins.cmd }) api.nvim__redraw({ flush = true, cursor = true, win = ext.wins.cmd })
else else
local h = api.nvim_win_text_height(ext.wins.cmd, {}) local h = api.nvim_win_text_height(ext.wins.cmd, {})
if (more or not api.nvim_win_get_config(ext.wins.cmd).hide) and h.all > ext.cmdheight then local want_pager = pager or will_pager or not api.nvim_win_get_config(ext.wins.pager).hide
msg_to_more(tar) if want_pager and h.all > ext.cmdheight then
msg_to_pager(tar)
return return
end end
@@ -312,7 +313,7 @@ function M.show_msg(tar, content, replace_last, append, more)
if M[tar] then if M[tar] then
-- Place (x) indicator for repeated messages. Mainly to mitigate unnecessary -- Place (x) indicator for repeated messages. Mainly to mitigate unnecessary
-- resizing of the message box window, but also placed in the cmdline. -- resizing of the message window, but also placed in the cmdline.
M.virt.msg[M.virt.idx.dupe][1] = dupe > 0 and { 0, ('(%d)'):format(dupe) } or nil M.virt.msg[M.virt.idx.dupe][1] = dupe > 0 and { 0, ('(%d)'):format(dupe) } or nil
M.prev_msg, M.dupe, M[tar].count = msg, dupe, count M.prev_msg, M.dupe, M[tar].count = msg, dupe, count
set_virttext('msg') set_virttext('msg')
@@ -350,16 +351,15 @@ function M.msg_show(kind, content, _, _, append)
vim.api.nvim_feedkeys(vim.keycode('<CR>'), 'n', false) vim.api.nvim_feedkeys(vim.keycode('<CR>'), 'n', false)
elseif kind == 'verbose' then elseif kind == 'verbose' then
-- Verbose messages are sent too often to be meaningful in the cmdline: -- Verbose messages are sent too often to be meaningful in the cmdline:
-- always route to box regardless of cfg.msg.pos. -- always route to message window regardless of cfg.msg.target.
M.show_msg('box', content, false, append) M.show_msg('msg', content, false, append)
elseif ext.cmd.prompt then elseif ext.cmd.prompt then
-- Route to prompt that stays open so long as the cmdline prompt is active. -- Route to dialog that stays open so long as the cmdline prompt is active.
api.nvim_buf_set_lines(ext.bufs.prompt, 0, -1, false, { '' }) M.show_msg('dialog', content, api.nvim_win_get_config(ext.wins.dialog).hide, append)
M.show_msg('prompt', content, true, append) M.set_pos('dialog')
M.set_pos('prompt')
else else
-- Set the entered search command in the cmdline (if available). -- Set the entered search command in the cmdline (if available).
local tar = kind == 'search_cmd' and 'cmd' or ext.cfg.msg.pos local tar = kind == 'search_cmd' and 'cmd' or ext.cfg.msg.target
if tar == 'cmd' then if tar == 'cmd' then
if ext.cmdheight == 0 or (ext.cmd.level > 0 and ext.cmd.row == 0) then if ext.cmdheight == 0 or (ext.cmd.level > 0 and ext.cmd.row == 0) then
return -- Do not overwrite an active cmdline unless in block mode. return -- Do not overwrite an active cmdline unless in block mode.
@@ -372,10 +372,10 @@ function M.msg_show(kind, content, _, _, append)
M.msg_showcmd({}) M.msg_showcmd({})
end end
-- Typed "inspection" messages should be routed to the more window. -- Typed "inspection" messages should be routed to the pager.
local typed_more = { 'echo', 'echomsg', 'lua_print' } local inspect = { 'echo', 'echomsg', 'lua_print' }
local more = kind == 'list_cmd' or (ext.cmd.level >= 0 and vim.tbl_contains(typed_more, kind)) local pager = kind == 'list_cmd' or (ext.cmd.level >= 0 and vim.tbl_contains(inspect, kind))
M.show_msg(tar, content, replace_bufwrite, append, more) M.show_msg(tar, content, replace_bufwrite, append, pager)
-- Replace message for every second bufwrite message. -- Replace message for every second bufwrite message.
replace_bufwrite = not replace_bufwrite and kind == 'bufwrite' replace_bufwrite = not replace_bufwrite and kind == 'bufwrite'
-- Don't remember search_cmd message as actual message. -- Don't remember search_cmd message as actual message.
@@ -415,7 +415,7 @@ function M.msg_ruler(content)
end end
---@alias MsgHistory [string, MsgContent] ---@alias MsgHistory [string, MsgContent]
--- Open the message history in the more window. --- Open the message history in the pager.
--- ---
---@param entries MsgHistory[] ---@param entries MsgHistory[]
function M.msg_history_show(entries) function M.msg_history_show(entries)
@@ -423,19 +423,19 @@ function M.msg_history_show(entries)
return return
end end
api.nvim_buf_set_lines(ext.bufs.more, 0, -1, false, {}) api.nvim_buf_set_lines(ext.bufs.pager, 0, -1, false, {})
for i, entry in ipairs(entries) do for i, entry in ipairs(entries) do
M.show_msg('more', entry[2], i == 1, false) M.show_msg('pager', entry[2], i == 1, false)
end end
M.set_pos('more') M.set_pos('pager')
end end
function M.msg_history_clear() end function M.msg_history_clear() end
--- Adjust dimensions of the message windows after certain events. --- Adjust dimensions of the message windows after certain events.
--- ---
---@param type? 'box'|'cmd'|'more'|'prompt' Type of to be positioned window (nil for all). ---@param type? 'cmd'|'dialog'|'msg'|'pager' Type of to be positioned window (nil for all).
function M.set_pos(type) function M.set_pos(type)
local function win_set_pos(win) local function win_set_pos(win)
local texth = type and api.nvim_win_text_height(win, {}) or 0 local texth = type and api.nvim_win_text_height(win, {}) or 0
@@ -444,18 +444,18 @@ function M.set_pos(type)
hide = false, hide = false,
relative = 'laststatus', relative = 'laststatus',
height = height, height = height,
row = win == ext.wins.box and 0 or 1, row = win == ext.wins.msg and 0 or 1,
col = 10000, col = 10000,
} }
if type == 'box' then if type == 'msg' then
-- Ensure last line is visible and first line is at top of window. -- Ensure last line is visible and first line is at top of window.
local row = (texth.all > height and texth.end_row or 0) + 1 local row = (texth.all > height and texth.end_row or 0) + 1
api.nvim_win_set_cursor(ext.wins.box, { row, 0 }) api.nvim_win_set_cursor(ext.wins.msg, { row, 0 })
elseif type == 'more' and api.nvim_win_get_config(win).hide then elseif type == 'pager' and api.nvim_win_get_config(win).hide then
-- Cannot leave the cmdwin to enter the "more" window, so close it. -- Cannot leave the cmdwin to enter the pager, so close it.
-- NOTE: regression w.r.t. the message grid, which allowed this. Resolving -- NOTE: regression w.r.t. the message grid, which allowed this. Resolving
-- that would require somehow bypassing textlock for the "more" window. -- that would require somehow bypassing textlock for the pager.
if fn.getcmdwintype() ~= '' then if fn.getcmdwintype() ~= '' then
api.nvim_command('quit') api.nvim_command('quit')
end end
@@ -465,18 +465,17 @@ function M.set_pos(type)
api.nvim_set_current_win(win) api.nvim_set_current_win(win)
api.nvim_create_autocmd({ 'WinEnter', 'CmdwinEnter', 'CmdwinLeave' }, { api.nvim_create_autocmd({ 'WinEnter', 'CmdwinEnter', 'CmdwinLeave' }, {
callback = function(ev) callback = function(ev)
if ev.event == 'CmdwinEnter' then -- Make pager relative to cmdwin when it is opened, restore when it is closed.
api.nvim_win_set_config(win, { relative = 'win', win = 0, row = 0, col = 0 }) config = ev.event == 'CmdwinLeave' and config
elseif ev.event == 'CmdwinLeave' then or ev.event == 'WinEnter' and { hide = true }
or { relative = 'win', win = 0, row = 0, col = 0 }
if api.nvim_win_is_valid(win) then
api.nvim_win_set_config(win, config) api.nvim_win_set_config(win, config)
else
if api.nvim_win_is_valid(win) then
api.nvim_win_set_config(win, { hide = true })
end
return true
end end
-- Delete autocmd when a window other than the cmdwin is entered.
return ev.event == 'WinEnter'
end, end,
desc = 'Hide inactive "more" window.', desc = 'Hide inactive pager window.',
}) })
end) end)
end end

View File

@@ -5,17 +5,15 @@ local M = {
ns = api.nvim_create_namespace('nvim._ext_ui'), ns = api.nvim_create_namespace('nvim._ext_ui'),
augroup = api.nvim_create_augroup('nvim._ext_ui', {}), augroup = api.nvim_create_augroup('nvim._ext_ui', {}),
cmdheight = -1, -- 'cmdheight' option value set by user. cmdheight = -1, -- 'cmdheight' option value set by user.
wins = { box = -1, cmd = -1, more = -1, prompt = -1 }, wins = { cmd = -1, dialog = -1, msg = -1, pager = -1 },
bufs = { box = -1, cmd = -1, more = -1, prompt = -1 }, bufs = { cmd = -1, dialog = -1, msg = -1, pager = -1 },
cfg = { cfg = {
enable = true, enable = true,
msg = { -- Options related to the message module. msg = { -- Options related to the message module.
---@type 'box'|'cmd' Type of window used to place messages, either in the ---@type 'cmd'|'msg' Where to place regular messages, either in the
---cmdline or in a separate ephemeral message box window. ---cmdline or in a separate ephemeral message window.
pos = 'cmd', target = 'cmd',
box = { -- Options related to the message box window. timeout = 4000, -- Time a message is visible in the message window.
timeout = 4000, -- Time a message is visible.
},
}, },
}, },
} }
@@ -33,7 +31,7 @@ local tab = 0
--- Ensure the various buffers and windows have not been deleted. --- Ensure the various buffers and windows have not been deleted.
function M.tab_check_wins() function M.tab_check_wins()
local curtab = api.nvim_get_current_tabpage() local curtab = api.nvim_get_current_tabpage()
for _, type in ipairs({ 'box', 'cmd', 'more', 'prompt' }) do for _, type in ipairs({ 'cmd', 'dialog', 'msg', 'pager' }) do
local setopt = not api.nvim_buf_is_valid(M.bufs[type]) local setopt = not api.nvim_buf_is_valid(M.bufs[type])
if setopt then if setopt then
M.bufs[type] = api.nvim_create_buf(false, true) M.bufs[type] = api.nvim_create_buf(false, true)
@@ -50,16 +48,16 @@ function M.tab_check_wins()
or not api.nvim_win_get_config(M.wins[type]).zindex -- no longer floating or not api.nvim_win_get_config(M.wins[type]).zindex -- no longer floating
then then
local top = { vim.opt.fcs:get().horiz or o.ambw == 'single' and '' or '-', 'WinSeparator' } local top = { vim.opt.fcs:get().horiz or o.ambw == 'single' and '' or '-', 'WinSeparator' }
local border = (type == 'more' or type == 'prompt') and { '', top, '', '', '', '', '', '' } local border = (type == 'pager' or type == 'dialog') and { '', top, '', '', '', '', '', '' }
local cfg = vim.tbl_deep_extend('force', wincfg, { local cfg = vim.tbl_deep_extend('force', wincfg, {
focusable = type == 'more', focusable = type == 'pager',
mouse = type ~= 'cmd' and true or nil, mouse = type ~= 'cmd' and true or nil,
anchor = type ~= 'cmd' and 'SE' or nil, anchor = type ~= 'cmd' and 'SE' or nil,
hide = type ~= 'cmd' or M.cmdheight == 0 or nil, hide = type ~= 'cmd' or M.cmdheight == 0 or nil,
title = type == 'more' and 'Messages' or nil, title = type == 'pager' and 'Pager' or nil,
border = type == 'box' and 'single' or border or 'none', border = type == 'msg' and 'single' or border or 'none',
-- kZIndexMessages < zindex < kZIndexCmdlinePopupMenu (grid_defs.h), 'more' below others. -- kZIndexMessages < zindex < kZIndexCmdlinePopupMenu (grid_defs.h), pager below others.
zindex = 200 - (type == 'more' and 1 or 0), zindex = 200 - (type == 'pager' and 1 or 0),
_cmdline_offset = type == 'cmd' and 0 or nil, _cmdline_offset = type == 'cmd' and 0 or nil,
}) })
if tab ~= curtab and api.nvim_win_is_valid(M.wins[type]) then if tab ~= curtab and api.nvim_win_is_valid(M.wins[type]) then
@@ -77,10 +75,11 @@ function M.tab_check_wins()
end end
if setopt then if setopt then
api.nvim_buf_set_name(M.bufs[type], 'nvim.' .. type) local name = { cmd = 'Cmd', dialog = 'Dialog', msg = 'Msg', pager = 'Pager' }
if type == 'more' then api.nvim_buf_set_name(M.bufs[type], ('[%s]'):format(name[type]))
-- Close more window with `q`, same as `checkhealth` if type == 'pager' then
api.nvim_buf_set_keymap(M.bufs.more, 'n', 'q', '<Cmd>wincmd c<CR>', {}) -- Close pager with `q`, same as `checkhealth`
api.nvim_buf_set_keymap(M.bufs.pager, 'n', 'q', '<Cmd>wincmd c<CR>', {})
end end
-- Fire a FileType autocommand with window context to let the user reconfigure local options. -- Fire a FileType autocommand with window context to let the user reconfigure local options.
@@ -88,9 +87,9 @@ function M.tab_check_wins()
api.nvim_set_option_value('wrap', true, { scope = 'local' }) api.nvim_set_option_value('wrap', true, { scope = 'local' })
api.nvim_set_option_value('linebreak', false, { scope = 'local' }) api.nvim_set_option_value('linebreak', false, { scope = 'local' })
api.nvim_set_option_value('smoothscroll', true, { scope = 'local' }) api.nvim_set_option_value('smoothscroll', true, { scope = 'local' })
local ft = type == 'cmd' and 'cmdline' or ('msg' .. type) local ft = name[type]:sub(1, 1):lower() .. name[type]:sub(2)
api.nvim_set_option_value('filetype', ft, { scope = 'local' }) api.nvim_set_option_value('filetype', ft, { scope = 'local' })
local ignore = 'all' .. (type == 'more' and ',-TextYankPost' or '') local ignore = 'all' .. (type == 'pager' and ',-TextYankPost' or '')
api.nvim_set_option_value('eventignorewin', ignore, { scope = 'local' }) api.nvim_set_option_value('eventignorewin', ignore, { scope = 'local' })
end) end)
end end