Custom column API #36

Closed
opened 2026-09-21 18:54:22 +00:00 by barrettruth · 1 comment
Owner

Original issue: barrettruth/canola.nvim#192
Original author: barrettruth
Original date: 2026-03-19T03:03:31Z

Problem

Users want to register custom columns (recursive directory sizes, git blame info, custom metadata fields). The internal columns.register(name, def) function exists but is completely undocumented. There is no public API, no vimdoc entry, no recipe, and no stability guarantee. Third-party plugins (e.g. canola-git for a git status column) have no reliable surface to hook into.

Consolidates

Current state

lua/oil/columns.lua exposes:

M.register = function(name, column) ... end

Adapters call this at module load time for their own columns (icon, type, name in columns.lua; size, permissions, mtime, owner, group in adapters/files.lua). The interface works but is internal-only.

Proposed public API

require('canola').register_column(name, column_def)

This is a thin re-export of columns.register through the public oil module, making it part of the stable API surface.

Column definition interface

Every field except render is optional.

render(entry, conf, bufnr) -> oil.TextChunk (required)

Returns the text and highlight for one cell. entry is a 4-tuple {id, name, type, meta}. conf is the per-column config table from the user's columns list (e.g. { "mtime", format = "%Y-%m-%d" }). Return value is either a plain string, a {text, hl_group} pair, or columns.EMPTY ({"-", "OilEmpty"}) when the column has no value for this entry.

parse(line, conf) -> value, remainder

Consumes the column's text from the front of line and returns the parsed value plus the unconsumed remainder. Required for physical columns (those that appear in the buffer). Omit for virtual columns (virtual = true).

compare(entry, parsed_value) -> boolean

Returns true when the parsed value differs from the cached entry's current value. Used to generate DiffChange actions on :w. Only needed for columns that represent mutable file metadata (e.g. permissions, ownership).

render_action(action) -> string

Returns a human-readable description of the change action for the confirmation prompt.

perform_action(action, callback)

Executes the change (e.g. fs_chmod). callback is fun(err: string|nil).

get_sort_value(entry) -> number|string

Returns a sortable scalar for this entry. Used when the user sorts by this column.

create_sort_value_factory(num_entries) -> fun(entry) -> number|string

Alternative to get_sort_value for sort implementations that need to precompute state across all entries (e.g. natural-order sorting with memoization). Takes precedence over get_sort_value when present.

virtual (boolean, v1.1+)

When true, the column renders as virt_text via extmarks rather than inline buffer text. parse is not called for virtual columns. See #142.

Adapter-scoped columns

Columns registered via require('canola').register_column are global — they appear for all adapters. Adapter-specific columns (size, permissions, mtime) are registered through the adapter's get_column(name) method, which takes precedence for that adapter's scheme.

If a third-party column should only appear for local files, the render function can check entry[FIELD_META].stat and return columns.EMPTY when stat is absent (as the built-in size column does).

Work needed

  1. Export register_column from lua/oil/init.lua
  2. Add oil.register_column entry to doc/canola.txt with the full field reference above
  3. Add oil-recipe-custom-column recipe to vimdoc showing a working example (e.g. a column that renders the number of hardlinks from entry[FIELD_META].stat.nlink)
  4. Add virtual field to oil.ColumnDefinition type annotation in columns.lua (coordinate with #142)
> Original issue: barrettruth/canola.nvim#192 > Original author: `barrettruth` > Original date: 2026-03-19T03:03:31Z ## Problem Users want to register custom columns (recursive directory sizes, git blame info, custom metadata fields). The internal `columns.register(name, def)` function exists but is completely undocumented. There is no public API, no vimdoc entry, no recipe, and no stability guarantee. Third-party plugins (e.g. canola-git for a git status column) have no reliable surface to hook into. ## Consolidates - stevearc/oil.nvim#457 ## Current state `lua/oil/columns.lua` exposes: ```lua M.register = function(name, column) ... end ``` Adapters call this at module load time for their own columns (icon, type, name in `columns.lua`; size, permissions, mtime, owner, group in `adapters/files.lua`). The interface works but is internal-only. ## Proposed public API ```lua require('canola').register_column(name, column_def) ``` This is a thin re-export of `columns.register` through the public `oil` module, making it part of the stable API surface. ## Column definition interface Every field except `render` is optional. **`render(entry, conf, bufnr) -> oil.TextChunk`** (required) Returns the text and highlight for one cell. `entry` is a 4-tuple `{id, name, type, meta}`. `conf` is the per-column config table from the user's `columns` list (e.g. `{ "mtime", format = "%Y-%m-%d" }`). Return value is either a plain string, a `{text, hl_group}` pair, or `columns.EMPTY` (`{"-", "OilEmpty"}`) when the column has no value for this entry. **`parse(line, conf) -> value, remainder`** Consumes the column's text from the front of `line` and returns the parsed value plus the unconsumed remainder. Required for physical columns (those that appear in the buffer). Omit for virtual columns (`virtual = true`). **`compare(entry, parsed_value) -> boolean`** Returns true when the parsed value differs from the cached entry's current value. Used to generate `DiffChange` actions on `:w`. Only needed for columns that represent mutable file metadata (e.g. permissions, ownership). **`render_action(action) -> string`** Returns a human-readable description of the change action for the confirmation prompt. **`perform_action(action, callback)`** Executes the change (e.g. `fs_chmod`). `callback` is `fun(err: string|nil)`. **`get_sort_value(entry) -> number|string`** Returns a sortable scalar for this entry. Used when the user sorts by this column. **`create_sort_value_factory(num_entries) -> fun(entry) -> number|string`** Alternative to `get_sort_value` for sort implementations that need to precompute state across all entries (e.g. natural-order sorting with memoization). Takes precedence over `get_sort_value` when present. **`virtual` (boolean, v1.1+)** When `true`, the column renders as `virt_text` via extmarks rather than inline buffer text. `parse` is not called for virtual columns. See #142. ## Adapter-scoped columns Columns registered via `require('canola').register_column` are global — they appear for all adapters. Adapter-specific columns (size, permissions, mtime) are registered through the adapter's `get_column(name)` method, which takes precedence for that adapter's scheme. If a third-party column should only appear for local files, the `render` function can check `entry[FIELD_META].stat` and return `columns.EMPTY` when stat is absent (as the built-in size column does). ## Work needed 1. Export `register_column` from `lua/oil/init.lua` 2. Add `oil.register_column` entry to `doc/canola.txt` with the full field reference above 3. Add `oil-recipe-custom-column` recipe to vimdoc showing a working example (e.g. a column that renders the number of hardlinks from `entry[FIELD_META].stat.nlink`) 4. Add `virtual` field to `oil.ColumnDefinition` type annotation in `columns.lua` (coordinate with #142)
barrettruth added this to the v1.1 milestone 2026-09-21 18:54:22 +00:00
Author
Owner

Original comment: barrettruth/canola.nvim#192, comment 4101797575
Original author: barrettruth
Original date: 2026-03-21T01:47:30Z

Implemented in #229 — register_column() exported from lua/canola/init.lua, vimdoc and recipe added.

> Original comment: barrettruth/canola.nvim#192, comment 4101797575 > Original author: `barrettruth` > Original date: 2026-03-21T01:47:30Z Implemented in #229 — `register_column()` exported from `lua/canola/init.lua`, vimdoc and recipe added.
Sign in to join this conversation.
No description provided.