-
Notifications
You must be signed in to change notification settings - Fork 0
Lua Plugin API
VimCode embeds Lua 5.4 (via mlua, fully vendored — no system Lua required). Plugins have access to the vimcode global object.
Call these at the top level of your script (during load time):
-- Register a custom command (callable via :MyCommand args)
vimcode.command("MyCommand", function(args)
-- args is a string containing everything after the command name
vimcode.message("Got: " .. args)
end)
-- Register an event hook
vimcode.on("save", function(path)
vimcode.message("Saved: " .. path)
end)
-- Register a key mapping
-- Modes: "n" (normal), "i" (insert), "v" (visual), "c" (command)
vimcode.keymap("n", "<leader>h", function()
vimcode.message("Hello from keymap!")
end)vimcode.message(text) -- Display a status bar message
vimcode.cwd() -- Get current working directory (string)
vimcode.command_run(cmd) -- Execute a VimCode command (e.g., "w", "q", "split")All line numbers are 1-indexed.
vimcode.buf.lines() -- All buffer lines as a table
vimcode.buf.line(n) -- Get line n (returns string or nil)
vimcode.buf.set_line(n, text) -- Replace line n with text (undoable)
vimcode.buf.insert_line(n, text) -- Insert new line before position n
vimcode.buf.delete_line(n) -- Delete line n
vimcode.buf.line_count() -- Total number of lines
vimcode.buf.path() -- File path (string or nil for unnamed buffers)
vimcode.buf.cursor() -- Returns {line=N, col=M} (1-indexed)
vimcode.buf.set_cursor(line, col) -- Move cursor to position (clamped)
vimcode.buf.annotate_line(n, text) -- Add virtual text annotation to line n
vimcode.buf.clear_annotations() -- Clear all line annotations
vimcode.buf.open_scratch(name, content, opts) -- Open a scratch buffer
-- opts (optional table): readonly=bool, filetype=string, split="vertical"|"horizontal"vimcode.opt.get("tabstop") -- Query a setting value (returns string)
vimcode.opt.set("tabstop", "4") -- Set a setting value (applied after callback)Available settings include: number, relativenumber, tabstop, shiftwidth, expandtab, autoindent, wrap, hlsearch, ignorecase, smartcase, scrolloff, cursorline, colorcolumn, textwidth, splitbelow, splitright, colorscheme, and more.
vimcode.state.mode() -- Current mode: "Normal", "Insert", "Visual", etc.
vimcode.state.filetype() -- Buffer language ID: "rust", "python", etc.
vimcode.state.register("a") -- Get register: {content="...", linewise=false} or nil
vimcode.state.set_register("a", "text", false) -- Set register (char, content, linewise)
vimcode.state.mark("a") -- Get mark position: {line=N, col=M} or nil-- Blame
vimcode.git.blame_line(10) -- {hash, author, date, relative_date, message, not_committed} or nil
vimcode.git.blame_file() -- [{hash, author, ...}, ...] for entire buffer
-- File history
vimcode.git.log_file(20) -- [{hash, message}, ...] for current file
vimcode.git.file_log_detailed(20)-- [{hash, author, date, message, stat}, ...]
vimcode.git.line_log(10, 20, 50) -- Commits touching line range [10,20], limit 50
-- Repository
vimcode.git.log(100) -- Repo-wide commit log, limit 100
vimcode.git.show("abc123") -- Full commit details (string or nil)
vimcode.git.diff_ref("main") -- Diff against ref (string or nil)
vimcode.git.repo_root() -- Repository root path (string or nil)
vimcode.git.branch() -- Current branch name (string or nil)
vimcode.git.branches() -- [{name, tracking, is_current}, ...]
-- Stash
vimcode.git.stash_list() -- [{index, message, branch}, ...]
vimcode.git.stash_push("msg") -- Push to stash (returns string)
vimcode.git.stash_pop(0) -- Pop stash entry (returns string)
vimcode.git.stash_show(0) -- Stash diff (string or nil)Run shell commands in a background thread with results delivered via event hooks:
-- Basic usage
vimcode.async_shell("git status", "my_result_event")
-- With options
vimcode.async_shell("grep -n pattern", "search_done", {
stdin = "input text", -- Optional: pipe to stdin
cwd = "/path/to/dir" -- Optional: working directory
})
-- Handle the result
vimcode.on("my_result_event", function(output)
vimcode.message("Result: " .. output)
end)Extensions can register custom sidebar panels that appear in the activity bar.
-- Register a custom sidebar panel (call at load time)
vimcode.panel.register("my_panel", {
icon = "X", -- Single character for activity bar icon
sections = {"Section A", "Section B"}, -- Named collapsible sections
on_focus = function() -- Called when panel gains focus
-- Populate sections here
end
})
-- Populate a section with items
vimcode.panel.set_items("my_panel", "Section A", {
{label = "Item 1", hint = "description", icon = "*", style = "normal"},
{label = "Item 2", hint = "extra info", icon = "+", style = "dim"},
{label = "Item 3", hint = "important", icon = "!", style = "bold"},
})
-- Parse event argument from panel_select/panel_action hooks
local info = vimcode.panel.parse_event(arg)
-- info = {panel="my_panel", section="Section A", index=0, label="Item 1", key="a"}| Key | Action |
|---|---|
j / k
|
Navigate items |
Tab |
Expand/collapse section |
Enter |
Fire panel_select event for current item |
q / Escape
|
Unfocus panel |
| Other keys | Fire panel_action event with the key |
| Event | Argument | When |
|---|---|---|
panel_focus |
panel name | Panel gains focus in sidebar |
panel_select |
"panel:section:index:label" |
Enter pressed on item |
panel_action |
"key:panel:section:index:label" |
Other key pressed on item |
| Style | Effect |
|---|---|
"normal" |
Default foreground color |
"dim" |
Muted/grey text |
"bold" |
Highlighted/bright text |
Override comment syntax for a language:
vimcode.set_comment_style("haskell", {
line = "--",
block_open = "{-",
block_close = "-}"
})| Event | Argument | When |
|---|---|---|
save |
file path | Before buffer is written to disk |
BufWrite |
file path | After buffer is written to disk |
open |
file path | File opened in editor |
BufNew |
file path | New buffer created |
BufEnter |
file path | Buffer/window entered |
cursor_move |
"line,col" |
Cursor moves (Normal mode only) |
VimEnter |
"" |
Editor initialization complete |
InsertEnter |
mode name | Entered Insert mode |
InsertLeave |
mode name | Left Insert mode |
ModeChanged |
"Old->New" |
Any mode change (e.g., "Normal->Insert") |
panel_focus |
panel name | Extension panel gains focus |
panel_select |
"panel:section:index:label" |
Enter pressed on extension panel item |
panel_action |
"key:panel:section:index:label" |
Other key pressed on extension panel item |
| Custom | shell output |
async_shell() callback event |
-- ~/.config/vimcode/extensions/wordcount/wordcount.lua
vimcode.command("WordCount", function(_)
local lines = vimcode.buf.lines()
local count = 0
for _, line in ipairs(lines) do
for _ in line:gmatch("%S+") do
count = count + 1
end
end
vimcode.message("Word count: " .. count)
end)
vimcode.on("save", function(_)
local lines = vimcode.buf.lines()
local count = 0
for _, line in ipairs(lines) do
for _ in line:gmatch("%S+") do
count = count + 1
end
end
vimcode.message("Saved (" .. count .. " words)")
end)With manifest:
name = "wordcount"
display_name = "Word Count"
description = "Word counting commands and save-time word count display"
scripts = ["wordcount.lua"]-- blame.lua — show inline blame annotations as you move the cursor
vimcode.on("cursor_move", function(pos)
local line, _ = pos:match("(%d+),(%d+)")
line = tonumber(line)
if not line then return end
local blame = vimcode.git.blame_line(line)
if blame and not blame.not_committed then
local text = blame.author .. " • " .. blame.relative_date .. " • " .. blame.message
vimcode.buf.clear_annotations()
vimcode.buf.annotate_line(line, text)
else
vimcode.buf.clear_annotations()
end
end)-- autoformat.lua — run LSP formatter on save for specific filetypes
local format_filetypes = { rust = true, python = true, go = true, javascript = true }
vimcode.on("save", function(path)
local ft = vimcode.state.filetype()
if format_filetypes[ft] then
vimcode.command_run("Lformat")
end
end):Plugin list " Show all loaded plugins
:Plugin reload " Reload all plugins from disk
:Plugin enable <name> " Enable a disabled plugin
:Plugin disable <name> " Disable a plugin (persisted in settings)Plugins have unrestricted file and process access (same trust model as Neovim). Only install plugins you trust.