Open-source project
luukvbaal/statuscol.nvim avatar
luukvbaal/statuscol.nvim

statuscol.nvim: composing a Neovim status column from segments

Status column plugin that provides a configurable 'statuscolumn' and click handlers.

614 stars33 forksLuaMIT

At a glance

What is it?
The plugin turns Neovim's statuscolumn option into a list of segments with their own text, signs, conditions and click handlers, which makes clickable folds, diagnostics, git signs and line numbers possible in one narrow column.
Who is it for?
statuscol.nvim is the right choice for a Neovim 0.10 user who wants one narrow column that carries fold markers, diagnostics, relative numbers and click handlers without maintaining a long statuscolumn expression by hand. It is the wrong choice for a Neovim version below 0.10, and it is the wrong choice if you need a released, versioned artifact to pin, since the repository publishes no GitHub releases and a lockfile has to reference a commit.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 106 days ago.
What is it written in?
Mainly Lua, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why the built-in statuscolumn option is awkward to extend

Neovim grew a statuscolumn option that lets a user define the content of the gutter next to the line numbers. It is a string expression, evaluated for every line of every window, and it is very good at showing a number and a sign column. It is not good at being extended.

The problem is composition. Every additional piece of information means another concatenation in that string, another escape sequence, and a matching click handler registered elsewhere, and the result becomes hard to read and harder to change. The plugin's answer is to replace the string with a list.

A status column is described as a configurable statuscolumn and click handlers, and it requires Neovim 0.10 or newer, which is the version where the statuscolumn option and the window and buffer handles it exposes are available. The stated goal is a clickable status column that looks the same as the Neovim default while still obeying most of the options that modify how the status column looks, so the point is not to restyle the gutter but to make it programmable.

The audience is narrow and self selecting: Neovim users who have already accepted the plugin ecosystem and want their gutter to do more than show numbers.

The segments model: text, sign, condition, click

Everything the plugin does comes out of one configuration table, the segments list. Each entry is a segment, and a segment can carry a text element, a sign element, a condition list, a click handler and a highlight group.

lua
{
  text = { '%C' },       -- table of strings or functions returning a string
  click = 'v:lua.ScFa',  -- %@ click function label, applies to each text element
  hl = 'FoldColumn',     -- %# highlight group label, applies to each text element
  condition = { true },  -- table of booleans or functions returning a boolean
  sign = {               -- table of fields that configure a sign segment
    -- at least one of 'name', 'text', and 'namespace' is required
    -- legacy signs are matched against the defined sign name e.g. 'DapBreakpoint'
    -- extmark signs can be matched against either the namespace or the sign text itself
    name = { '.*' },     -- table of Lua patterns to match the legacy sign name against
    text = { '.*' },     -- table of Lua patterns to match the extmark sign text against
    namespace = { '.*' },-- table of Lua patterns to match the extmark sign namespace against
    -- below values list the default when omitted:

Text is a table of strings or functions returning a string, which means a segment can be a static fragment or a computed one. Signs are matched by Lua patterns against a legacy sign name, against the text of an extmark sign, or against the extmark namespace, and a segment can also decide what to draw when nothing matches, whether signs should appear on the virtual or wrapped part of a line, and how a partially filled segment is aligned.

Two rules constrain the shape. The text and sign elements are mutually exclusive, except when the text contains the builtin line number function, in which case a sign segment can control what appears in the number area when signcolumn is set to number. And the text and condition lists should have the same length, one condition per text element, so a segment can switch off individual pieces rather than the whole column.

The default segments and the built-in click handlers

Out of the box the plugin builds a fold column, a sign column and a line number column, and the configuration file shows the defaults in full.

lua
  thousands = false,     -- or line number thousands separator string ('.' / ',')
  relculright = false,   -- whether to right-align the cursor line number with 'relativenumber' set
  -- Builtin 'statuscolumn' options
  ft_ignore = nil,       -- Lua table with 'filetype' values for which 'statuscolumn' will be unset
  bt_ignore = nil,       -- Lua table with 'buftype' values for which 'statuscolumn' will be unset
  -- Default segments (fold -> sign -> line number + separator), explained below
  segments = {
    { text = { '%C' }, click = 'v:lua.ScFa' },
    { text = { '%s' }, click = 'v:lua.ScSa' },
    {
      text = { builtin.lnumfunc, ' ' },
      condition = { true, builtin.not_empty },
      click = 'v:lua.ScLa',
    }
  },

The three defaults are the fold column, the sign column, and the number plus a trailing space, with the number text conditioned on the line not being empty. The two ignore options are the escape hatch, allowing the status column to be unset entirely for given filetypes or buffer types.

The click handlers are what make the column worth the effort, and they are a table of patterns matched against the thing you clicked.

lua
  clickhandlers = {       -- builtin click handlers, keys are pattern matched
    Lnum                    = builtin.lnum_click,
    FoldClose               = builtin.foldclose_click,
    FoldOpen                = builtin.foldopen_click,
    FoldOther               = builtin.foldother_click,
    DapBreakpointRejected   = builtin.toggle_breakpoint,
    DapBreakpoint           = builtin.toggle_breakpoint,
    DapBreakpointCondition  = builtin.toggle_breakpoint,
    ['diagnostic/signs']    = builtin.diagnostic_click,
    gitsigns                = builtin.gitsigns_click,
  },

So a click on a line number jumps to it, a click on a closed fold opens it, a click on a diagnostic sign cycles diagnostics, a click on a git sign opens the diff, and a click on a debugger breakpoint toggles it. A separate clickmod option chooses the modifier used by certain actions, with a for Alt, c for Ctrl and m for Meta.

Installing with lazy.nvim and calling setup

The plugin is a plain Lua module, so installation is a spec entry in whichever plugin manager you use, and the example given is for lazy.nvim.

lua
{
  'luukvbaal/statuscol.nvim', config = function()
    -- local builtin = require('statuscol.builtin')
    require('statuscol').setup({
      -- configuration goes here, for example:
      -- relculright = true,
      -- segments = {
      --   { text = { builtin.foldfunc }, click = 'v:lua.ScFa' },
      --   {
      --     sign = { namespace = { 'diagnostic/signs' }, maxwidth = 2, auto = true },
      --     click = 'v:lua.ScSa'
      --   },
      --   { text = { builtin.lnumfunc }, click = 'v:lua.ScLa', },
      --   {
      --     sign = { name = { '.*' }, maxwidth = 2, colwidth = 1, auto = true, wrap = true },
      --     click = 'v:lua.ScSa'
      --   },

The call itself is require('statuscol').setup({...}), and by default that function also sets the statuscolumn option for you. The setopt option exists so that the plugin does not set the option, which is what you want if you prefer to write your own statuscolumn and only use the click handlers, in which case the handlers are reachable as global functions.

The plugin itself recommends the other path. Building the column from the segments list rather than a hand written expression is what benefits from the performance work in the plugin, because the column is recomputed for every visible line and a hand written string is not as cheap to evaluate.

What the text and condition functions receive

Because a segment can be a function, the plugin passes a table of context to every text and condition function, and the documented fields are worth knowing before you write one.

lua
{
  lnum = 43,             -- v:lnum
  relnum = 5,            -- v:relnum
  virtnum = 0,           -- v:virtnum
  buf = 1,               -- buffer handle of drawn window
  win = 1000,            -- window handle of drawn window
  nu = true,             -- 'number' option value
  rnu = true,            -- 'relativenumber' option value
  empty = true,          -- statuscolumn is currently empty
  fold = {
    width = 1,           -- current width of the fold column

Everything a custom segment needs about the line and the window is in there, including the fold column width and the current values of the number and relativenumber options, and two further handles for the actual current buffer and window. The empty flag is what the default segments use to suppress the number on empty lines.

The table also carries what the documentation calls FFI data, with a tick field described as the display_tick value and further fields that are cut off in the published documentation. That is worth flagging, because it means the argument table is partly populated from a foreign function interface layer, which is a stronger dependency on the internals of the editor than a pure Lua plugin would have.

One more option belongs with the numbers rather than the segments: the thousands setting accepts a separator string such as a dot or a comma, so a line number above a thousand can be written as 1.234 or 1,234 without changing the underlying number.

Where statuscol.nvim is the wrong tool

Three limits are real, and one of them is about packaging rather than behaviour.

The version floor is the first. Neovim 0.10 or newer is required, so the plugin is unavailable to anyone on an older editor, and the version requirement is not a soft preference since the statuscolumn option and the handles it exposes do not exist earlier.

The second is scope. This is a plugin for the status column and nothing else. It does not draw a sign column as a separate option, and it does not replace the signcolumn or number options, so a user who wants a wide, conventional sign column with gitsigns and diagnostics in their natural positions gets no benefit from it. The whole value of the plugin is the opposite: folding several narrow pieces into one small column.

The third is the packaging. The repository publishes no GitHub releases, and its top level contains only a lua directory, a doc directory, a licence file, a stylua configuration and the readme. With no tagged versions, a lockfile has to reference a commit hash, and there is no changelog in the repository to tell you what changed between commits. The last push was on 2026-07-09, so the code is not frozen, but you are tracking a moving target without version markers.

The FFI dependency is a fourth, quieter risk, since the argument table is filled partly from interface data, which ties the plugin to internal details of how the editor renders.

MIT licensing, documentation and how to adopt it safely

The licence is MIT, so the code can be copied into your own configuration, modified and redistributed, with the notice kept intact, and there is no per user fee or support obligation attached. That is a permissive grant with no legal weight beyond the notice requirement, though a company with a policy on third party code will still want it recorded.

The documentation is generated into a doc directory at the top level, and the readme carries the markers for a documentation generator that stops before the configuration examples so they are not duplicated. Formatting is handled with a stylua configuration, which tells you the project expects a particular Lua style and that a formatter is part of the workflow.

For an adopter, the safe order is the one the project itself implies. Start with the default segments, which is the configuration the plugin's own documentation opens with, and check that the column looks like your previous one. Then add one segment at a time, most usefully a sign segment scoped to a single namespace such as diagnostics, so you can see the effect of each change separately. Only after that is it worth deciding whether to keep setopt at its default or to take over the statuscolumn expression yourself.

The one thing to settle first is the version you pin. With no released versions, choose a commit deliberately, record it in your plugin lockfile, and re-check it when you update, because the argument table and the builtin handler names are the parts most likely to change.

Editorial conclusion

statuscol.nvim is the right choice for a Neovim 0.10 user who wants one narrow column that carries fold markers, diagnostics, relative numbers and click handlers without maintaining a long statuscolumn expression by hand. It is the wrong choice for a Neovim version below 0.10, and it is the wrong choice if you need a released, versioned artifact to pin, since the repository publishes no GitHub releases and a lockfile has to reference a commit. Before adopting, set up() with the default segments first and check that the builtin lnum, diagnostic and gitsigns click handlers line up with the plugins you actually run, then decide whether setopt should stay true or whether you will call the click functions from your own statuscolumn.

Frequently asked questions

What does statuscol.nvim do in Neovim?

It provides a configurable statuscolumn and click handlers, and it requires Neovim 0.10 or newer. The setup() function configures the statuscolumn option so you get a clickable column that looks like the default while still obeying most of the options that change how the status column looks.

How do I install statuscol.nvim with lazy.nvim?

Add the plugin spec luukvbaal/statuscol.nvim and call require('statuscol').setup({...}) in the config function, optionally requiring statuscol.builtin first. The default segments are a fold column, a sign column and a line number with a separator.

Which click handlers does statuscol.nvim provide by default?

The builtin handlers cover the line number, fold close, fold open and other folds, debugger breakpoints for the rejected, normal and condition cases, diagnostic signs, and gitsigns. Keys in the clickhandlers table are pattern matched, and a clickmod option selects the modifier used by certain actions.

Can I use statuscol.nvim without letting it set the statuscolumn option?

Yes. Setting setopt to false leaves the option alone so you can build your own statuscolumn and still use the click handlers through the global functions, although the documentation recommends building the column from the segments field to benefit from the performance work in the plugin.

Does statuscol.nvim have released versions I can pin to?

The repository has no GitHub releases, and its top level holds the lua source directory, a doc directory, the licence, a stylua configuration and the readme. The last push was on 2026-07-09, so a plugin lockfile has to reference a commit rather than a version tag.

Official sources

  1. Issues
  2. License: MIT
  3. luukvbaal/statuscol.nvim on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/luukvbaal-statuscol-nvim.svg)](https://hysenlabs.com/projects/luukvbaal-statuscol-nvim)

Was this useful?