mirror of
https://github.com/neovim/neovim.git
synced 2026-02-09 13:28:47 +00:00
Problem: The wildmenu is hidden behind the dialog window with "list" in 'wildmode'.
Global ('laststatus' set to 3) statusline is hidden behind the
pager window.
Solution: Check wildmenumode() to adjust the dialog position when necessary.
Ensure pager is positioned above the global statusline with 'laststus' set to 3.
185 lines
6.6 KiB
Lua
185 lines
6.6 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 'dialog' window.
|
|
srow = 0, -- Buffer row at which the current cmdline starts; > 0 in block mode.
|
|
erow = 0, -- Buffer row at which the current cmdline ends; messages appended here in block mode.
|
|
level = -1, -- Current cmdline level; 0 when inactive, -1 one loop iteration after closing.
|
|
wmnumode = 0, -- Return value of wildmenumode(), dialog position adjusted when toggled.
|
|
}
|
|
|
|
--- 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
|
|
-- Avoid moving the cursor with 'splitkeep' = "screen", and altering the user
|
|
-- configured value with noautocmd.
|
|
vim._with({ noautocmd = true, o = { splitkeep = 'screen' } }, function()
|
|
vim.o.cmdheight = height
|
|
end)
|
|
ext.msg.set_pos()
|
|
elseif M.wmnumode ~= (M.prompt and fn.wildmenumode() or 0) then
|
|
M.wmnumode = (M.wmnumode == 1 and 0 or 1)
|
|
ext.msg.set_pos()
|
|
end
|
|
end
|
|
|
|
local cmdbuff = '' ---@type string Stored cmdline used to calculate translation offset.
|
|
local promptlen = 0 -- Current length of the last line in the prompt.
|
|
--- 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)
|
|
local lines = {} ---@type string[]
|
|
for line in (prompt .. '\n'):gmatch('(.-)\n') do
|
|
lines[#lines + 1] = fn.strtrans(line)
|
|
end
|
|
cmdbuff, promptlen, M.erow = '', #lines[#lines], M.srow + #lines - 1
|
|
for _, chunk in ipairs(content) do
|
|
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)
|
|
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, 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', {}))
|
|
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()
|
|
end
|
|
ext.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 })
|
|
end
|
|
|
|
local height = math.max(ext.cmdheight, api.nvim_win_text_height(ext.wins.cmd, {}).all)
|
|
win_config(ext.wins.cmd, false, height)
|
|
M.cmdline_pos(pos)
|
|
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.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)
|
|
pos = #fn.strtrans(cmdbuff:sub(1, pos))
|
|
if curpos[1] ~= M.erow + 1 or curpos[2] ~= promptlen + pos then
|
|
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_exec_autocmds('CursorMoved', {})
|
|
end)
|
|
end
|
|
api.nvim_win_set_cursor(ext.wins.cmd, curpos)
|
|
end
|
|
end
|
|
|
|
--- Leaving the cmdline, restore 'cmdheight' and 'ruler'.
|
|
---
|
|
---@param level integer
|
|
---@param abort boolean
|
|
function M.cmdline_hide(level, abort)
|
|
if M.srow > 0 or level > (fn.getcmdwintype() == '' and 1 or 2) then
|
|
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 })
|
|
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
|
|
|
|
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)
|
|
end
|
|
-- Messages emitted as a result of a typed command are treated specially:
|
|
-- remember if the cmdline was used this event loop iteration.
|
|
-- NOTE: Message event callbacks are themselves scheduled, so delay two iterations.
|
|
vim.schedule(function()
|
|
M.level = -1
|
|
end)
|
|
end)
|
|
clear(M.prompt)
|
|
|
|
M.prompt, M.level, curpos[1], curpos[2] = false, 0, 0, 0
|
|
win_config(ext.wins.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.srow = M.srow + 1
|
|
end
|
|
end
|
|
|
|
--- Append line to a multiline cmdline.
|
|
---
|
|
---@param line CmdContent
|
|
function M.cmdline_block_append(line)
|
|
set_text(line, ':')
|
|
M.srow = M.srow + 1
|
|
end
|
|
|
|
--- Clear cmdline buffer and leave the cmdline.
|
|
function M.cmdline_block_hide()
|
|
M.srow = 0
|
|
M.cmdline_hide(M.level, true)
|
|
end
|
|
|
|
return M
|