mirror of
https://github.com/neovim/neovim
synced 2025-07-15 16:51:49 +00:00
Problem: Active cmdline may be overwritten by a message. Solution: Check if cmdline is active before writing message.
164 lines
5.5 KiB
Lua
164 lines
5.5 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.
|
|
level = 0, -- Current cmdline level, 0 when inactive (otherwise unused).
|
|
}
|
|
|
|
--- 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()
|
|
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, level, hl_id)
|
|
M.level, M.indent, M.prompt = level, 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, M.level, curpos[1], curpos[2] = false, 0, 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
|