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.
159 lines
5.2 KiB
Lua
159 lines
5.2 KiB
Lua
local ext = require('vim._extui.shared')
|
|
local api, fn = vim.api, vim.fn
|
|
---@class vim._extui.cmdline
|
|
local M = {
|
|
highlighter = nil, ---@type vim.treesitter.highlighter?
|
|
indent = 0, -- Current indent for block event.
|
|
prompt = false, -- Whether a prompt is active; messages are placed in the 'prompt' window.
|
|
row = 0, -- Current row in the cmdline buffer, > 0 for block events.
|
|
}
|
|
|
|
--- Set the 'cmdheight' and cmdline window height. Reposition message windows.
|
|
---
|
|
---@param win integer Cmdline window in the current tabpage.
|
|
---@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
|
|
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)
|
|
end
|
|
if vim.o.cmdheight ~= height then
|
|
vim.cmd('noautocmd set cmdheight=' .. height)
|
|
ext.msg.set_pos()
|
|
end
|
|
end
|
|
|
|
local promptlen = 0 -- Current length of the prompt, stored for use in "cmdline_pos"
|
|
--- Concatenate content chunks and set the text for the current row in the cmdline buffer.
|
|
---
|
|
---@alias CmdChunk [integer, string]
|
|
---@alias CmdContent CmdChunk[]
|
|
---@param content CmdContent
|
|
---@param prompt string
|
|
local function set_text(content, prompt)
|
|
promptlen = #prompt
|
|
for _, chunk in ipairs(content) do
|
|
prompt = prompt .. chunk[2]
|
|
end
|
|
api.nvim_buf_set_lines(ext.bufs.cmd, M.row, -1, false, { prompt .. ' ' })
|
|
end
|
|
|
|
--- Set the cmdline buffer text and cursor position.
|
|
---
|
|
---@param content CmdContent
|
|
---@param pos integer
|
|
---@param firstc string
|
|
---@param prompt string
|
|
---@param indent integer
|
|
--@param level integer
|
|
---@param hl_id integer
|
|
function M.cmdline_show(content, pos, firstc, prompt, indent, _, hl_id)
|
|
M.indent, M.prompt = indent, #prompt > 0
|
|
-- Only enable TS highlighter for Ex commands (not search or filter commands).
|
|
M.highlighter.active[ext.bufs.cmd] = firstc == ':' and M.highlighter or nil
|
|
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 })
|
|
end
|
|
|
|
local height = math.max(ext.cmdheight, api.nvim_win_text_height(ext.wins[ext.tab].cmd, {}).all)
|
|
win_config(ext.wins[ext.tab].cmd, false, height)
|
|
M.cmdline_pos(pos)
|
|
|
|
-- 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
|
|
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)
|
|
ext.msg.virt.msg = { {}, {} }
|
|
end
|
|
ext.msg.virt.last = { {}, {}, {}, {} }
|
|
end
|
|
|
|
--- Insert special character at cursor position.
|
|
---
|
|
---@param c string
|
|
---@param shift boolean
|
|
--@param level integer
|
|
function M.cmdline_special_char(c, shift)
|
|
api.nvim_win_call(ext.wins[ext.tab].cmd, function()
|
|
api.nvim_put({ c }, shift and '' or 'c', false, false)
|
|
end)
|
|
end
|
|
|
|
local curpos = { 0, 0 } -- Last drawn cursor position.
|
|
--- Set the cmdline cursor position.
|
|
---
|
|
---@param pos integer
|
|
--@param level integer
|
|
function M.cmdline_pos(pos)
|
|
if curpos[1] ~= M.row + 1 or curpos[2] ~= promptlen + pos then
|
|
curpos[1], curpos[2] = M.row + 1, promptlen + pos
|
|
-- Add matchparen highlighting to non-prompt part of cmdline.
|
|
if pos > 0 and fn.exists('#matchparen') then
|
|
api.nvim_win_set_cursor(ext.wins[ext.tab].cmd, { curpos[1], curpos[2] - 1 })
|
|
vim.wo[ext.wins[ext.tab].cmd].eventignorewin = ''
|
|
fn.win_execute(ext.wins[ext.tab].cmd, 'doautocmd CursorMoved')
|
|
vim.wo[ext.wins[ext.tab].cmd].eventignorewin = 'all'
|
|
end
|
|
api.nvim_win_set_cursor(ext.wins[ext.tab].cmd, curpos)
|
|
end
|
|
end
|
|
|
|
--- Leaving the cmdline, restore 'cmdheight' and 'ruler'.
|
|
---
|
|
--@param level integer
|
|
---@param abort boolean
|
|
function M.cmdline_hide(_, abort)
|
|
if M.row > 0 then
|
|
return -- No need to hide when still in cmdline_block.
|
|
end
|
|
|
|
fn.clearmatches(ext.wins[ext.tab].cmd) -- Clear matchparen highlights.
|
|
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, {})
|
|
end
|
|
|
|
-- 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 M.prompt then
|
|
vim.schedule(function()
|
|
if not M.prompt then
|
|
api.nvim_buf_set_lines(ext.bufs.cmd, 0, -1, false, {})
|
|
api.nvim_win_set_config(ext.wins[ext.tab].prompt, { hide = true })
|
|
end
|
|
end)
|
|
end
|
|
|
|
M.prompt, curpos[1], curpos[2] = false, 0, 0
|
|
win_config(ext.wins[ext.tab].cmd, true, ext.cmdheight)
|
|
end
|
|
|
|
--- Set multi-line cmdline buffer text.
|
|
---
|
|
---@param lines CmdContent[]
|
|
function M.cmdline_block_show(lines)
|
|
for _, content in ipairs(lines) do
|
|
set_text(content, ':')
|
|
M.row = M.row + 1
|
|
end
|
|
end
|
|
|
|
--- Append line to a multiline cmdline.
|
|
---
|
|
---@param line CmdContent
|
|
function M.cmdline_block_append(line)
|
|
set_text(line, ':')
|
|
M.row = M.row + 1
|
|
end
|
|
|
|
--- Clear cmdline buffer and leave the cmdline.
|
|
function M.cmdline_block_hide()
|
|
M.cmdline_hide(nil, true)
|
|
M.row = 0
|
|
end
|
|
|
|
return M
|