Plugins

The Lua API, declaring plugins, commands, keymaps, floating windows.

ctrlvim plugins are Lua files, loaded through mlua, against a vim.* API surface modeled on Neovim's own. If you have written a small Neovim plugin before, the API will feel familiar: vim.api.*, vim.fn.*, vim.opt and vim.o, vim.g, vim.cmd, vim.keymap, vim.notify, vim.schedule, the vim.tbl_* helpers, and vim.treesitter are all available. Language servers are configured separately through lsp.lua; see Language Servers.

# Declaring a plugin

Plugins are declared in config.toml, not auto discovered from a directory:

toml[[plugin]]
name = "hello"
path = "~/.config/ctrlvim/plugins/hello.lua"

path points at the plugin's entry file. ~ is expanded. Every [[plugin]] entry runs once, in order, over the same Lua path :luafile uses.

By default a plugin loads eagerly at startup. Add event to load it lazily instead, the first time that autocommand event fires:

toml[[plugin]]
name = "hello"
path = "~/.config/ctrlvim/plugins/hello.lua"
event = "BufWritePre"

Set enabled = false to keep a plugin declared, visible in the plugin manager, without loading it.

# A minimal plugin

lua-- hello.lua
Hello = {}

function Hello.greet()
  vim.api.ctrlvim_set_current_line("Hello from ctrlvim!")
end

With this declared in config.toml, :lua Hello.greet() replaces the current line. The full version of this example ships in the repository at examples/plugins/hello.lua.

# Registering commands and keymaps

A plugin contributes behavior mainly by registering a user command, then letting config or a keymap refer to it by name:

luavim.api.ctrlvim_create_user_command('Format', function()
  -- formatting logic here
end, {})
toml[[keymap]]
lhs = "<leader>lf"
rhs = ":Format<CR>"
desc = "format buffer"

A plugin can also bind a key directly, with a Lua function as the right hand side rather than an Ex command:

luavim.keymap.set('n', '<leader>pc', function()
  vim.api.ctrlvim_set_current_line('FIRED')
end, { desc = 'open claude' })

A desc set this way shows up in the which-key popup and in ? exactly like a [[keymap]] from config.toml does. Under the hood this registers a hidden generated command to back the callback; that generated command is intentionally left out of the command palette, since it is implementation detail nobody typed and nothing meaningful to run by name.

# The plugin manager

The dashboard's plugin manager (p from the shell keymap) lists every [[plugin]] entry, its path, whether it loaded successfully or failed with an error, and which commands it contributed, matched against the entry's name field against each registered command's source. Status and command list come entirely from config.toml and the one time load outcome, never from scanning the filesystem.

# Floating windows

Plugins can open their own floating windows and buffers with the same nvim_create_buf / nvim_open_win / nvim_buf_set_lines calls Neovim plugins use, useful for a picker, a panel, or a preview:

lualocal buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(buf, 0, -1, false, { 'panel contents' })
vim.api.nvim_open_win(buf, false, { width = 20, height = 5, row = 1, col = 1 })

A plugin window's buffer is kept separate from your actual file buffers, so opening one never overwrites the file you are editing.

# The msgpack-RPC server

Beyond in process Lua plugins, an external client can attach to ctrlvim over a Unix socket and drive the same API surface Lua uses, through msgpack-RPC. This is the same integration model Neovim's own RPC clients use, useful for tooling that would rather live outside the Lua VM entirely.

On this page