neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

cmdline.lua (6472B)


      1 local ui = require('vim._core.ui2')
      2 local api, fn = vim.api, vim.fn
      3 ---@class vim._core.ui2.cmdline
      4 local M = {
      5  highlighter = nil, ---@type vim.treesitter.highlighter?
      6  indent = 0, -- Current indent for block event.
      7  prompt = false, -- Whether a prompt is active; route to dialog regardless of ui.cfg.msg.target.
      8  dialog = false, -- Whether a dialog window was opened.
      9  srow = 0, -- Buffer row at which the current cmdline starts; > 0 in block mode.
     10  erow = 0, -- Buffer row at which the current cmdline ends; messages appended here in block mode.
     11  level = 0, -- Current cmdline level; 0 when inactive.
     12  wmnumode = 0, -- wildmenumode() when not using the pum, dialog position adjusted when toggled.
     13 }
     14 
     15 --- Set the 'cmdheight' and cmdline window height. Reposition message windows.
     16 ---
     17 ---@param win integer Cmdline window in the current tabpage.
     18 ---@param hide boolean Whether to hide or show the window.
     19 ---@param height integer (Text)height of the cmdline window.
     20 local function win_config(win, hide, height)
     21  if ui.cmdheight == 0 and api.nvim_win_get_config(win).hide ~= hide then
     22    api.nvim_win_set_config(win, { hide = hide, height = not hide and height or nil })
     23  elseif api.nvim_win_get_height(win) ~= height then
     24    api.nvim_win_set_height(win, height)
     25  end
     26  if vim.o.cmdheight ~= height then
     27    -- Avoid moving the cursor with 'splitkeep' = "screen", and altering the user
     28    -- configured value with noautocmd.
     29    vim._with({ noautocmd = true, o = { splitkeep = 'screen' } }, function()
     30      vim.o.cmdheight = height
     31    end)
     32    ui.msg.set_pos()
     33  elseif M.wmnumode ~= (M.dialog and fn.pumvisible() == 0 and fn.wildmenumode() or 0) then
     34    M.wmnumode = (M.wmnumode == 1 and 0 or 1)
     35    ui.msg.set_pos()
     36  end
     37 end
     38 
     39 local cmdbuff = '' ---@type string Stored cmdline used to calculate translation offset.
     40 local promptlen = 0 -- Current length of the last line in the prompt.
     41 --- Concatenate content chunks and set the text for the current row in the cmdline buffer.
     42 ---
     43 ---@alias CmdChunk [integer, string]
     44 ---@alias CmdContent CmdChunk[]
     45 ---@param content CmdContent
     46 ---@param prompt string
     47 local function set_text(content, prompt)
     48  local lines = {} ---@type string[]
     49  for line in (prompt .. '\n'):gmatch('(.-)\n') do
     50    lines[#lines + 1] = fn.strtrans(line)
     51  end
     52  cmdbuff, promptlen, M.erow = '', #lines[#lines], M.srow + #lines - 1
     53  for _, chunk in ipairs(content) do
     54    cmdbuff = cmdbuff .. chunk[2]
     55  end
     56  lines[#lines] = ('%s%s '):format(lines[#lines], fn.strtrans(cmdbuff))
     57  api.nvim_buf_set_lines(ui.bufs.cmd, M.srow, -1, false, lines)
     58 end
     59 
     60 --- Set the cmdline buffer text and cursor position.
     61 ---
     62 ---@param content CmdContent
     63 ---@param pos integer
     64 ---@param firstc string
     65 ---@param prompt string
     66 ---@param indent integer
     67 ---@param level integer
     68 ---@param hl_id integer
     69 function M.cmdline_show(content, pos, firstc, prompt, indent, level, hl_id)
     70  M.level, M.indent, M.prompt = level, indent, #prompt > 0
     71  if M.highlighter == nil or M.highlighter.bufnr ~= ui.bufs.cmd then
     72    local parser = assert(vim.treesitter.get_parser(ui.bufs.cmd, 'vim', {}))
     73    M.highlighter = vim.treesitter.highlighter.new(parser)
     74  end
     75  -- Only enable TS highlighter for Ex commands (not search or filter commands).
     76  M.highlighter.active[ui.bufs.cmd] = firstc == ':' and M.highlighter or nil
     77  if ui.msg.cmd.msg_row ~= -1 then
     78    ui.msg.msg_clear()
     79  end
     80  ui.msg.virt.last = { {}, {}, {}, {} }
     81 
     82  set_text(content, ('%s%s%s'):format(firstc, prompt, (' '):rep(indent)))
     83  if promptlen > 0 and hl_id > 0 then
     84    api.nvim_buf_set_extmark(ui.bufs.cmd, ui.ns, 0, 0, { hl_group = hl_id, end_col = promptlen })
     85  end
     86 
     87  local height = math.max(ui.cmdheight, api.nvim_win_text_height(ui.wins.cmd, {}).all)
     88  win_config(ui.wins.cmd, false, height)
     89  M.cmdline_pos(pos)
     90 end
     91 
     92 --- Insert special character at cursor position.
     93 ---
     94 ---@param c string
     95 ---@param shift boolean
     96 --@param level integer
     97 function M.cmdline_special_char(c, shift)
     98  api.nvim_win_call(ui.wins.cmd, function()
     99    api.nvim_put({ c }, shift and '' or 'c', false, false)
    100  end)
    101 end
    102 
    103 local curpos = { 0, 0 } -- Last drawn cursor position.
    104 --- Set the cmdline cursor position.
    105 ---
    106 ---@param pos integer
    107 --@param level integer
    108 function M.cmdline_pos(pos)
    109  pos = #fn.strtrans(cmdbuff:sub(1, pos))
    110  if curpos[1] ~= M.erow + 1 or curpos[2] ~= promptlen + pos then
    111    curpos[1], curpos[2] = M.erow + 1, promptlen + pos
    112    -- Add matchparen highlighting to non-prompt part of cmdline.
    113    if pos > 0 and fn.exists('#matchparen#CursorMoved') == 1 then
    114      api.nvim_win_set_cursor(ui.wins.cmd, { curpos[1], curpos[2] - 1 })
    115      vim._with({ win = ui.wins.cmd, wo = { eventignorewin = '' } }, function()
    116        api.nvim_exec_autocmds('CursorMoved', {})
    117      end)
    118    end
    119    api.nvim_win_set_cursor(ui.wins.cmd, curpos)
    120  end
    121 end
    122 
    123 --- Leaving the cmdline, restore 'cmdheight' and 'ruler'.
    124 ---
    125 ---@param level integer
    126 ---@param abort boolean
    127 function M.cmdline_hide(level, abort)
    128  if M.srow > 0 or level > (fn.getcmdwintype() == '' and 1 or 2) then
    129    return -- No need to hide when still in nested cmdline or cmdline_block.
    130  end
    131 
    132  fn.clearmatches(ui.wins.cmd) -- Clear matchparen highlights.
    133  api.nvim_win_set_cursor(ui.wins.cmd, { 1, 0 })
    134  if M.prompt or abort then
    135    -- Clear cmd buffer prompt or aborted command (non-abort is left visible).
    136    api.nvim_buf_set_lines(ui.bufs.cmd, 0, -1, false, {})
    137  end
    138 
    139  vim.schedule(function()
    140    -- Avoid clearing prompt window when it is re-entered before the next event
    141    -- loop iteration. E.g. when a non-choice confirm button is pressed.
    142    if M.dialog and M.level == 0 then
    143      api.nvim_buf_set_lines(ui.bufs.dialog, 0, -1, false, {})
    144      api.nvim_win_set_config(ui.wins.dialog, { hide = true })
    145      vim.on_key(nil, ui.msg.dialog_on_key)
    146      M.dialog, ui.msg.dialog_on_key = false, nil
    147    end
    148  end)
    149 
    150  M.prompt, M.level, curpos[1], curpos[2] = false, 0, 0, 0
    151  win_config(ui.wins.cmd, true, ui.cmdheight)
    152 end
    153 
    154 --- Set multi-line cmdline buffer text.
    155 ---
    156 ---@param lines CmdContent[]
    157 function M.cmdline_block_show(lines)
    158  for _, content in ipairs(lines) do
    159    set_text(content, ':')
    160    M.srow = M.srow + 1
    161  end
    162 end
    163 
    164 --- Append line to a multiline cmdline.
    165 ---
    166 ---@param line CmdContent
    167 function M.cmdline_block_append(line)
    168  set_text(line, ':')
    169  M.srow = M.srow + 1
    170 end
    171 
    172 --- Clear cmdline buffer and leave the cmdline.
    173 function M.cmdline_block_hide()
    174  M.srow = 0
    175  M.cmdline_hide(M.level, true)
    176 end
    177 
    178 return M