In Neovim, we can use the virtual text feature to place text anywhere in the window, which is not possible with normal text.

A bit of history

Initially, the Nvim core dev bfredl add the function nvim_buf_set_virtual_text() to add virtual text to a buffer in this PR.

Over the time, the code is refactored and deprecated, in favor of the new API function nvim_buf_set_extmark()1.

Plugins utilizing virtual text

Plugins have been using virtual text to do various things. Here is a non-exhaustive list:

Using extmarks

The nvim_buf_set_extmark() function has a lot of parameters, and some of them can be confusing for users due to the conciseness of its documentation.

Here is a working example to demonstrate its usage:

local bnr = vim.fn.bufnr('%')
local ns_id = vim.api.nvim_create_namespace('demo')

local line_num = 5
local col_num = 5

local opts = {
  end_line = 10,
  id = 1,
  virt_text = {{"demo", "IncSearch"}},
  virt_text_pos = 'overlay',
  -- virt_text_win_col = 20,

local mark_id = vim.api.nvim_buf_set_extmark(bnr, ns_id, line_num, col_num, opts)

In the above, line_num variable specify which line we want to put the virtual text. col_num specify which column we want to put virtual text (but this is actually only half true, since we can put virtual text at any position). col_num shouldn’t exceed the actual column number of the line indicated by line_num. Otherwise, we get the following error:

E5113: Error while calling lua chunk: test.lua:17: col value outside range

In the opts parameter, there are more options to change the behavior of this function. virt_text is a table of table, where each table contains some text and the highlight group for it.

The option virt_text_pos has three possible values:

  • overlay: overlay the virtual text on the column specified with col_num, this is actually the only case the col_num really works.
  • eol: virtual text is place at the end of the line (col_num does not take effect).
  • right_align: virtual is place on the right of current window.

See the following image for a comparison:

To actually put virtual text on any column, we should use virt_text_win_col in the opts table. If we use virt_text_win_col, virt_text_pos does not take effect any more.

To delete a virtual text, use vim.api.nvim_buf_del_extmark(bnr, ns_id, id), where id is the id used in opts.


  1. In fact, extmark is more than virtual text. See :h api-extended-marks. ↩︎

  2. As of writing of this post, this plugin is still using the old API. ↩︎