Skip to content

A Guide to Messages

Ranjith Hegde edited this page Oct 29, 2022 · 4 revisions

Messages and notifications in Neovim

The message area in vim/neovim displays a large variety of notifications. Some are triggered by vim's internal functionality and others are dispatched by error handlers, echo/print calls, command output, tools such as LSP, external plugins etc...

If you are suddenly alarmed at search hit bottom message, E:486 pattern not found error message, or various LSP notifications, you found yourself in the right wiki.

Many of vim's internal messages are classified as Hit-enter messages. :h messages

  Press ENTER or type command to continue

This message is given when there is something on the screen for you to read,
and the screen is about to be redrawn:
- After executing an external command (e.g., ":!ls" and "=").
- Something is displayed on the status line that is longer than the width of
  the window, or runs into the 'showcmd' or 'ruler' output.

When cmdheight > 0, it's possible that many of these messages are ignored or entirely unnoticed by users as many of motion and other key-press make these messages disappear.

Neovim also has nvim_ui_attach Api method to handle/override Ui events and related functions. When using GUI-neovim (with appropriate UI features) or any plugin that makes use of external UI Api, neovim hands over these messages via ext-messages. Check :help ui-messages (or skip head). This plugin handles the ext-messages using various views. For more information on the specifics check :help noice.nvim-views

This guide is a best effort collection of tips and tricks to customize/hide both messages and ui-messages for both using Neovim's internal systems and noice routing system.

Handling hit enter messages

Vim option shortmess allows customizing or avoiding these hit enter messages. Here is a quick overview.

Set (:set shortmess=, vim.o.shortmess=) or append (:set shortmess+=, vim.opt.shortmess:append()) to the option as necessary.

	  s	don't give "search hit BOTTOM, continuing at TOP" or "search
		hit TOP, continuing at BOTTOM" messages; when using the search
		count do not show "W" after the count message (see S below)
	  W	don't give "written" or "[w]" when writing a file
	  A	don't give the "ATTENTION" message when an existing swap file
		is found.
	  I	don't give the intro message when starting Vim |:intro|.
	  c	don't give |ins-completion-menu| messages.  For example,
		"-- XXX completion (YYY)", "match 1 of 2", "The only match",
		"Pattern not found", "Back at original", etc.
	  C	don't give messages while scanning for ins-completion items,
		for instance "scanning tags"
	  q	use "recording" instead of "recording @a"
	  F	don't give the file info when editing a file, like `:silent`
		was used for the command
	  S     do not show search count message when searching, e.g.
	        "[1/5]"

Check :help shortmess for a full list of flags. Neovim defaults to filnxtToOF

For external UI and noice, these messages are of event msg_show. This event categorizes the messages using the key kind.

	    Name indicating the message kind:
		"" (empty)	Unknown (consider a feature-request: |bugs|)
		"confirm"	|confirm()| or |:confirm| dialog
		"confirm_sub"	|:substitute| confirm dialog |:s_c|
		"emsg"		Error (|errors|, internal error, |:throw|, …)
		"echo"		|:echo| message
		"echomsg"	|:echomsg| message
		"echoerr"	|:echoerr| message
		"lua_error"	Error in |:lua| code
		"rpc_error"	Error response from |rpcrequest()|
		"return_prompt"	|press-enter| prompt after a multiple messages
		"quickfix"	Quickfix navigation message
		"search_count"	Search count message ("S" flag of 'shortmess')
		"wmsg"		Warning ("search hit BOTTOM", |W10|, …)

Some quick notes:

  • "Written" messages have the kind ""
  • Lua print and pretty_print messages also have the kind ""

For customizing these messages using noice, you can edit the following snippet:

  • Avoid written messages
require("noice").setup({
    routes = {
      {
        filter = {
          event = "msg_show",
          kind = "",
          find = "written",
        },
        opts = { skip = true },
      },
    },
  })
  • Avoid search messages. Noice by default uses virttext, this snippets also disables that.
require("noice").setup({
    routes = {
      {
        filter = {
          event = "msg_show",
          kind = "search_count",
        },
        opts = { skip = true },
      },
    },
  })
  • Avoid all messages with kind ""
require("noice").setup({
    routes = {
      {
        filter = {
          event = "msg_show",
          kind = "",
        },
        opts = { skip = true },
      },
    },
  })

Showmode

Vim option showmode is responsible for messages such as --INSERT For external UI, this corresponds to event msg_showmode which also handles Macros messages such as recording @

Noice by default skips these messages. But to enable it, use this snippet.

 require("noice").setup ({
    routes = {
      {
        view = "notify",
        filter = { event = "msg_showmode" },
      },
    },
  })

If you wish to have the showmode messages displayed in statusline, use the following snippet (Written for lualine)

require("lualine").setup({
  sections = {
    lualine_x = {
      {
        require("noice").api.statusline.mode.get,
        cond = require("noice").api.statusline.mode.has,
        color = { fg = "#ff9e64" },
      }
    },
  },
})

Commands & key press

Depending on the definition, vim/neovim commands (:help command) and key mappings (:help map) produce output in the command area.

The simplest way of avoiding these messages is to add the :silent modifier for commands.

For keymaps, use the <silent> flag when defining. Eg: imap <silent> 'key' 'key or command'

vim.keymap.set({"v", "s"}, "keys" , function()...end, {silent = true} )

If you are using which-key you can avoid help prompts and key press messages as follows:

   require('which-key').setup {
       show_help = false,
       show_keys = false,
   }

vim.notify

The default vim.notify simply parses the message to determine the importance based on vim.log.levels. For errors, it uses the default lua error handling. It uses :lua print for the rest. So, the ui-message event would be msg_show and kind is ""

Use one of the snippets defined earlier to reroute those messages.

Plugins that override vim.notify such as nvim-notify or noice take advantage of opts key passed to vim.notify(msg, level,{opts}) to categories and stylize the notification and then render as a popup or virttext.

One of the keys that can help identity and filter the messages is opts.title (if set).

noice by default overrides vim.notify and redirects it either to nvim-notify or its custom mini view, which is a virttext rendering similar to notifier.nvim and fidget.nvim.

An example snippet to reroute long notifications to splits

require("noice").setup({
    routes = {
      {
        filter = {
          event = "notify",
          min_height = 15
        },
        view = 'split'
      },
    },
  })

Lsp Messages

Neovim LSP client uses handler functions to manage various requests. This includes [$/progress] for LSP progress updates, [window/showMessage] to display messages notified by the server and other calls to update on job status.

Neovim LSP client uses vim.notify for many such use cases. Since noice by default overrides vim.notify, you can customize them similar to the previous snippet.

Methods [$/progress] and [window/showMessage] are overridden by noice.

Some language servers such as ltex-ls and null-ls provide various notifications, which many users find to be excessive.

Here is a snippet that customizes [$/progress] by taking advantage of opts.title and the lsp token. This snippet cuts off any recurring notifications of the same kind.

Please note: This snippet overrides the progress handler. So you will need to disable noice's handling of the same:

require("noice").setup({
    lsp = { progress = { enabled = false }}
})
local null_ls_token = nil
local ltex_token = nil

vim.lsp.handlers['$/progress'] = function(_, result, ctx)
    local value = result.value
    if not value.kind then
        return
    end

    local client_id = ctx.client_id
    local name = vim.lsp.get_client_by_id(client_id).name

    if name == 'null-ls' then
        if result.token == null_ls_token then
            return
        end
        if value.title == 'formatting' then
            null_ls_token = result.token
            return
        end
    end

    if name == 'ltex' then
        if result.token == ltex_token then
            return
        end
        if value.title == 'Checking document' then
            ltex_token = result.token
            return
        end
    end

    vim.notify(value.message, 'info', {
        title = value.title,
    })
end

If you wish to only reduce the notifications from ltex-ls and still preserve noice lsp_progress implementation, simply change the diagnostic config

 vim.diagnostic.config({
     update_in_insert = false
  })