mirror of
https://github.com/neovim/neovim.git
synced 2026-03-28 03:12:00 +00:00
fix(ui2): only highlight Ex command lines in the cmdline #38182
Problem: Prompts and message text (in block mode) in the cmdline are
parsed and highlighted as if it is Vimscript.
Entering the cmdline while it is expanded can work more like
it does with UI1, where the press enter prompt is replaced
and previous messages stay on the message grid, while
subsequent messages are placed below it.
Solution: Highlight manually with string parser on lines starting with ':'.
Spoof cmdline block mode when the cmdline is entered while it
is expanded.
This commit is contained in:
@@ -2,7 +2,6 @@ local ui = require('vim._core.ui2')
|
||||
local api, fn = vim.api, vim.fn
|
||||
---@class vim._core.ui2.cmdline
|
||||
local M = {
|
||||
highlighter = nil, ---@type vim.treesitter.highlighter?
|
||||
indent = 0, -- Current indent for block event.
|
||||
prompt = false, -- Whether a prompt is active; route to dialog regardless of ui.cfg.msg.target.
|
||||
dialog = false, -- Whether a dialog window was opened.
|
||||
@@ -44,7 +43,8 @@ local promptlen = 0 -- Current length of the last line in the prompt.
|
||||
---@alias CmdContent CmdChunk[]
|
||||
---@param content CmdContent
|
||||
---@param prompt string
|
||||
local function set_text(content, prompt)
|
||||
---@param hl_id integer Prompt highlight group.
|
||||
local function set_text(content, prompt, hl_id)
|
||||
local lines = {} ---@type string[]
|
||||
for line in (prompt .. '\n'):gmatch('(.-)\n') do
|
||||
lines[#lines + 1] = fn.strtrans(line)
|
||||
@@ -55,6 +55,29 @@ local function set_text(content, prompt)
|
||||
end
|
||||
lines[#lines] = ('%s%s '):format(lines[#lines], fn.strtrans(cmdbuff))
|
||||
api.nvim_buf_set_lines(ui.bufs.cmd, M.srow, -1, false, lines)
|
||||
|
||||
-- Highlight prompt, or parse and highlight line starting with ':' as Vimscript.
|
||||
if promptlen > 0 and hl_id > 0 then
|
||||
local opts = { invalidate = true, undo_restore = false, end_col = promptlen, hl_group = hl_id }
|
||||
opts.end_line = M.erow
|
||||
api.nvim_buf_set_extmark(ui.bufs.cmd, ui.ns, M.srow, 0, opts)
|
||||
elseif lines[1]:sub(1, 1) == ':' then
|
||||
local parser = vim.treesitter.get_string_parser(lines[1], 'vim')
|
||||
parser:parse(true)
|
||||
parser:for_each_tree(function(tstree, tree)
|
||||
local query = tstree and vim.treesitter.query.get(tree:lang(), 'highlights')
|
||||
if query then
|
||||
for capture, node in query:iter_captures(tstree:root(), lines[1]) do
|
||||
local _, start_col, _, end_col = node:range()
|
||||
if query.captures[capture]:sub(1, 1) ~= '_' then
|
||||
local opts = { invalidate = true, undo_restore = false, end_col = end_col }
|
||||
opts.hl_group = ('@%s.%s'):format(query.captures[capture], query.lang)
|
||||
api.nvim_buf_set_extmark(ui.bufs.cmd, ui.ns, M.srow, start_col, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the cmdline buffer text and cursor position.
|
||||
@@ -67,22 +90,17 @@ end
|
||||
---@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
|
||||
if M.highlighter == nil or M.highlighter.bufnr ~= ui.bufs.cmd then
|
||||
local parser = assert(vim.treesitter.get_parser(ui.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[ui.bufs.cmd] = firstc == ':' and M.highlighter or nil
|
||||
if ui.msg.cmd.msg_row ~= -1 then
|
||||
-- When entering the cmdline while it is expanded, place cmdline below messages.
|
||||
if M.level == 0 and ui.msg.cmd_on_key then
|
||||
M.srow = api.nvim_buf_line_count(ui.bufs.cmd)
|
||||
vim.on_key(nil, ui.msg.cmd_on_key)
|
||||
elseif ui.msg.cmd.msg_row ~= -1 and not ui.msg.cmd_on_key then
|
||||
ui.msg.msg_clear()
|
||||
end
|
||||
ui.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(ui.bufs.cmd, ui.ns, 0, 0, { hl_group = hl_id, end_col = promptlen })
|
||||
end
|
||||
M.level, M.indent, M.prompt = level, indent, #prompt > 0
|
||||
set_text(content, ('%s%s%s'):format(firstc, prompt, (' '):rep(indent)), hl_id)
|
||||
ui.msg.virt.last = { {}, {}, {}, {} }
|
||||
|
||||
local height = math.max(ui.cmdheight, api.nvim_win_text_height(ui.wins.cmd, {}).all)
|
||||
win_config(ui.wins.cmd, false, height)
|
||||
@@ -125,7 +143,15 @@ end
|
||||
---@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
|
||||
if ui.msg.cmd_on_key then
|
||||
if abort then
|
||||
api.nvim_win_close(ui.wins.cmd, true)
|
||||
ui.check_targets()
|
||||
else
|
||||
ui.msg.set_pos('cmd')
|
||||
end
|
||||
ui.msg.cmd_on_key, M.srow = nil, 0
|
||||
elseif 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
|
||||
|
||||
@@ -156,7 +182,7 @@ end
|
||||
---@param lines CmdContent[]
|
||||
function M.cmdline_block_show(lines)
|
||||
for _, content in ipairs(lines) do
|
||||
set_text(content, ':')
|
||||
set_text(content, ':', 0)
|
||||
M.srow = M.srow + 1
|
||||
end
|
||||
end
|
||||
@@ -165,7 +191,7 @@ end
|
||||
---
|
||||
---@param line CmdContent
|
||||
function M.cmdline_block_append(line)
|
||||
set_text(line, ':')
|
||||
set_text(line, ':', 0)
|
||||
M.srow = M.srow + 1
|
||||
end
|
||||
|
||||
|
||||
@@ -36,13 +36,13 @@ local M = {
|
||||
delayed = false, -- Whether placement of 'last' virt_text is delayed.
|
||||
},
|
||||
dialog_on_key = nil, ---@type integer? vim.on_key namespace for paging in the dialog window.
|
||||
cmd_on_key = nil, ---@type integer? vim.on_key namespace for paging in the dialog window.
|
||||
}
|
||||
|
||||
local cmd_on_key ---@type integer? Set to vim.on_key namespace while cmdline is expanded.
|
||||
-- An external redraw indicates the start of a new batch of messages in the cmdline.
|
||||
api.nvim_set_decoration_provider(ui.ns, {
|
||||
on_start = function()
|
||||
M.cmd.ids = (ui.redrawing or cmd_on_key) and M.cmd.ids or {}
|
||||
M.cmd.ids = (ui.redrawing or M.cmd_on_key) and M.cmd.ids or {}
|
||||
end,
|
||||
})
|
||||
|
||||
@@ -86,7 +86,7 @@ end
|
||||
---@param type 'last'|'msg'|'top'|'bot'
|
||||
---@param tgt? 'cmd'|'msg'|'dialog'
|
||||
local function set_virttext(type, tgt)
|
||||
if (type == 'last' and (ui.cmdheight == 0 or M.virt.delayed)) or cmd_on_key then
|
||||
if (type == 'last' and (ui.cmdheight == 0 or M.virt.delayed)) or M.cmd_on_key then
|
||||
return -- Don't show virtual text while cmdline is expanded or delaying for error.
|
||||
end
|
||||
|
||||
@@ -213,10 +213,6 @@ local function expand_msg(src)
|
||||
hlopts.end_col, hlopts.hl_group = mark[4].end_col, mark[4].hl_group
|
||||
api.nvim_buf_set_extmark(ui.bufs[tgt], ui.ns, srow + mark[2], mark[3], hlopts)
|
||||
end
|
||||
|
||||
if tgt == 'cmd' and ui.cmd.highlighter then
|
||||
ui.cmd.highlighter.active[ui.bufs.cmd] = nil
|
||||
end
|
||||
else
|
||||
M.virt.msg[M.virt.idx.dupe][1] = nil
|
||||
for _, id in pairs(M.virt.ids) do
|
||||
@@ -338,9 +334,6 @@ function M.show_msg(tgt, kind, content, replace_last, append, id)
|
||||
ui.cmd.srow = row + 1
|
||||
else
|
||||
api.nvim_win_set_cursor(ui.wins.cmd, { 1, 0 }) -- ensure first line is visible
|
||||
if ui.cmd.highlighter then
|
||||
ui.cmd.highlighter.active[buf] = nil
|
||||
end
|
||||
-- Place [+x] indicator for lines that spill over 'cmdheight'.
|
||||
local texth = api.nvim_win_text_height(ui.wins.cmd, {})
|
||||
local spill = texth.all > ui.cmdheight and (' [+%d]'):format(texth.all - ui.cmdheight)
|
||||
@@ -371,7 +364,7 @@ function M.show_msg(tgt, kind, content, replace_last, append, id)
|
||||
-- Reset message state the next event loop iteration.
|
||||
if not cmd_timer and (col > 0 or next(M.cmd.ids) ~= nil) then
|
||||
cmd_timer = vim.defer_fn(function()
|
||||
M.cmd.ids, cmd_timer, col = cmd_on_key and M.cmd.ids or {}, nil, 0
|
||||
M.cmd.ids, cmd_timer, col = M.cmd_on_key and M.cmd.ids or {}, nil, 0
|
||||
end, 0)
|
||||
end
|
||||
end
|
||||
@@ -480,7 +473,7 @@ function M.msg_history_show(entries, prev_cmd)
|
||||
end
|
||||
|
||||
-- Showing output of previous command, clear in case still visible.
|
||||
if cmd_on_key or prev_cmd then
|
||||
if M.cmd_on_key or prev_cmd then
|
||||
M.msg_clear()
|
||||
api.nvim_feedkeys(vim.keycode('<Esc>'), 'n', false)
|
||||
end
|
||||
@@ -511,20 +504,20 @@ function M.set_pos(tgt)
|
||||
cfg.title = tgt == 'dialog' and cfg.height < texth.all and { title } or nil
|
||||
api.nvim_win_set_config(win, cfg)
|
||||
|
||||
if tgt == 'cmd' and not cmd_on_key then
|
||||
if tgt == 'cmd' and not M.cmd_on_key then
|
||||
-- Temporarily expand the cmdline, until next key press.
|
||||
local save_spill = M.virt.msg[M.virt.idx.spill][1]
|
||||
local spill = texth.all > cfg.height and (' [+%d]'):format(texth.all - cfg.height)
|
||||
M.virt.msg[M.virt.idx.spill][1] = spill and { 0, spill } or nil
|
||||
set_virttext('msg', 'cmd')
|
||||
M.virt.msg[M.virt.idx.spill][1] = save_spill
|
||||
cmd_on_key = vim.on_key(function(_, typed)
|
||||
M.cmd_on_key = vim.on_key(function(_, typed)
|
||||
typed = typed and fn.keytrans(typed)
|
||||
if not typed or typed == '<MouseMove>' then
|
||||
if not typed or typed == '<MouseMove>' or typed == ':' then
|
||||
return
|
||||
end
|
||||
vim.on_key(nil, ui.ns)
|
||||
cmd_on_key, M.cmd.ids = nil, {}
|
||||
M.cmd_on_key, M.cmd.ids = nil, {}
|
||||
|
||||
-- Check if window was entered and reopen with original config.
|
||||
local entered = typed == '<CR>'
|
||||
|
||||
@@ -78,17 +78,17 @@ describe('cmdline2', function()
|
||||
{1:~ }|*9
|
||||
{16::}{15:if} {26:1} |
|
||||
{16::} {15:echo} {26:"foo"} |
|
||||
{15:foo} |
|
||||
foo |
|
||||
{16::} ^ |
|
||||
]])
|
||||
feed([[echo input("foo\nbar:")<CR>]])
|
||||
screen:expect([[
|
||||
|
|
||||
{1:~ }|*7
|
||||
:if 1 |
|
||||
: echo "foo" |
|
||||
{16::}{15:if} {26:1} |
|
||||
{16::} {15:echo} {26:"foo"} |
|
||||
foo |
|
||||
: echo input("foo\nbar:") |
|
||||
{16::} {15:echo} {25:input}{16:(}{26:"foo\nbar:"}{16:)} |
|
||||
foo |
|
||||
bar:^ |
|
||||
]])
|
||||
@@ -96,10 +96,10 @@ describe('cmdline2', function()
|
||||
screen:expect([[
|
||||
|
|
||||
{1:~ }|*7
|
||||
:if 1 |
|
||||
: echo "foo" |
|
||||
{16::}{15:if} {26:1} |
|
||||
{16::} {15:echo} {26:"foo"} |
|
||||
foo |
|
||||
: echo input("foo\nbar:") |
|
||||
{16::} {15:echo} {25:input}{16:(}{26:"foo\nbar:"}{16:)} |
|
||||
foo |
|
||||
bar:baz^ |
|
||||
]])
|
||||
@@ -109,11 +109,11 @@ describe('cmdline2', function()
|
||||
{1:~ }|*5
|
||||
{16::}{15:if} {26:1} |
|
||||
{16::} {15:echo} {26:"foo"} |
|
||||
{15:foo} |
|
||||
foo |
|
||||
{16::} {15:echo} {25:input}{16:(}{26:"foo\nbar:"}{16:)} |
|
||||
{15:foo} |
|
||||
{15:bar}:baz |
|
||||
{15:baz} |
|
||||
foo |
|
||||
bar:baz |
|
||||
baz |
|
||||
{16::} ^ |
|
||||
]])
|
||||
feed('endif')
|
||||
@@ -122,11 +122,11 @@ describe('cmdline2', function()
|
||||
{1:~ }|*5
|
||||
{16::}{15:if} {26:1} |
|
||||
{16::} {15:echo} {26:"foo"} |
|
||||
{15:foo} |
|
||||
foo |
|
||||
{16::} {15:echo} {25:input}{16:(}{26:"foo\nbar:"}{16:)} |
|
||||
{15:foo} |
|
||||
{15:bar}:baz |
|
||||
{15:baz} |
|
||||
foo |
|
||||
bar:baz |
|
||||
baz |
|
||||
{16::} {15:endif}^ |
|
||||
]])
|
||||
feed('<CR>')
|
||||
@@ -178,7 +178,7 @@ describe('cmdline2', function()
|
||||
screen:expect([[
|
||||
{10:f}oo |
|
||||
{1:~ }|*12
|
||||
{16::}{15:s}{16:/f}^ |
|
||||
{16::}{15:s}{16:/f^ } |
|
||||
]])
|
||||
end)
|
||||
|
||||
|
||||
@@ -334,10 +334,14 @@ describe('messages2', function()
|
||||
]])
|
||||
command('echo "foo\nbar"')
|
||||
screen:expect_unchanged()
|
||||
-- Place cmdline below expanded cmdline instead: #37653.
|
||||
feed(':')
|
||||
screen:expect([[
|
||||
|
|
||||
{1:~ }|*12
|
||||
{1:~ }|*9
|
||||
{3: }|
|
||||
foo |
|
||||
bar |
|
||||
{16::}^ |
|
||||
]])
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user