Skip to content

A minimal UI event hook that forwards messages to `vim.notify()`.

Notifications You must be signed in to change notification settings

nqrk/msghook.nvim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 

Repository files navigation

What is this?

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).


Hook

This api is considered experimental, refers to :h ui_attach and :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.

Caveat

Nvim will no longer allocate screen space for the command line or messages, and cmdheight will 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.

Extui

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.


Usage

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.

Lazy.nvim

return {
    dir = "msghook.nvim",
    lazy = false,
    opts = {
        enabled = true, -- required
    }
}

Notifications

If you prefer to use another notification system, you can override vim.notify.

vim.notify = require("notify") -- or override_vim_notify = true for fidget

Alternatives to default vim.notify().

With UI hook

Example

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

About

A minimal UI event hook that forwards messages to `vim.notify()`.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages