mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	Problem:  Unable to see e.g. `inputlist()` prompts that exceed the dialog
          window height.
          Multi-line prompts are not handled properly, and tracking
          is insufficient for messages in cmdline_block mode.
Solution: Add vim.on_key handler while the dialog window is open that
          forwards paging keys to the window.
          Properly render multi-line prompts. Keep track of both the start
          and end of the current cmdline prompt. Append messages after the
          current prompt in cmdline_block mode.
		
	
		
			
				
	
	
		
			181 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			181 lines
		
	
	
		
			6.4 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.
 | 
						|
}
 | 
						|
 | 
						|
--- 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 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: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 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
 |