mirror of
https://github.com/neovim/neovim.git
synced 2026-04-26 09:14:15 +00:00
refactor: rename _extui => _core.ui2 #37692
Problem: _extui module name is confusing and should eventually end up in _core/. Solution: Move it there and name it ui2.
This commit is contained in:
256
runtime/lua/vim/_core/ui2.lua
Normal file
256
runtime/lua/vim/_core/ui2.lua
Normal file
@@ -0,0 +1,256 @@
|
||||
--- @brief
|
||||
---
|
||||
---WARNING: This is an experimental interface intended to replace the message
|
||||
---grid in the TUI.
|
||||
---
|
||||
---To enable the experimental UI (default opts shown):
|
||||
---```lua
|
||||
---require('vim._core.ui2').enable({
|
||||
--- enable = true, -- Whether to enable or disable the UI.
|
||||
--- msg = { -- Options related to the message module.
|
||||
--- ---@type 'cmd'|'msg' Where to place regular messages, either in the
|
||||
--- ---cmdline or in a separate ephemeral message window.
|
||||
--- target = 'cmd',
|
||||
--- timeout = 4000, -- Time a message is visible in the message window.
|
||||
--- },
|
||||
---})
|
||||
---```
|
||||
---
|
||||
---There are four separate window types used by this interface:
|
||||
---- "cmd": The cmdline window; also used for 'showcmd', 'showmode', 'ruler', and
|
||||
--- messages if 'cmdheight' > 0.
|
||||
---- "msg": The message window; used for messages when 'cmdheight' == 0.
|
||||
---- "pager": The pager window; used for |:messages| and certain messages
|
||||
--- that should be shown in full.
|
||||
---- "dialog": The dialog window; used for prompt messages that expect user input.
|
||||
---
|
||||
---These four windows are assigned the "cmd", "msg", "pager" and "dialog"
|
||||
---'filetype' respectively. Use a |FileType| autocommand to configure any local
|
||||
---options for these windows and their respective buffers.
|
||||
---
|
||||
---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
|
||||
---spilled lines. To see the full message, the |g<| command can be used.
|
||||
|
||||
local api = vim.api
|
||||
local M = {
|
||||
ns = api.nvim_create_namespace('nvim.ui2'),
|
||||
augroup = api.nvim_create_augroup('nvim.ui2', {}),
|
||||
cmdheight = vim.o.cmdheight, -- 'cmdheight' option value set by user.
|
||||
redrawing = false, -- True when redrawing to display UI event.
|
||||
wins = { cmd = -1, dialog = -1, msg = -1, pager = -1 },
|
||||
bufs = { cmd = -1, dialog = -1, msg = -1, pager = -1 },
|
||||
cfg = {
|
||||
enable = true,
|
||||
msg = { -- Options related to the message module.
|
||||
---@type 'cmd'|'msg' Where to place regular messages, either in the
|
||||
---cmdline or in a separate ephemeral message window.
|
||||
target = 'cmd',
|
||||
timeout = 4000, -- Time a message is visible in the message window.
|
||||
},
|
||||
},
|
||||
}
|
||||
--- @type vim.api.keyset.win_config
|
||||
local wincfg = { -- Default cfg for nvim_open_win().
|
||||
relative = 'laststatus',
|
||||
style = 'minimal',
|
||||
col = 0,
|
||||
row = 1,
|
||||
width = 10000,
|
||||
height = 1,
|
||||
noautocmd = true,
|
||||
}
|
||||
|
||||
local tab = 0
|
||||
---Ensure target buffers and windows are still valid.
|
||||
function M.check_targets()
|
||||
local curtab = api.nvim_get_current_tabpage()
|
||||
for i, type in ipairs({ 'cmd', 'dialog', 'msg', 'pager' }) do
|
||||
local setopt = not api.nvim_buf_is_valid(M.bufs[type])
|
||||
if setopt then
|
||||
M.bufs[type] = api.nvim_create_buf(false, false)
|
||||
end
|
||||
|
||||
if
|
||||
tab ~= curtab
|
||||
or not api.nvim_win_is_valid(M.wins[type])
|
||||
or not api.nvim_win_get_config(M.wins[type]).zindex -- no longer floating
|
||||
then
|
||||
local cfg = vim.tbl_deep_extend('force', wincfg, {
|
||||
focusable = type == 'pager',
|
||||
mouse = type ~= 'cmd' and true or nil,
|
||||
anchor = type ~= 'cmd' and 'SE' or nil,
|
||||
hide = type ~= 'cmd' or M.cmdheight == 0 or nil,
|
||||
border = type ~= 'msg' and 'none' or nil,
|
||||
-- kZIndexMessages < cmd zindex < kZIndexCmdlinePopupMenu (grid_defs.h), pager below others.
|
||||
zindex = 201 - i,
|
||||
_cmdline_offset = type == 'cmd' and 0 or nil,
|
||||
})
|
||||
if tab ~= curtab and api.nvim_win_is_valid(M.wins[type]) then
|
||||
cfg = api.nvim_win_get_config(M.wins[type])
|
||||
api.nvim_win_close(M.wins[type], true)
|
||||
end
|
||||
M.wins[type] = api.nvim_open_win(M.bufs[type], false, cfg)
|
||||
setopt = true
|
||||
elseif api.nvim_win_get_buf(M.wins[type]) ~= M.bufs[type] then
|
||||
api.nvim_win_set_buf(M.wins[type], M.bufs[type])
|
||||
setopt = true
|
||||
end
|
||||
|
||||
if setopt then
|
||||
-- Set options without firing OptionSet and BufFilePost.
|
||||
vim._with({ win = M.wins[type], noautocmd = true }, function()
|
||||
local ignore = 'all,-FileType' .. (type == 'pager' and ',-TextYankPost' or '')
|
||||
api.nvim_set_option_value('eventignorewin', ignore, { 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('smoothscroll', true, { scope = 'local' })
|
||||
api.nvim_set_option_value('breakindent', false, { scope = 'local' })
|
||||
api.nvim_set_option_value('foldenable', false, { scope = 'local' })
|
||||
api.nvim_set_option_value('showbreak', '', { scope = 'local' })
|
||||
api.nvim_set_option_value('spell', false, { scope = 'local' })
|
||||
api.nvim_set_option_value('swapfile', false, { scope = 'local' })
|
||||
api.nvim_set_option_value('modifiable', true, { scope = 'local' })
|
||||
api.nvim_set_option_value('bufhidden', 'hide', { scope = 'local' })
|
||||
api.nvim_set_option_value('buftype', 'nofile', { scope = 'local' })
|
||||
-- Use MsgArea except in the msg window. Hide Search highlighting except in the pager.
|
||||
local search_hide = 'Search:,CurSearch:,IncSearch:'
|
||||
local hl = 'Normal:MsgArea,' .. search_hide
|
||||
if type == 'pager' then
|
||||
hl = 'Normal:MsgArea'
|
||||
elseif type == 'msg' then
|
||||
hl = search_hide
|
||||
end
|
||||
api.nvim_set_option_value('winhighlight', hl, { scope = 'local' })
|
||||
end)
|
||||
api.nvim_buf_set_name(M.bufs[type], ('[%s]'):format(type:sub(1, 1):upper() .. type:sub(2)))
|
||||
-- Fire FileType with window context to let the user reconfigure local options.
|
||||
vim._with({ win = M.wins[type] }, function()
|
||||
api.nvim_set_option_value('filetype', type, { scope = 'local' })
|
||||
end)
|
||||
|
||||
if type == 'pager' then
|
||||
-- Close pager with `q`, same as `checkhealth`
|
||||
api.nvim_buf_set_keymap(M.bufs.pager, 'n', 'q', '<Cmd>wincmd c<CR>', {})
|
||||
elseif type == M.cfg.msg.target then
|
||||
M.msg.prev_msg = '' -- Will no longer be visible.
|
||||
end
|
||||
end
|
||||
end
|
||||
tab = curtab
|
||||
end
|
||||
|
||||
local function ui_callback(redraw_msg, event, ...)
|
||||
local handler = M.msg[event] or M.cmd[event]
|
||||
M.check_targets()
|
||||
handler(...)
|
||||
-- Cmdline mode, non-fast message and non-empty showcmd require an immediate redraw.
|
||||
if M.cmd[event] or redraw_msg or (event == 'msg_showcmd' and select(1, ...)[1]) then
|
||||
M.redrawing = true
|
||||
api.nvim__redraw({
|
||||
flush = handler ~= M.cmd.cmdline_hide or nil,
|
||||
cursor = handler == M.cmd[event] and true or nil,
|
||||
win = handler == M.cmd[event] and M.wins.cmd or nil,
|
||||
})
|
||||
M.redrawing = false
|
||||
end
|
||||
end
|
||||
local scheduled_ui_callback = vim.schedule_wrap(ui_callback)
|
||||
|
||||
---@nodoc
|
||||
function M.enable(opts)
|
||||
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')
|
||||
vim.validate('opts.msg.target', opts.msg.target, function(tar)
|
||||
return tar == 'cmd' or tar == 'msg'
|
||||
end, "'cmd'|'msg'")
|
||||
end
|
||||
M.cfg = vim.tbl_deep_extend('keep', opts, M.cfg)
|
||||
M.cmd = require('vim._core.ui2.cmdline')
|
||||
M.msg = require('vim._core.ui2.messages')
|
||||
|
||||
if M.cfg.enable == false then
|
||||
-- Detach and cleanup windows, buffers and autocommands.
|
||||
for _, win in pairs(M.wins) do
|
||||
if api.nvim_win_is_valid(win) then
|
||||
api.nvim_win_close(win, true)
|
||||
end
|
||||
end
|
||||
for _, buf in pairs(M.bufs) do
|
||||
if api.nvim_buf_is_valid(buf) then
|
||||
api.nvim_buf_delete(buf, {})
|
||||
end
|
||||
end
|
||||
api.nvim_clear_autocmds({ group = M.augroup })
|
||||
vim.ui_detach(M.ns)
|
||||
return
|
||||
end
|
||||
|
||||
vim.ui_attach(M.ns, { ext_messages = true, set_cmdheight = false }, function(event, ...)
|
||||
if not (M.msg[event] or M.cmd[event]) then
|
||||
return
|
||||
end
|
||||
-- Ensure cmdline is placed after a scheduled message in block mode.
|
||||
if vim.in_fast_event() or (event == 'cmdline_show' and M.cmd.srow > 0) then
|
||||
scheduled_ui_callback(false, event, ...)
|
||||
else
|
||||
ui_callback(event == 'msg_show', event, ...)
|
||||
end
|
||||
return true
|
||||
end)
|
||||
|
||||
-- The visibility and appearance of the cmdline and message window is
|
||||
-- dependent on some option values. Reconfigure windows when option value
|
||||
-- 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.
|
||||
local function check_cmdheight(value)
|
||||
M.check_targets()
|
||||
-- 'cmdheight' set; (un)hide cmdline window and set its height.
|
||||
local cfg = { height = math.max(value, 1), hide = value == 0 }
|
||||
api.nvim_win_set_config(M.wins.cmd, cfg)
|
||||
-- Change message position when 'cmdheight' was or becomes 0.
|
||||
if value == 0 or M.cmdheight == 0 then
|
||||
M.cfg.msg.target = value == 0 and 'msg' or 'cmd'
|
||||
M.msg.prev_msg = ''
|
||||
end
|
||||
M.cmdheight = value
|
||||
end
|
||||
|
||||
if vim.v.vim_did_enter == 0 then
|
||||
vim.schedule(function()
|
||||
check_cmdheight(vim.o.cmdheight)
|
||||
end)
|
||||
end
|
||||
|
||||
api.nvim_create_autocmd('OptionSet', {
|
||||
group = M.augroup,
|
||||
pattern = { 'cmdheight' },
|
||||
callback = function()
|
||||
check_cmdheight(vim.v.option_new)
|
||||
M.msg.set_pos()
|
||||
end,
|
||||
desc = 'Set cmdline and message window dimensions for changed option values.',
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd({ 'VimResized', 'TabEnter' }, {
|
||||
group = M.augroup,
|
||||
callback = function()
|
||||
M.msg.set_pos()
|
||||
end,
|
||||
desc = 'Set cmdline and message window dimensions after shell resize or tabpage change.',
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd('WinEnter', {
|
||||
callback = function()
|
||||
local win = api.nvim_get_current_win()
|
||||
if vim.tbl_contains(M.wins, win) and api.nvim_win_get_config(win).hide then
|
||||
vim.cmd.wincmd('p')
|
||||
end
|
||||
end,
|
||||
desc = 'Make sure hidden UI window is never current.',
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,6 +1,6 @@
|
||||
local ext = require('vim._extui.shared')
|
||||
local ui = require('vim._core.ui2')
|
||||
local api, fn = vim.api, vim.fn
|
||||
---@class vim._extui.cmdline
|
||||
---@class vim._core.ui2.cmdline
|
||||
local M = {
|
||||
highlighter = nil, ---@type vim.treesitter.highlighter?
|
||||
indent = 0, -- Current indent for block event.
|
||||
@@ -17,7 +17,7 @@ local M = {
|
||||
---@param hide boolean Whether to hide or show the window.
|
||||
---@param height integer (Text)height of the cmdline window.
|
||||
local function win_config(win, hide, height)
|
||||
if ext.cmdheight == 0 and api.nvim_win_get_config(win).hide ~= hide then
|
||||
if ui.cmdheight == 0 and api.nvim_win_get_config(win).hide ~= hide then
|
||||
api.nvim_win_set_config(win, { hide = hide, height = not hide and height or nil })
|
||||
elseif api.nvim_win_get_height(win) ~= height then
|
||||
api.nvim_win_set_height(win, height)
|
||||
@@ -28,10 +28,10 @@ local function win_config(win, hide, height)
|
||||
vim._with({ noautocmd = true, o = { splitkeep = 'screen' } }, function()
|
||||
vim.o.cmdheight = height
|
||||
end)
|
||||
ext.msg.set_pos()
|
||||
ui.msg.set_pos()
|
||||
elseif M.wmnumode ~= (M.prompt and fn.pumvisible() == 0 and fn.wildmenumode() or 0) then
|
||||
M.wmnumode = (M.wmnumode == 1 and 0 or 1)
|
||||
ext.msg.set_pos()
|
||||
ui.msg.set_pos()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,7 +53,7 @@ local function set_text(content, prompt)
|
||||
cmdbuff = cmdbuff .. chunk[2]
|
||||
end
|
||||
lines[#lines] = ('%s%s '):format(lines[#lines], fn.strtrans(cmdbuff))
|
||||
api.nvim_buf_set_lines(ext.bufs.cmd, M.srow, -1, false, lines)
|
||||
api.nvim_buf_set_lines(ui.bufs.cmd, M.srow, -1, false, lines)
|
||||
end
|
||||
|
||||
--- Set the cmdline buffer text and cursor position.
|
||||
@@ -67,24 +67,24 @@ end
|
||||
---@param hl_id integer
|
||||
function M.cmdline_show(content, pos, firstc, prompt, indent, level, hl_id)
|
||||
M.level, M.indent, M.prompt = level, indent, M.prompt or #prompt > 0
|
||||
if M.highlighter == nil or M.highlighter.bufnr ~= ext.bufs.cmd then
|
||||
local parser = assert(vim.treesitter.get_parser(ext.bufs.cmd, 'vim', {}))
|
||||
if M.highlighter == nil or M.highlighter.bufnr ~= ui.bufs.cmd then
|
||||
local parser = assert(vim.treesitter.get_parser(ui.bufs.cmd, 'vim', {}))
|
||||
M.highlighter = vim.treesitter.highlighter.new(parser)
|
||||
end
|
||||
-- Only enable TS highlighter for Ex commands (not search or filter commands).
|
||||
M.highlighter.active[ext.bufs.cmd] = firstc == ':' and M.highlighter or nil
|
||||
if ext.msg.cmd.msg_row ~= -1 then
|
||||
ext.msg.msg_clear()
|
||||
M.highlighter.active[ui.bufs.cmd] = firstc == ':' and M.highlighter or nil
|
||||
if ui.msg.cmd.msg_row ~= -1 then
|
||||
ui.msg.msg_clear()
|
||||
end
|
||||
ext.msg.virt.last = { {}, {}, {}, {} }
|
||||
ui.msg.virt.last = { {}, {}, {}, {} }
|
||||
|
||||
set_text(content, ('%s%s%s'):format(firstc, prompt, (' '):rep(indent)))
|
||||
if promptlen > 0 and hl_id > 0 then
|
||||
api.nvim_buf_set_extmark(ext.bufs.cmd, ext.ns, 0, 0, { hl_group = hl_id, end_col = promptlen })
|
||||
api.nvim_buf_set_extmark(ui.bufs.cmd, ui.ns, 0, 0, { hl_group = hl_id, end_col = promptlen })
|
||||
end
|
||||
|
||||
local height = math.max(ext.cmdheight, api.nvim_win_text_height(ext.wins.cmd, {}).all)
|
||||
win_config(ext.wins.cmd, false, height)
|
||||
local height = math.max(ui.cmdheight, api.nvim_win_text_height(ui.wins.cmd, {}).all)
|
||||
win_config(ui.wins.cmd, false, height)
|
||||
M.cmdline_pos(pos)
|
||||
end
|
||||
|
||||
@@ -94,7 +94,7 @@ end
|
||||
---@param shift boolean
|
||||
--@param level integer
|
||||
function M.cmdline_special_char(c, shift)
|
||||
api.nvim_win_call(ext.wins.cmd, function()
|
||||
api.nvim_win_call(ui.wins.cmd, function()
|
||||
api.nvim_put({ c }, shift and '' or 'c', false, false)
|
||||
end)
|
||||
end
|
||||
@@ -110,12 +110,12 @@ function M.cmdline_pos(pos)
|
||||
curpos[1], curpos[2] = M.erow + 1, promptlen + pos
|
||||
-- Add matchparen highlighting to non-prompt part of cmdline.
|
||||
if pos > 0 and fn.exists('#matchparen#CursorMoved') == 1 then
|
||||
api.nvim_win_set_cursor(ext.wins.cmd, { curpos[1], curpos[2] - 1 })
|
||||
vim._with({ win = ext.wins.cmd, wo = { eventignorewin = '' } }, function()
|
||||
api.nvim_win_set_cursor(ui.wins.cmd, { curpos[1], curpos[2] - 1 })
|
||||
vim._with({ win = ui.wins.cmd, wo = { eventignorewin = '' } }, function()
|
||||
api.nvim_exec_autocmds('CursorMoved', {})
|
||||
end)
|
||||
end
|
||||
api.nvim_win_set_cursor(ext.wins.cmd, curpos)
|
||||
api.nvim_win_set_cursor(ui.wins.cmd, curpos)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -128,21 +128,21 @@ function M.cmdline_hide(level, abort)
|
||||
return -- No need to hide when still in nested cmdline or cmdline_block.
|
||||
end
|
||||
|
||||
fn.clearmatches(ext.wins.cmd) -- Clear matchparen highlights.
|
||||
api.nvim_win_set_cursor(ext.wins.cmd, { 1, 0 })
|
||||
fn.clearmatches(ui.wins.cmd) -- Clear matchparen highlights.
|
||||
api.nvim_win_set_cursor(ui.wins.cmd, { 1, 0 })
|
||||
if abort then
|
||||
-- Clear cmd buffer for aborted command (non-abort is left visible).
|
||||
api.nvim_buf_set_lines(ext.bufs.cmd, 0, -1, false, {})
|
||||
api.nvim_buf_set_lines(ui.bufs.cmd, 0, -1, false, {})
|
||||
end
|
||||
|
||||
local clear = vim.schedule_wrap(function(was_prompt)
|
||||
-- Avoid clearing prompt window when it is re-entered before the next event
|
||||
-- loop iteration. E.g. when a non-choice confirm button is pressed.
|
||||
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.dialog, 0, -1, false, {})
|
||||
api.nvim_win_set_config(ext.wins.dialog, { hide = true })
|
||||
vim.on_key(nil, ext.msg.dialog_on_key)
|
||||
api.nvim_buf_set_lines(ui.bufs.cmd, 0, -1, false, {})
|
||||
api.nvim_buf_set_lines(ui.bufs.dialog, 0, -1, false, {})
|
||||
api.nvim_win_set_config(ui.wins.dialog, { hide = true })
|
||||
vim.on_key(nil, ui.msg.dialog_on_key)
|
||||
end
|
||||
-- Messages emitted as a result of a typed command are treated specially:
|
||||
-- remember if the cmdline was used this event loop iteration.
|
||||
@@ -154,7 +154,7 @@ function M.cmdline_hide(level, abort)
|
||||
clear(M.prompt)
|
||||
|
||||
M.prompt, M.level, curpos[1], curpos[2] = false, 0, 0, 0
|
||||
win_config(ext.wins.cmd, true, ext.cmdheight)
|
||||
win_config(ui.wins.cmd, true, ui.cmdheight)
|
||||
end
|
||||
|
||||
--- Set multi-line cmdline buffer text.
|
||||
@@ -1,8 +1,8 @@
|
||||
local api, fn, o = vim.api, vim.fn, vim.o
|
||||
local ext = require('vim._extui.shared')
|
||||
local ui = require('vim._core.ui2')
|
||||
|
||||
---@alias Msg { extid: integer, timer: uv.uv_timer_t? }
|
||||
---@class vim._extui.messages
|
||||
---@class vim._core.ui2.messages
|
||||
local M = {
|
||||
-- Message window. Used for regular messages with 'cmdheight' == 0 or,
|
||||
-- cfg.msg.target == 'msg'. Automatically resizes to the text dimensions up to
|
||||
@@ -40,16 +40,16 @@ local M = {
|
||||
|
||||
local cmd_on_key ---@type integer? Set to vim.on_key namespace while cmdline is expanded.
|
||||
-- An external redraw indicates the start of a new batch of messages in the cmdline.
|
||||
api.nvim_set_decoration_provider(ext.ns, {
|
||||
api.nvim_set_decoration_provider(ui.ns, {
|
||||
on_start = function()
|
||||
M.cmd.ids = (ext.redrawing or cmd_on_key) and M.cmd.ids or {}
|
||||
M.cmd.ids = (ui.redrawing or cmd_on_key) and M.cmd.ids or {}
|
||||
end,
|
||||
})
|
||||
|
||||
function M.msg:close()
|
||||
self.width, M.virt.msg[M.virt.idx.dupe][1] = 1, nil
|
||||
if api.nvim_win_is_valid(ext.wins.msg) then
|
||||
api.nvim_win_set_config(ext.wins.msg, { hide = true })
|
||||
if api.nvim_win_is_valid(ui.wins.msg) then
|
||||
api.nvim_win_set_config(ui.wins.msg, { hide = true })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,18 +63,18 @@ function M.msg:start_timer(buf, id)
|
||||
end
|
||||
self.ids[id].timer = vim.defer_fn(function()
|
||||
local extid = api.nvim_buf_is_valid(buf) and self.ids[id] and self.ids[id].extid
|
||||
local mark = extid and api.nvim_buf_get_extmark_by_id(buf, ext.ns, extid, { details = true })
|
||||
local mark = extid and api.nvim_buf_get_extmark_by_id(buf, ui.ns, extid, { details = true })
|
||||
self.ids[id] = nil
|
||||
if not mark or not mark[1] then
|
||||
return
|
||||
end
|
||||
-- Clear prev_msg when line that may have dupe marker is removed.
|
||||
local erow = api.nvim_buf_line_count(buf) - 1
|
||||
M.prev_msg = ext.cfg.msg.target == 'msg' and mark[3].end_row == erow and '' or M.prev_msg
|
||||
M.prev_msg = ui.cfg.msg.target == 'msg' and mark[3].end_row == erow and '' or M.prev_msg
|
||||
|
||||
-- Remove message (including potentially leftover empty line).
|
||||
api.nvim_buf_set_text(buf, mark[1], mark[2], mark[3].end_row, mark[3].end_col, {})
|
||||
if fn.col({ mark[1] + 1, '$' }, ext.wins.msg) == 1 then
|
||||
if fn.col({ mark[1] + 1, '$' }, ui.wins.msg) == 1 then
|
||||
api.nvim_buf_set_lines(buf, mark[1], mark[1] + 1, false, {})
|
||||
end
|
||||
|
||||
@@ -84,7 +84,7 @@ function M.msg:start_timer(buf, id)
|
||||
else
|
||||
self:close()
|
||||
end
|
||||
end, ext.cfg.msg.timeout)
|
||||
end, ui.cfg.msg.timeout)
|
||||
end
|
||||
|
||||
--- Place or delete a virtual text mark in the cmdline or message window.
|
||||
@@ -92,7 +92,7 @@ end
|
||||
---@param type 'last'|'msg'|'top'|'bot'
|
||||
---@param tar? 'cmd'|'msg'|'dialog'
|
||||
local function set_virttext(type, tar)
|
||||
if (type == 'last' and (ext.cmdheight == 0 or M.virt.delayed)) or cmd_on_key then
|
||||
if (type == 'last' and (ui.cmdheight == 0 or M.virt.delayed)) or cmd_on_key then
|
||||
return -- Don't show virtual text while cmdline is expanded or delaying for error.
|
||||
end
|
||||
|
||||
@@ -105,17 +105,17 @@ local function set_virttext(type, tar)
|
||||
width = width + api.nvim_strwidth(chunk[2])
|
||||
end
|
||||
end
|
||||
tar = tar or type == 'msg' and ext.cfg.msg.target or 'cmd'
|
||||
tar = tar or type == 'msg' and ui.cfg.msg.target or 'cmd'
|
||||
|
||||
if M.virt.ids[type] and #chunks == 0 then
|
||||
api.nvim_buf_del_extmark(ext.bufs[tar], ext.ns, M.virt.ids[type])
|
||||
api.nvim_buf_del_extmark(ui.bufs[tar], ui.ns, M.virt.ids[type])
|
||||
M.cmd.last_col = type == 'last' and o.columns or M.cmd.last_col
|
||||
M.virt.ids[type] = nil
|
||||
elseif #chunks > 0 then
|
||||
local win = ext.wins[tar]
|
||||
local win = ui.wins[tar]
|
||||
local line = (tar == 'msg' or type == 'top') and 'w0' or type == 'bot' and 'w$'
|
||||
local srow = line and fn.line(line, ext.wins.dialog) - 1
|
||||
local erow = tar == 'cmd' and math.min(M.cmd.msg_row, api.nvim_buf_line_count(ext.bufs.cmd) - 1)
|
||||
local srow = line and fn.line(line, ui.wins.dialog) - 1
|
||||
local erow = tar == 'cmd' and math.min(M.cmd.msg_row, api.nvim_buf_line_count(ui.bufs.cmd) - 1)
|
||||
local texth = api.nvim_win_text_height(win, {
|
||||
max_height = (type == 'top' or type == 'bot') and 1 or api.nvim_win_get_height(win),
|
||||
start_row = srow or nil,
|
||||
@@ -147,20 +147,20 @@ local function set_virttext(type, tar)
|
||||
|
||||
-- Give virt_text the same highlight as the message tail.
|
||||
local pos, opts = { row, col }, { details = true, overlap = true, type = 'highlight' }
|
||||
local hl = api.nvim_buf_get_extmarks(ext.bufs[tar], ext.ns, pos, pos, opts)
|
||||
local hl = api.nvim_buf_get_extmarks(ui.bufs[tar], ui.ns, pos, pos, opts)
|
||||
for _, chunk in ipairs(hl[1] and chunks or {}) do
|
||||
chunk[2] = hl[1][4].hl_group
|
||||
end
|
||||
else
|
||||
local mode = #M.virt.last[M.virt.idx.mode]
|
||||
local pad = o.columns - width ---@type integer
|
||||
local newlines = math.max(0, ext.cmdheight - texth.all)
|
||||
local newlines = math.max(0, ui.cmdheight - texth.all)
|
||||
row = row + newlines
|
||||
M.cmd.last_col = mode > 0 and 0 or o.columns - (newlines > 0 and 0 or width)
|
||||
|
||||
if newlines > 0 then
|
||||
-- Add empty lines to place virt_text on the last screen row.
|
||||
api.nvim_buf_set_lines(ext.bufs.cmd, -1, -1, false, fn['repeat']({ '' }, newlines))
|
||||
api.nvim_buf_set_lines(ui.bufs.cmd, -1, -1, false, fn['repeat']({ '' }, newlines))
|
||||
col = 0
|
||||
else
|
||||
if scol > M.cmd.last_col then
|
||||
@@ -179,7 +179,7 @@ local function set_virttext(type, tar)
|
||||
col = vcol <= 0 and 0 or fn.virtcol2col(win, row + 1, vcol)
|
||||
M.prev_msg = mode > 0 and '' or M.prev_msg
|
||||
M.virt.msg = mode > 0 and { {}, {} } or M.virt.msg
|
||||
api.nvim_buf_set_text(ext.bufs.cmd, row, col, row, -1, { mode > 0 and ' ' or '' })
|
||||
api.nvim_buf_set_text(ui.bufs.cmd, row, col, row, -1, { mode > 0 and ' ' or '' })
|
||||
end
|
||||
|
||||
pad = pad - ((mode > 0 or col == 0) and 0 or math.min(M.cmd.last_col, scol))
|
||||
@@ -188,7 +188,7 @@ local function set_virttext(type, tar)
|
||||
set_virttext('msg') -- Readjust to new M.cmd.last_col or clear for mode.
|
||||
end
|
||||
|
||||
M.virt.ids[type] = api.nvim_buf_set_extmark(ext.bufs[tar], ext.ns, row, col, {
|
||||
M.virt.ids[type] = api.nvim_buf_set_extmark(ui.bufs[tar], ui.ns, row, col, {
|
||||
virt_text = chunks,
|
||||
virt_text_pos = 'overlay',
|
||||
right_gravity = false,
|
||||
@@ -205,22 +205,22 @@ local hlopts = { undo_restore = false, invalidate = true, priority = 1 }
|
||||
local function expand_msg(src)
|
||||
-- Copy and clear message from src to enlarged cmdline that is dismissed by any
|
||||
-- key press, or append to pager in case that is already open (not hidden).
|
||||
local hidden = api.nvim_win_get_config(ext.wins.pager).hide
|
||||
local hidden = api.nvim_win_get_config(ui.wins.pager).hide
|
||||
local tar = hidden and 'cmd' or 'pager'
|
||||
if tar ~= src then
|
||||
local srow = hidden and 0 or api.nvim_buf_line_count(ext.bufs.pager)
|
||||
local srow = hidden and 0 or api.nvim_buf_line_count(ui.bufs.pager)
|
||||
local opts = { details = true, type = 'highlight' }
|
||||
local marks = api.nvim_buf_get_extmarks(ext.bufs[src], -1, 0, -1, opts)
|
||||
local lines = api.nvim_buf_get_lines(ext.bufs[src], 0, -1, false)
|
||||
api.nvim_buf_set_lines(ext.bufs[src], 0, -1, false, {})
|
||||
api.nvim_buf_set_lines(ext.bufs[tar], srow, -1, false, lines)
|
||||
local marks = api.nvim_buf_get_extmarks(ui.bufs[src], -1, 0, -1, opts)
|
||||
local lines = api.nvim_buf_get_lines(ui.bufs[src], 0, -1, false)
|
||||
api.nvim_buf_set_lines(ui.bufs[src], 0, -1, false, {})
|
||||
api.nvim_buf_set_lines(ui.bufs[tar], srow, -1, false, lines)
|
||||
for _, mark in ipairs(marks) do
|
||||
hlopts.end_col, hlopts.hl_group = mark[4].end_col, mark[4].hl_group
|
||||
api.nvim_buf_set_extmark(ext.bufs[tar], ext.ns, srow + mark[2], mark[3], hlopts)
|
||||
api.nvim_buf_set_extmark(ui.bufs[tar], ui.ns, srow + mark[2], mark[3], hlopts)
|
||||
end
|
||||
|
||||
if tar == 'cmd' and ext.cmd.highlighter then
|
||||
ext.cmd.highlighter.active[ext.bufs.cmd] = nil
|
||||
if tar == 'cmd' and ui.cmd.highlighter then
|
||||
ui.cmd.highlighter.active[ui.bufs.cmd] = nil
|
||||
elseif tar == 'pager' then
|
||||
api.nvim_command('norm! G')
|
||||
end
|
||||
@@ -230,7 +230,7 @@ local function expand_msg(src)
|
||||
M.msg:close()
|
||||
else
|
||||
for _, id in pairs(M.virt.ids) do
|
||||
api.nvim_buf_del_extmark(ext.bufs.cmd, ext.ns, id)
|
||||
api.nvim_buf_del_extmark(ui.bufs.cmd, ui.ns, id)
|
||||
end
|
||||
end
|
||||
M.set_pos(tar)
|
||||
@@ -246,25 +246,25 @@ local cmd_timer ---@type uv.uv_timer_t? Timer resetting cmdline state next event
|
||||
---@param append boolean
|
||||
---@param id integer|string
|
||||
function M.show_msg(tar, content, replace_last, append, id)
|
||||
local mark, msg, cr, dupe, buf = {}, '', false, 0, ext.bufs[tar]
|
||||
local mark, msg, cr, dupe, buf = {}, '', false, 0, ui.bufs[tar]
|
||||
|
||||
if M[tar] then -- tar == 'cmd'|'msg'
|
||||
local extid = M[tar].ids[id] and M[tar].ids[id].extid
|
||||
if tar == ext.cfg.msg.target then
|
||||
if tar == ui.cfg.msg.target then
|
||||
-- Save the concatenated message to identify repeated messages.
|
||||
for _, chunk in ipairs(content) do
|
||||
msg = msg .. chunk[2]
|
||||
end
|
||||
dupe = (not extid and msg == M.prev_msg and ext.cmd.srow == 0 and M.dupe + 1 or 0)
|
||||
dupe = (not extid and msg == M.prev_msg and ui.cmd.srow == 0 and M.dupe + 1 or 0)
|
||||
end
|
||||
|
||||
cr = next(M[tar].ids) ~= nil and msg:sub(1, 1) == '\r'
|
||||
replace_last = next(M[tar].ids) ~= nil and not extid and (replace_last or dupe > 0)
|
||||
extid = extid or replace_last and M[tar].ids[M.prev_id] and M[tar].ids[M.prev_id].extid
|
||||
mark = extid and api.nvim_buf_get_extmark_by_id(buf, ext.ns, extid, { details = true }) or {}
|
||||
mark = extid and api.nvim_buf_get_extmark_by_id(buf, ui.ns, extid, { details = true }) or {}
|
||||
|
||||
-- Ensure cmdline is clear when writing the first message.
|
||||
if tar == 'cmd' and dupe == 0 and not next(M.cmd.ids) and ext.cmd.srow == 0 then
|
||||
if tar == 'cmd' and dupe == 0 and not next(M.cmd.ids) and ui.cmd.srow == 0 then
|
||||
api.nvim_buf_set_lines(buf, 0, -1, false, {})
|
||||
end
|
||||
end
|
||||
@@ -278,7 +278,7 @@ function M.show_msg(tar, content, replace_last, append, id)
|
||||
---@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.
|
||||
local row = mark[1]
|
||||
or (M[tar] and not next(M[tar].ids) and ext.cmd.srow == 0 and 0)
|
||||
or (M[tar] and not next(M[tar].ids) and ui.cmd.srow == 0 and 0)
|
||||
or (line_count - ((replace_last or cr or append) and 1 or 0))
|
||||
local curline = (cr or append) and api.nvim_buf_get_lines(buf, row, row + 1, false)[1]
|
||||
local start_row, width = row, M.msg.width
|
||||
@@ -307,7 +307,7 @@ function M.show_msg(tar, content, replace_last, append, id)
|
||||
|
||||
if chunk[3] > 0 then
|
||||
hlopts.end_col, hlopts.hl_group = end_col, chunk[3]
|
||||
api.nvim_buf_set_extmark(buf, ext.ns, row, col, hlopts)
|
||||
api.nvim_buf_set_extmark(buf, ui.ns, row, col, hlopts)
|
||||
end
|
||||
|
||||
if pat == '\n' then
|
||||
@@ -322,12 +322,12 @@ function M.show_msg(tar, content, replace_last, append, id)
|
||||
-- Keep track of message span to replace by ID.
|
||||
local opts = { end_row = row, end_col = col, invalidate = true, undo_restore = false }
|
||||
M[tar].ids[id] = M[tar].ids[id] or {}
|
||||
M[tar].ids[id].extid = api.nvim_buf_set_extmark(buf, ext.ns, start_row, start_col, opts)
|
||||
M[tar].ids[id].extid = api.nvim_buf_set_extmark(buf, ui.ns, start_row, start_col, opts)
|
||||
end
|
||||
|
||||
if tar == 'msg' then
|
||||
api.nvim_win_set_width(ext.wins.msg, width)
|
||||
local texth = api.nvim_win_text_height(ext.wins.msg, { start_row = start_row, end_row = row })
|
||||
api.nvim_win_set_width(ui.wins.msg, width)
|
||||
local texth = api.nvim_win_text_height(ui.wins.msg, { start_row = start_row, end_row = row })
|
||||
if texth.all > math.ceil(o.lines * 0.5) then
|
||||
expand_msg(tar)
|
||||
else
|
||||
@@ -336,23 +336,23 @@ function M.show_msg(tar, content, replace_last, append, id)
|
||||
M.msg:start_timer(buf, id)
|
||||
end
|
||||
elseif tar == 'cmd' and dupe == 0 then
|
||||
fn.clearmatches(ext.wins.cmd) -- Clear matchparen highlights.
|
||||
if ext.cmd.srow > 0 then
|
||||
fn.clearmatches(ui.wins.cmd) -- Clear matchparen highlights.
|
||||
if ui.cmd.srow > 0 then
|
||||
-- In block mode the cmdheight is already dynamic, so just print the full message
|
||||
-- regardless of height. Put cmdline below message.
|
||||
ext.cmd.srow = row + 1
|
||||
ui.cmd.srow = row + 1
|
||||
else
|
||||
api.nvim_win_set_cursor(ext.wins.cmd, { 1, 0 }) -- ensure first line is visible
|
||||
if ext.cmd.highlighter then
|
||||
ext.cmd.highlighter.active[buf] = nil
|
||||
api.nvim_win_set_cursor(ui.wins.cmd, { 1, 0 }) -- ensure first line is visible
|
||||
if ui.cmd.highlighter then
|
||||
ui.cmd.highlighter.active[buf] = nil
|
||||
end
|
||||
-- Place [+x] indicator for lines that spill over 'cmdheight'.
|
||||
local texth = api.nvim_win_text_height(ext.wins.cmd, {})
|
||||
local spill = texth.all > ext.cmdheight and (' [+%d]'):format(texth.all - ext.cmdheight)
|
||||
local texth = api.nvim_win_text_height(ui.wins.cmd, {})
|
||||
local spill = texth.all > ui.cmdheight and (' [+%d]'):format(texth.all - ui.cmdheight)
|
||||
M.virt.msg[M.virt.idx.spill][1] = spill and { 0, spill } or nil
|
||||
M.cmd.msg_row = texth.end_row
|
||||
|
||||
if texth.all > ext.cmdheight then
|
||||
if texth.all > ui.cmdheight then
|
||||
expand_msg(tar)
|
||||
end
|
||||
end
|
||||
@@ -387,7 +387,7 @@ end
|
||||
function M.msg_show(kind, content, replace_last, _, append, id)
|
||||
if kind == 'empty' then
|
||||
-- A sole empty message clears the cmdline.
|
||||
if ext.cfg.msg.target == 'cmd' and not next(M.cmd.ids) and ext.cmd.srow == 0 then
|
||||
if ui.cfg.msg.target == 'cmd' and not next(M.cmd.ids) and ui.cmd.srow == 0 then
|
||||
M.msg_clear()
|
||||
end
|
||||
elseif kind == 'search_count' then
|
||||
@@ -398,20 +398,20 @@ function M.msg_show(kind, content, replace_last, _, append, id)
|
||||
M.virt.last[M.virt.idx.search] = content
|
||||
M.virt.last[M.virt.idx.cmd] = { { 0, (' '):rep(11) } }
|
||||
set_virttext('last')
|
||||
elseif (ext.cmd.prompt or kind == 'wildlist') and ext.cmd.srow == 0 then
|
||||
elseif (ui.cmd.prompt or kind == 'wildlist') and ui.cmd.srow == 0 then
|
||||
-- Route to dialog that stays open so long as the cmdline prompt is active.
|
||||
replace_last = api.nvim_win_get_config(ext.wins.dialog).hide or kind == 'wildlist'
|
||||
replace_last = api.nvim_win_get_config(ui.wins.dialog).hide or kind == 'wildlist'
|
||||
if kind == 'wildlist' then
|
||||
api.nvim_buf_set_lines(ext.bufs.dialog, 0, -1, false, {})
|
||||
ext.cmd.prompt = true -- Ensure dialog is closed when cmdline is hidden.
|
||||
api.nvim_buf_set_lines(ui.bufs.dialog, 0, -1, false, {})
|
||||
ui.cmd.prompt = true -- Ensure dialog is closed when cmdline is hidden.
|
||||
end
|
||||
M.show_msg('dialog', content, replace_last, append, id)
|
||||
M.set_pos('dialog')
|
||||
else
|
||||
-- Set the entered search command in the cmdline (if available).
|
||||
local tar = kind == 'search_cmd' and 'cmd' or ext.cfg.msg.target
|
||||
local tar = kind == 'search_cmd' and 'cmd' or ui.cfg.msg.target
|
||||
if tar == 'cmd' then
|
||||
if ext.cmdheight == 0 or (ext.cmd.level > 0 and ext.cmd.srow == 0) then
|
||||
if ui.cmdheight == 0 or (ui.cmd.level > 0 and ui.cmd.srow == 0) then
|
||||
return -- Do not overwrite an active cmdline unless in block mode.
|
||||
end
|
||||
-- Store the time when an important message was emitted in order to not overwrite
|
||||
@@ -431,10 +431,10 @@ end
|
||||
|
||||
---Clear currently visible messages.
|
||||
function M.msg_clear()
|
||||
api.nvim_buf_set_lines(ext.bufs.cmd, 0, -1, false, {})
|
||||
api.nvim_buf_set_lines(ext.bufs.msg, 0, -1, false, {})
|
||||
api.nvim_win_set_config(ext.wins.msg, { hide = true })
|
||||
M[ext.cfg.msg.target].ids, M.dupe, M.cmd.msg_row, M.msg.width = {}, 0, -1, 1
|
||||
api.nvim_buf_set_lines(ui.bufs.cmd, 0, -1, false, {})
|
||||
api.nvim_buf_set_lines(ui.bufs.msg, 0, -1, false, {})
|
||||
api.nvim_win_set_config(ui.wins.msg, { hide = true })
|
||||
M[ui.cfg.msg.target].ids, M.dupe, M.cmd.msg_row, M.msg.width = {}, 0, -1, 1
|
||||
M.prev_msg, M.virt.msg = '', { {}, {} }
|
||||
end
|
||||
|
||||
@@ -442,7 +442,7 @@ end
|
||||
---
|
||||
---@param content MsgContent
|
||||
function M.msg_showmode(content)
|
||||
M.virt.last[M.virt.idx.mode] = ext.cmd.level > 0 and {} or content
|
||||
M.virt.last[M.virt.idx.mode] = ui.cmd.level > 0 and {} or content
|
||||
M.virt.last[M.virt.idx.search] = {}
|
||||
set_virttext('last')
|
||||
end
|
||||
@@ -461,7 +461,7 @@ end
|
||||
---
|
||||
---@param content MsgContent
|
||||
function M.msg_ruler(content)
|
||||
M.virt.last[M.virt.idx.ruler] = ext.cmd.level > 0 and {} or content
|
||||
M.virt.last[M.virt.idx.ruler] = ui.cmd.level > 0 and {} or content
|
||||
set_virttext('last')
|
||||
end
|
||||
|
||||
@@ -483,7 +483,7 @@ function M.msg_history_show(entries, prev_cmd)
|
||||
M.msg_clear()
|
||||
end
|
||||
|
||||
api.nvim_buf_set_lines(ext.bufs.pager, 0, -1, false, {})
|
||||
api.nvim_buf_set_lines(ui.bufs.pager, 0, -1, false, {})
|
||||
for i, entry in ipairs(entries) do
|
||||
M.show_msg('pager', entry[2], i == 1, entry[3], 0)
|
||||
end
|
||||
@@ -500,10 +500,10 @@ function M.set_pos(type)
|
||||
local texth = type and api.nvim_win_text_height(win, {}) or {}
|
||||
local top = { vim.opt.fcs:get().msgsep or ' ', 'MsgSeparator' }
|
||||
cfg.height = type and math.min(texth.all, math.ceil(o.lines * 0.5))
|
||||
cfg.border = win ~= ext.wins.msg and { '', top, '', '', '', '', '', '' } or nil
|
||||
cfg.border = win ~= ui.wins.msg and { '', top, '', '', '', '', '', '' } or nil
|
||||
cfg.focusable = type == 'cmd' or nil
|
||||
cfg.row = (win == ext.wins.msg and 0 or 1) - ext.cmd.wmnumode
|
||||
cfg.row = cfg.row - ((win == ext.wins.pager and o.laststatus == 3) and 1 or 0)
|
||||
cfg.row = (win == ui.wins.msg and 0 or 1) - ui.cmd.wmnumode
|
||||
cfg.row = cfg.row - ((win == ui.wins.pager and o.laststatus == 3) and 1 or 0)
|
||||
api.nvim_win_set_config(win, cfg)
|
||||
|
||||
if type == 'cmd' and not cmd_on_key then
|
||||
@@ -518,32 +518,32 @@ function M.set_pos(type)
|
||||
return
|
||||
end
|
||||
vim.schedule(function()
|
||||
local entered = api.nvim_get_current_win() == ext.wins.cmd
|
||||
local entered = api.nvim_get_current_win() == ui.wins.cmd
|
||||
cmd_on_key = nil
|
||||
if api.nvim_win_is_valid(ext.wins.cmd) then
|
||||
api.nvim_win_close(ext.wins.cmd, true)
|
||||
if api.nvim_win_is_valid(ui.wins.cmd) then
|
||||
api.nvim_win_close(ui.wins.cmd, true)
|
||||
end
|
||||
ext.check_targets()
|
||||
ui.check_targets()
|
||||
-- Show or clear the message depending on if the pager was opened.
|
||||
if entered or not api.nvim_win_get_config(ext.wins.pager).hide then
|
||||
if entered or not api.nvim_win_get_config(ui.wins.pager).hide then
|
||||
M.virt.msg[M.virt.idx.spill][1] = nil
|
||||
api.nvim_buf_set_lines(ext.bufs.cmd, 0, -1, false, {})
|
||||
api.nvim_buf_set_lines(ui.bufs.cmd, 0, -1, false, {})
|
||||
if entered then
|
||||
api.nvim_command('norm! g<') -- User entered the cmdline window: open the pager.
|
||||
end
|
||||
elseif ext.cfg.msg.target == 'cmd' and ext.cmd.level <= 0 then
|
||||
ext.check_targets()
|
||||
elseif ui.cfg.msg.target == 'cmd' and ui.cmd.level <= 0 then
|
||||
ui.check_targets()
|
||||
set_virttext('msg')
|
||||
end
|
||||
api.nvim__redraw({ flush = true }) -- NOTE: redundant unless cmdline was opened.
|
||||
end)
|
||||
vim.on_key(nil, ext.ns)
|
||||
end, ext.ns)
|
||||
vim.on_key(nil, ui.ns)
|
||||
end, ui.ns)
|
||||
elseif type == 'dialog' then
|
||||
-- Add virtual [+x] text to indicate scrolling is possible.
|
||||
local function set_top_bot_spill()
|
||||
local topspill = fn.line('w0', ext.wins.dialog) - 1
|
||||
local botspill = api.nvim_buf_line_count(ext.bufs.dialog) - fn.line('w$', ext.wins.dialog)
|
||||
local topspill = fn.line('w0', ui.wins.dialog) - 1
|
||||
local botspill = api.nvim_buf_line_count(ui.bufs.dialog) - fn.line('w$', ui.wins.dialog)
|
||||
M.virt.top[1][1] = topspill > 0 and { 0, (' [+%d]'):format(topspill) } or nil
|
||||
set_virttext('top', 'dialog')
|
||||
M.virt.bot[1][1] = botspill > 0 and { 0, (' [+%d]'):format(botspill) } or nil
|
||||
@@ -567,17 +567,17 @@ function M.set_pos(type)
|
||||
f = [[\<C-F>]],
|
||||
b = [[\<C-B>]],
|
||||
}
|
||||
local info = page_keys[key] and fn.getwininfo(ext.wins.dialog)[1]
|
||||
if info and (key ~= 'f' or info.botline < api.nvim_buf_line_count(ext.bufs.dialog)) then
|
||||
fn.win_execute(ext.wins.dialog, ('exe "norm! %s"'):format(page_keys[key]))
|
||||
local info = page_keys[key] and fn.getwininfo(ui.wins.dialog)[1]
|
||||
if info and (key ~= 'f' or info.botline < api.nvim_buf_line_count(ui.bufs.dialog)) then
|
||||
fn.win_execute(ui.wins.dialog, ('exe "norm! %s"'):format(page_keys[key]))
|
||||
set_top_bot_spill()
|
||||
return fn.getwininfo(ext.wins.dialog)[1].topline ~= info.topline and '' or nil
|
||||
return fn.getwininfo(ui.wins.dialog)[1].topline ~= info.topline and '' or nil
|
||||
end
|
||||
end)
|
||||
elseif type == 'msg' then
|
||||
-- Ensure last line is visible and first line is at top of window.
|
||||
local row = (texth.all > cfg.height and texth.end_row or 0) + 1
|
||||
api.nvim_win_set_cursor(ext.wins.msg, { row, 0 })
|
||||
api.nvim_win_set_cursor(ui.wins.msg, { row, 0 })
|
||||
elseif type == 'pager' then
|
||||
if fn.getcmdwintype() ~= '' then
|
||||
-- Cannot leave the cmdwin to enter the pager, so close it.
|
||||
@@ -588,15 +588,15 @@ function M.set_pos(type)
|
||||
|
||||
-- Cmdwin is actually closed one event iteration later so schedule in case it was open.
|
||||
vim.schedule(function()
|
||||
api.nvim_set_current_win(ext.wins.pager)
|
||||
api.nvim_set_current_win(ui.wins.pager)
|
||||
-- Make pager relative to cmdwin when it is opened, restore when it is closed.
|
||||
api.nvim_create_autocmd({ 'WinEnter', 'CmdwinEnter', 'CmdwinLeave' }, {
|
||||
callback = function(ev)
|
||||
if api.nvim_win_is_valid(ext.wins.pager) then
|
||||
if api.nvim_win_is_valid(ui.wins.pager) then
|
||||
local config = ev.event == 'CmdwinLeave' and cfg
|
||||
or ev.event == 'WinEnter' and { hide = true }
|
||||
or { relative = 'win', win = 0, row = 0, col = 0 }
|
||||
api.nvim_win_set_config(ext.wins.pager, config)
|
||||
api.nvim_win_set_config(ui.wins.pager, config)
|
||||
end
|
||||
return ev.event == 'WinEnter'
|
||||
end,
|
||||
@@ -606,7 +606,7 @@ function M.set_pos(type)
|
||||
end
|
||||
end
|
||||
|
||||
for t, win in pairs(ext.wins) do
|
||||
for t, win in pairs(ui.wins) do
|
||||
local cfg = (t == type or (type == nil and t ~= 'cmd'))
|
||||
and api.nvim_win_is_valid(win)
|
||||
and api.nvim_win_get_config(win)
|
||||
@@ -1,152 +0,0 @@
|
||||
--- @brief
|
||||
---
|
||||
---WARNING: This is an experimental interface intended to replace the message
|
||||
---grid in the TUI.
|
||||
---
|
||||
---To enable the experimental UI (default opts shown):
|
||||
---```lua
|
||||
---require('vim._extui').enable({
|
||||
--- enable = true, -- Whether to enable or disable the UI.
|
||||
--- msg = { -- Options related to the message module.
|
||||
--- ---@type 'cmd'|'msg' Where to place regular messages, either in the
|
||||
--- ---cmdline or in a separate ephemeral message window.
|
||||
--- target = 'cmd',
|
||||
--- timeout = 4000, -- Time a message is visible in the message window.
|
||||
--- },
|
||||
---})
|
||||
---```
|
||||
---
|
||||
---There are four separate window types used by this interface:
|
||||
---- "cmd": The cmdline window; also used for 'showcmd', 'showmode', 'ruler', and
|
||||
--- messages if 'cmdheight' > 0.
|
||||
---- "msg": The message window; used for messages when 'cmdheight' == 0.
|
||||
---- "pager": The pager window; used for |:messages| and certain messages
|
||||
--- that should be shown in full.
|
||||
---- "dialog": The dialog window; used for prompt messages that expect user input.
|
||||
---
|
||||
---These four windows are assigned the "cmd", "msg", "pager" and "dialog"
|
||||
---'filetype' respectively. Use a |FileType| autocommand to configure any local
|
||||
---options for these windows and their respective buffers.
|
||||
---
|
||||
---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
|
||||
---spilled lines. To see the full message, the |g<| command can be used.
|
||||
|
||||
local api = vim.api
|
||||
local ext = require('vim._extui.shared')
|
||||
ext.msg = require('vim._extui.messages')
|
||||
ext.cmd = require('vim._extui.cmdline')
|
||||
local M = {}
|
||||
|
||||
local function ui_callback(redraw_msg, event, ...)
|
||||
local handler = ext.msg[event] or ext.cmd[event]
|
||||
ext.check_targets()
|
||||
handler(...)
|
||||
-- Cmdline mode, non-fast message and non-empty showcmd require an immediate redraw.
|
||||
if ext.cmd[event] or redraw_msg or (event == 'msg_showcmd' and select(1, ...)[1]) then
|
||||
ext.redrawing = true
|
||||
api.nvim__redraw({
|
||||
flush = handler ~= ext.cmd.cmdline_hide or nil,
|
||||
cursor = handler == ext.cmd[event] and true or nil,
|
||||
win = handler == ext.cmd[event] and ext.wins.cmd or nil,
|
||||
})
|
||||
ext.redrawing = false
|
||||
end
|
||||
end
|
||||
local scheduled_ui_callback = vim.schedule_wrap(ui_callback)
|
||||
|
||||
---@nodoc
|
||||
function M.enable(opts)
|
||||
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')
|
||||
vim.validate('opts.msg.target', opts.msg.target, function(tar)
|
||||
return tar == 'cmd' or tar == 'msg'
|
||||
end, "'cmd'|'msg'")
|
||||
end
|
||||
ext.cfg = vim.tbl_deep_extend('keep', opts, ext.cfg)
|
||||
|
||||
if ext.cfg.enable == false then
|
||||
-- Detach and cleanup windows, buffers and autocommands.
|
||||
for _, win in pairs(ext.wins) do
|
||||
if api.nvim_win_is_valid(win) then
|
||||
api.nvim_win_close(win, true)
|
||||
end
|
||||
end
|
||||
for _, buf in pairs(ext.bufs) do
|
||||
if api.nvim_buf_is_valid(buf) then
|
||||
api.nvim_buf_delete(buf, {})
|
||||
end
|
||||
end
|
||||
api.nvim_clear_autocmds({ group = ext.augroup })
|
||||
vim.ui_detach(ext.ns)
|
||||
return
|
||||
end
|
||||
|
||||
vim.ui_attach(ext.ns, { ext_messages = true, set_cmdheight = false }, function(event, ...)
|
||||
if not (ext.msg[event] or ext.cmd[event]) then
|
||||
return
|
||||
end
|
||||
-- Ensure cmdline is placed after a scheduled message in block mode.
|
||||
if vim.in_fast_event() or (event == 'cmdline_show' and ext.cmd.srow > 0) then
|
||||
scheduled_ui_callback(false, event, ...)
|
||||
else
|
||||
ui_callback(event == 'msg_show', event, ...)
|
||||
end
|
||||
return true
|
||||
end)
|
||||
|
||||
-- The visibility and appearance of the cmdline and message window is
|
||||
-- dependent on some option values. Reconfigure windows when option value
|
||||
-- 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.
|
||||
local function check_cmdheight(value)
|
||||
ext.check_targets()
|
||||
-- 'cmdheight' set; (un)hide cmdline window and set its height.
|
||||
local cfg = { height = math.max(value, 1), hide = value == 0 }
|
||||
api.nvim_win_set_config(ext.wins.cmd, cfg)
|
||||
-- Change message position when 'cmdheight' was or becomes 0.
|
||||
if value == 0 or ext.cmdheight == 0 then
|
||||
ext.cfg.msg.target = value == 0 and 'msg' or 'cmd'
|
||||
ext.msg.prev_msg = ''
|
||||
end
|
||||
ext.cmdheight = value
|
||||
end
|
||||
|
||||
if vim.v.vim_did_enter == 0 then
|
||||
vim.schedule(function()
|
||||
check_cmdheight(vim.o.cmdheight)
|
||||
end)
|
||||
end
|
||||
|
||||
api.nvim_create_autocmd('OptionSet', {
|
||||
group = ext.augroup,
|
||||
pattern = { 'cmdheight' },
|
||||
callback = function()
|
||||
check_cmdheight(vim.v.option_new)
|
||||
ext.msg.set_pos()
|
||||
end,
|
||||
desc = 'Set cmdline and message window dimensions for changed option values.',
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd({ 'VimResized', 'TabEnter' }, {
|
||||
group = ext.augroup,
|
||||
callback = function()
|
||||
ext.msg.set_pos()
|
||||
end,
|
||||
desc = 'Set cmdline and message window dimensions after shell resize or tabpage change.',
|
||||
})
|
||||
|
||||
api.nvim_create_autocmd('WinEnter', {
|
||||
callback = function()
|
||||
local win = api.nvim_get_current_win()
|
||||
if vim.tbl_contains(ext.wins, win) and api.nvim_win_get_config(win).hide then
|
||||
vim.cmd.wincmd('p')
|
||||
end
|
||||
end,
|
||||
desc = 'Make sure hidden extui window is never current.',
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,111 +0,0 @@
|
||||
local api = vim.api
|
||||
local M = {
|
||||
msg = nil, ---@type vim._extui.messages
|
||||
cmd = nil, ---@type vim._extui.cmdline
|
||||
ns = api.nvim_create_namespace('nvim._ext_ui'),
|
||||
augroup = api.nvim_create_augroup('nvim._ext_ui', {}),
|
||||
cmdheight = vim.o.cmdheight, -- 'cmdheight' option value set by user.
|
||||
redrawing = false, -- True when redrawing to display UI event.
|
||||
wins = { cmd = -1, dialog = -1, msg = -1, pager = -1 },
|
||||
bufs = { cmd = -1, dialog = -1, msg = -1, pager = -1 },
|
||||
cfg = {
|
||||
enable = true,
|
||||
msg = { -- Options related to the message module.
|
||||
---@type 'cmd'|'msg' Where to place regular messages, either in the
|
||||
---cmdline or in a separate ephemeral message window.
|
||||
target = 'cmd',
|
||||
timeout = 4000, -- Time a message is visible in the message window.
|
||||
},
|
||||
},
|
||||
}
|
||||
--- @type vim.api.keyset.win_config
|
||||
local wincfg = { -- Default cfg for nvim_open_win().
|
||||
relative = 'laststatus',
|
||||
style = 'minimal',
|
||||
col = 0,
|
||||
row = 1,
|
||||
width = 10000,
|
||||
height = 1,
|
||||
noautocmd = true,
|
||||
}
|
||||
|
||||
local tab = 0
|
||||
---Ensure target buffers and windows are still valid.
|
||||
function M.check_targets()
|
||||
local curtab = api.nvim_get_current_tabpage()
|
||||
for i, type in ipairs({ 'cmd', 'dialog', 'msg', 'pager' }) do
|
||||
local setopt = not api.nvim_buf_is_valid(M.bufs[type])
|
||||
if setopt then
|
||||
M.bufs[type] = api.nvim_create_buf(false, false)
|
||||
end
|
||||
|
||||
if
|
||||
tab ~= curtab
|
||||
or not api.nvim_win_is_valid(M.wins[type])
|
||||
or not api.nvim_win_get_config(M.wins[type]).zindex -- no longer floating
|
||||
then
|
||||
local cfg = vim.tbl_deep_extend('force', wincfg, {
|
||||
focusable = type == 'pager',
|
||||
mouse = type ~= 'cmd' and true or nil,
|
||||
anchor = type ~= 'cmd' and 'SE' or nil,
|
||||
hide = type ~= 'cmd' or M.cmdheight == 0 or nil,
|
||||
border = type ~= 'msg' and 'none' or nil,
|
||||
-- kZIndexMessages < cmd zindex < kZIndexCmdlinePopupMenu (grid_defs.h), pager below others.
|
||||
zindex = 201 - i,
|
||||
_cmdline_offset = type == 'cmd' and 0 or nil,
|
||||
})
|
||||
if tab ~= curtab and api.nvim_win_is_valid(M.wins[type]) then
|
||||
cfg = api.nvim_win_get_config(M.wins[type])
|
||||
api.nvim_win_close(M.wins[type], true)
|
||||
end
|
||||
M.wins[type] = api.nvim_open_win(M.bufs[type], false, cfg)
|
||||
setopt = true
|
||||
elseif api.nvim_win_get_buf(M.wins[type]) ~= M.bufs[type] then
|
||||
api.nvim_win_set_buf(M.wins[type], M.bufs[type])
|
||||
setopt = true
|
||||
end
|
||||
|
||||
if setopt then
|
||||
-- Set options without firing OptionSet and BufFilePost.
|
||||
vim._with({ win = M.wins[type], noautocmd = true }, function()
|
||||
local ignore = 'all,-FileType' .. (type == 'pager' and ',-TextYankPost' or '')
|
||||
api.nvim_set_option_value('eventignorewin', ignore, { 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('smoothscroll', true, { scope = 'local' })
|
||||
api.nvim_set_option_value('breakindent', false, { scope = 'local' })
|
||||
api.nvim_set_option_value('foldenable', false, { scope = 'local' })
|
||||
api.nvim_set_option_value('showbreak', '', { scope = 'local' })
|
||||
api.nvim_set_option_value('spell', false, { scope = 'local' })
|
||||
api.nvim_set_option_value('swapfile', false, { scope = 'local' })
|
||||
api.nvim_set_option_value('modifiable', true, { scope = 'local' })
|
||||
api.nvim_set_option_value('bufhidden', 'hide', { scope = 'local' })
|
||||
api.nvim_set_option_value('buftype', 'nofile', { scope = 'local' })
|
||||
-- Use MsgArea except in the msg window. Hide Search highlighting except in the pager.
|
||||
local search_hide = 'Search:,CurSearch:,IncSearch:'
|
||||
local hl = 'Normal:MsgArea,' .. search_hide
|
||||
if type == 'pager' then
|
||||
hl = 'Normal:MsgArea'
|
||||
elseif type == 'msg' then
|
||||
hl = search_hide
|
||||
end
|
||||
api.nvim_set_option_value('winhighlight', hl, { scope = 'local' })
|
||||
end)
|
||||
api.nvim_buf_set_name(M.bufs[type], ('[%s]'):format(type:sub(1, 1):upper() .. type:sub(2)))
|
||||
-- Fire FileType with window context to let the user reconfigure local options.
|
||||
vim._with({ win = M.wins[type] }, function()
|
||||
api.nvim_set_option_value('filetype', type, { scope = 'local' })
|
||||
end)
|
||||
|
||||
if type == 'pager' then
|
||||
-- Close pager with `q`, same as `checkhealth`
|
||||
api.nvim_buf_set_keymap(M.bufs.pager, 'n', 'q', '<Cmd>wincmd c<CR>', {})
|
||||
elseif type == M.cfg.msg.target then
|
||||
M.msg.prev_msg = '' -- Will no longer be visible.
|
||||
end
|
||||
end
|
||||
end
|
||||
tab = curtab
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user