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