mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 03:18:16 +00:00

Problem: We have an unmaintained Vimscript parser and cmdline highlighting mechanism, with which it is hard to leverage the treesitter highlighter. Long messages result in a hit-enter-prompt. Solution: Implement a vim.ui_attach() UI, that replaces the message grid (orphaning some 3000+ LOC core C code). Introduce an experimental vim._extui module, because removing the message grid at the same time is too risky. The new UI leverages the bundled treesitter highlighter and parser for Vimscript, as well as the matchparen plugin, to highlight the cmdline. Messages are truncated in the cmdline area, or placed in a floating message box in the bottom right corner. Special ("list_cmd") messages and the message history are shown in a, "more prompt" (now a fully interactive regular window). Various default UI elements ('showcmd', 'ruler') are also placed in the cmdline area, as virtual text. `require('vim._extui').enable({})` enables the experimental UI. `{ msg.pos = 'box' }` or `:set cmdheight=0` enables the message box variant. Followup: - Come to a consensus for how best to represent messages (by default). - Start removing message grid when this is deemed a successful replacement. When that is finished, make this new UI the default and update a lot of tests.
130 lines
4.2 KiB
Lua
130 lines
4.2 KiB
Lua
--- @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 'box'|'cmd' Type of window used to place messages, either in the
|
|
--- ---cmdline or in a separate message box window with ephemeral messages.
|
|
--- pos = 'cmd',
|
|
--- box = { -- Options related to the message box window.
|
|
--- timeout = 4000, -- Time a message is visible.
|
|
--- },
|
|
--- },
|
|
---})
|
|
---```
|
|
|
|
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(event, ...)
|
|
local handler = ext.msg[event] or ext.cmd[event]
|
|
if not handler then
|
|
return
|
|
end
|
|
ext.tab_check_wins()
|
|
handler(...)
|
|
api.nvim__redraw({
|
|
flush = true,
|
|
cursor = handler == ext.cmd[event] and true or nil,
|
|
win = handler == ext.cmd[event] and ext.wins[ext.tab].cmd or nil,
|
|
})
|
|
end
|
|
local scheduled_ui_callback = vim.schedule_wrap(ui_callback)
|
|
|
|
---@nodoc
|
|
function M.enable(opts)
|
|
vim.validate('opts', opts, 'table', true)
|
|
ext.cfg = vim.tbl_deep_extend('keep', opts, ext.cfg)
|
|
|
|
if ext.cfg.enable == false then
|
|
-- Detach and cleanup windows, buffers and autocommands.
|
|
for _, tab in ipairs(api.nvim_list_tabpages()) do
|
|
for _, win in pairs(ext.wins[tab] or {}) do
|
|
api.nvim_win_close(win, true)
|
|
end
|
|
end
|
|
for _, buf in pairs(ext.bufs) do
|
|
api.nvim_buf_delete(buf, {})
|
|
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 vim.in_fast_event() then
|
|
scheduled_ui_callback(event, ...)
|
|
else
|
|
ui_callback(event, ...)
|
|
end
|
|
end)
|
|
|
|
api.nvim_set_hl(ext.ns, 'Normal', { 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, 'IncSearch', { link = 'MsgArea' })
|
|
|
|
-- The visibility and appearance of the cmdline and message box window is
|
|
-- dependent on some option values. Reconfigure windows when option value
|
|
-- has changed and after VimEnter when the user configured value is known.
|
|
local function check_opt(name, value)
|
|
if name == 'cmdheight' then
|
|
-- 'cmdheight' set; (un)hide cmdline window and set its height.
|
|
ext.cmdheight = value
|
|
ext.cfg.msg.pos = ext.cmdheight == 0 and 'box' or ext.cfg.msg.pos
|
|
local cfg = { height = math.max(ext.cmdheight, 1), hide = ext.cmdheight == 0 }
|
|
api.nvim_win_set_config(ext.wins[ext.tab].cmd, cfg)
|
|
elseif name == 'termguicolors' then
|
|
-- 'termguicolors' toggled; add or remove border and set 'winblend' for box windows.
|
|
for _, tab in ipairs(api.nvim_list_tabpages()) do
|
|
api.nvim_win_set_config(ext.wins[tab].box, { border = value and 'none' or 'single' })
|
|
vim.wo[ext.wins[tab].box].winblend = value and 30 or 0
|
|
end
|
|
end
|
|
end
|
|
|
|
api.nvim_create_autocmd('OptionSet', {
|
|
group = ext.augroup,
|
|
pattern = { 'cmdheight', 'termguicolors' },
|
|
callback = function(ev)
|
|
ext.tab_check_wins()
|
|
check_opt(ev.match, vim.v.option_new)
|
|
ext.msg.set_pos()
|
|
end,
|
|
desc = 'Set cmdline and message window dimensions for changed option values.',
|
|
})
|
|
|
|
api.nvim_create_autocmd({ 'VimEnter', 'VimResized' }, {
|
|
group = ext.augroup,
|
|
callback = function(ev)
|
|
ext.tab_check_wins()
|
|
if ev.event == 'VimEnter' then
|
|
check_opt('cmdheight', vim.o.cmdheight)
|
|
check_opt('termguicolors', vim.o.termguicolors)
|
|
end
|
|
ext.msg.set_pos()
|
|
end,
|
|
desc = 'Set cmdline and message window dimensions after startup and shell resize.',
|
|
})
|
|
|
|
api.nvim_create_autocmd('WinEnter', {
|
|
callback = function()
|
|
local win = api.nvim_get_current_win()
|
|
if vim.tbl_contains(ext.wins[ext.tab] or {}, 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
|