A minimal UI event hook that forwards messages to vim.notify().
This is not a ready plugin!
Even if it can be loaded as such, this is only an example of how to route UI event messages.
Neovim 0.12+ required (because it relies on the experimental vim._extui API).
This api is considered experimental, refers to
:h ui_attachand:h ui-events.
Neovim's let you subscribe to the UI and receive events in a callback. It is used to implement screen elements like popupmenu or message handling in Lua.
We attach to ext_messages to externalize messages using our own logic.
vim.ui_attach(namespace, { ext_messages = true }, callback)Now the messages that would otherwise render in the screen space are now emitted as UI events.
The callback function acts as a filter for the msg_show event. We creates a context and feeds it to our message handler.
["msg_show", kind, content, replace_last, history, append, msg_id]Refers to :h ui-messages for details.
Nvim will no longer allocate screen space for the command line or messages, and
cmdheightwill be set to zero.
This means that after the hook, we must also handle ui-cmdline events (see :h ui-cmdline).
Redrawing the command line (including the niche edge cases) requires a lot of code.
We can delegate this job to vim._extui instead. It should work well for users who have cmdheight=0.
This experimental interface is intended to replace the message grid in the TUI.
:h extui
extui.enable {
enable = true,
msg = { target = 'cmd', timeout = 0 } -- let extui handle cmdline for us
}As it stands, the API is very bare‑bones and highly experimental. Let's hope that they will expose more configurable options in the future.
In our case, we make it work™ by setting cmdheight to zero before enabling the interface.
Messages are sent to the cmdline grid, but that grid is collapsed to a zero-height strip, so in fact hidden.
require('msghook').setup {
enabled = true,
-- optional
messages = {
errors = { "wmsg", "emsg", "lua_error" },
normal = { "echo" "list_cmd", "bufwrite", "lua_print" },
shell = { "shell_cmd" }
}
}Introduces :MsgHookToggle to enable/disable redirection without restarting Neovim.
return {
dir = "msghook.nvim",
lazy = false,
opts = {
enabled = true, -- required
}
}If you prefer to use another notification system, you can override vim.notify.
vim.notify = require("notify") -- or override_vim_notify = true for fidgetAlternatives to default vim.notify().
Let's intercept the shell message events and redirect them to a split buffer instead of the default vim.notify().
local hook = require("msghook")Set up the hook to handle all events except the shell ones.
hook.setup {
enabled = true,
messages = {
shell = {} -- we handle shell msg ourselves
}
}
:lua =print(vim.inspect(hook))
{
attach = <function 1>,
detach = <function 2>,
enabled = true,
handle = <function 3>,
messages = {
errors = { "wmsg", "emsg", "echoerr", "lua_error", "rpc_error", "shell_err" },
normal = { "bufwrite", "echo", "echomsg", "lua_print", "completion", "quickfix", "search_cmd", "search_count", "undo", "verbose", "wildlist", "list_cmd" },
shell = {}
},
setup = <function 4>
}We can override the handle method(3).
local win
local buf = vim.api.nvim_create_buf(false, true) -- new scratch buf
vim.treesitter.start(buf, "bash") -- attach treesitter highlight on it
local handle = hook.handle
hook.handle = function(ctx)
if not vim.tbl_contains({
"shell_cmd",
"shell_err",
"shell_out"
}, ctx.kind) then
handle(ctx) -- redirect to msghook handle
return
end
-- extract messages text
local s = vim.split(ctx:unpack(), "\n", { trimempty = false })
if ctx.kind == "shell_cmd" then
vim.api.nvim_buf_set_lines(buf, 0, -1, false, s)
else
if ctx.kind == "shell_out" then
vim.api.nvim_buf_set_lines(buf, -1, -1, false, s)
-- also highlight error messages
elseif ctx.kind == "shell_err" then
vim.api.nvim_buf_set_lines(buf, -1, -1, false, s)
vim.api.nvim_buf_set_extmark(
buf,
vim.api.nvim_create_namespace("foo"),
1, 0, {
end_row = #s,
hl_group = "ErrorMsg"
}
)
end
if win == nil or not vim.api.nvim_win_is_valid(win) then
win = vim.api.nvim_open_win(buf, false, { split = "above" })
end
end
end