Lua reference
Every function a Lua command can call in AutoDoc: the autodoc table, the editor object ed, and
the daemon's verbs. Lua in AutoDoc says where scripts live and how they load; the
tutorial uses all of this step by step.
The language is Lua 5.1 (gopher-lua), with the base, table, string and math libraries (the sandbox).
Positions
- 1-based. Line 1 is the first line; column 1 is the first character.
- A column is a character, a grapheme cluster:
éis one column, even written aseand an accent. Lua's#textcounts bytes, so it equals the column count only for ASCII. - A range runs from its start up to, not including, its end.
ed:text(2, 1, 2, 6)is line 2's first five characters. From column 1 of line n to column 1 of line n + 1 is line n with its line break. - Positions are clamped. A column past a line's end is its end; a line past the last is the
last; a range given end first is put in order.
#text + 1is therefore a safe end-of-line column. - In Vim's Normal mode the cursor sits on a character.
ed:set_cursorpast a line's last character puts it on that character, and the cursor after an edit at a line's end moves back onto it. The positions an edit returns are exact: chain edits through them, not throughed:cursor().
The autodoc table
| Function | Does | Returns |
|---|---|---|
autodoc.command{name, aliases, args, help, run, complete, id, title} |
Registers a : command, an entry of AutoDoc's command catalog (what it takes). Runs as the file loads. |
nothing |
autodoc.run(id, …) |
Runs a command of the catalog by its id, AutoDoc's or yours, with the rest as its arguments (below). | true, or false and why |
autodoc.notify(message [, opts]) |
Shows a notification: a toast in the corner, kept in the history (SPC h). opts is {id = "sync", ongoing = true}: with ongoing, a toast that stays and is updated in place by each notify with that id; without, that toast ended (the history keeps its last message); the id posted ongoing again starts again. An id is at most 64 letters, digits, ., _ or -, ongoing needs one, and a message is at most 1024 bytes: a bad option raises an error. Every script shares one set of ids. |
nothing |
autodoc.call(verb, params, cb) |
Calls any daemon verb by its name, with params a list of its params in order (nil or {} for none). cb is optional. |
nothing; the answer goes to cb |
autodoc.current_workspace() |
The workspace in use. | a string, or nil before one is entered |
autodoc.document() |
The page in the editor, the buffer shown (below). | a table |
autodoc.buffers() |
Every open buffer, in opening order: {n, path, workspace, kind, modified, active} (buffers). |
a list of tables |
autodoc.buffer(n) |
An editor object for buffer n, shown or not: ed's methods (buffers). |
the object, or nil and why |
autodoc.widgets() |
The places the keyboard can go, in the order :go and F6 know them: the editor, every panel, each tab of the terminal and the agent panels; never the menu bar. autodoc.run("widget.go", name) goes to one, opening a closed panel (a card plugin's too). |
a list of {name, title, open, focused} |
autodoc.map{mode, keys, command, title} |
Binds a key to a command line (keys and menus). | nothing |
autodoc.menu{where, title, command} |
Adds a menu row that runs a command line (keys and menus). | nothing |
autodoc.<domain>.<verb>(…, cb) |
Calls the daemon's <domain>.<verb>: its params in order, then cb. Made when AutoDoc connects (the daemon's verbs). |
nothing; the answer goes to cb |
The engine's own functions, beside command and notify:
| Function | Returns | Notes |
|---|---|---|
autodoc.now() |
number | The time, in seconds since 1970-01-01 UTC, with a fraction: os.time()'s, to the millisecond. |
autodoc.date(format, t) |
string, or a table | The time t (seconds, now() by default) written by format, as os.date does: %Y-%m-%d, %H:%M, …; %c by default. A format starting with ! is in UTC, else local time; "*t" gives a table: year, month, day, hour, min, sec, wday, yday, isdst. |
autodoc.commands() |
list of tables | Every command, in the order they were added: {id, name, aliases, args, help, title, source}. name is its : name, "" for one only a key or its id runs; source says whose it is ("lua", "plugin", "editor", …). A command that takes another's : name finds that one here, under its id and with no name, and runs it with autodoc.run. |
autodoc.on(event, fn) |
nothing | Calls fn(ed, info) each time event happens, from now until the scripts reload. The host says which events there are (events). A handler runs as a callback does, and its error is a notification. |
-- stamp.lua: :stamp inserts today's date, and :stamp! the time too
autodoc.command{
name = "stamp",
help = "insert the date (with !, the time too)",
run = function(ed, args, line, cmd)
ed:insert(autodoc.date(cmd.bang and "%Y-%m-%d %H:%M" or "%Y-%m-%d"))
end,
}
autodoc.document()
The page in the editor, the buffer shown, as a table. It can be called anywhere, a file's top level too.
| Field | What it is |
|---|---|
buffer |
the buffer's number, as :b and autodoc.buffers() give it |
path |
its path in its workspace; nil for the untitled draft |
workspace |
the workspace it is in; nil for the draft, or a file outside every workspace |
kind |
how AutoDoc reads it: "markdown", "text", "yaml", "derived" or "registered" (source code) |
derived |
a derived document's format, "PDF" or "DOCX": its text is read-only |
frontmatter |
a Markdown page's frontmatter, as Lua values, read from the editor's text now: {title = "Plan", tags = {"a", "b"}}; nil without one |
frontmatter_error |
why the frontmatter could not be read, when it is not YAML |
-- tagged.lua: :tagged says the page's tags, from its frontmatter
autodoc.command{
name = "tagged",
help = "say the page's tags",
run = function(ed)
local doc = autodoc.document()
local tags = doc.frontmatter and doc.frontmatter.tags
if type(tags) ~= "table" or #tags == 0 then
error((doc.path or "the draft") .. " has no tags", 0)
end
autodoc.notify((doc.path or "the draft") .. ": " .. table.concat(tags, ", "))
end,
}
autodoc.command{…}
| Field | Type | Required | What it is |
|---|---|---|---|
name |
string | yes | What you type after :. Letters, digits, - and _, not all digits. |
run |
function | yes | run(ed, args, line, cmd): what the command does. |
aliases |
list of strings | no | Other names for it, under the same rules as name. |
args |
string | no | Its arguments, as Tab shows them: "<term>", "[depth]". A hint only: nothing checks it. |
complete |
list of strings, or function | no | What Tab offers for an argument (below). |
help |
string | no | What it does, in one line, as Tab shows it. |
id |
string | no | Its id in the command catalog, which autodoc.run and F1 name it by: lua.<something>. lua.<name> when left out. |
title |
string | no | What F1's COMMANDS calls it. Its help, or its name, when left out. |
run gets:
| Argument | What it is |
|---|---|
ed |
the editor object, below |
args |
the words after the command, a list of strings: :hi Ada Lovelace gives {"Ada", "Lovelace"} |
line |
the rest of the line, trimmed at its ends only: "Ada Lovelace"; "" with none |
cmd |
how it was called: cmd.name, the name as typed (an alias, say), and cmd.bang, true for :hi! |
-- shape.lua: :shape shows every field autodoc.command takes
autodoc.command{
name = "shape", -- letters, digits, - and _; not all digits
aliases = {"sh"}, -- other names
args = "[words]", -- what Tab shows after the name
help = "show what run gets", -- what Tab shows after that
run = function(ed, args, line, cmd)
autodoc.notify("shape: " .. #args .. " args, line '" .. line .. "' (as :" .. cmd.name
.. (cmd.bang and "!" or "") .. ")")
end,
}
Errors. autodoc.command raises, and the file fails to load, for:
- no
name, or norunfunction:bad argument #1 to command (command needs a name and a run function); - a name or alias outside the rules:
tui: "has space" is not a ':' name: letters, digits, - and _, and not all digits; - an
idoutsidelua.:tui: the Lua command file.mine: a Lua id is lua.<name>.lua.is yours; AutoDoc's ids and the plugins' are theirs.
What a command takes
- Its id replaces the command with that id. A command named
tableislua.table, so yours replaces AutoDoc's:tablewhole, and a later file's replaces an earlier file's. - Each of its names, it takes from whatever had it, and only that name. Your
termtakes:termfrom AutoDoc's terminal command, which keeps:terminaland its id (terminal.open);:findof yours takes:find, and AutoDoc's find in the page still runs by its id (find.term). F1,:and Tab all show the name under yours. - The editor's own verbs are replaced whole, as they always were: they are the editor's, not
catalog entries. An alias
writereplaces:wand:writeboth.:w,:q,:x,:wqand:replaceare the editor's.
The bang. :hello! runs hello with cmd.bang true; :hello with false. Run by a key or
by autodoc.run, cmd.bang is false and cmd.name is the command's name.
One undo. Everything run edits is one undo step, ed:run's commands included.
Completing arguments
After the name and a space, Tab completes the word being typed from complete:
- A list is the candidates:
complete = {"flowchart", "sequence"}. AutoDoc's:mermaidoffers its diagram types this way. - A function is called as
complete(ed, word, args, cmd)each time Tab starts on a word:wordis the word typed so far (""after a space),argsthe words before it,cmdasrun's. It returns a list of strings. It runs asrundoes, with 2 seconds anded, so it can offer what is on the page. - Tab keeps the candidates that start with the word, so return them all. One is filled in
with a space after it; several are listed, and Tab and Shift+Tab go through them. With none,
Tab shows
argsandhelp. - A function's error is a notification,
<name>'s complete: <error>, and Tab showsargs.
-- heading.lua: :heading <name> goes to a heading of the page; Tab offers them
local function headings(ed)
local out = {}
for n, text in ipairs(ed:lines()) do
local title = text:match("^#+%s+(.-)%s*$")
if title then out[#out + 1] = {name = (title:gsub("%s+", "-")), line = n} end
end
return out
end
autodoc.command{
name = "heading",
args = "<name>",
help = "go to a heading of the page",
complete = function(ed, word)
local names = {}
for _, h in ipairs(headings(ed)) do names[#names + 1] = h.name end
return names
end,
run = function(ed, args)
for _, h in ipairs(headings(ed)) do
if h.name == args[1] then
ed:set_cursor(h.line, 1)
return
end
end
error("heading: no " .. (args[1] or "name") .. " on the page", 0)
end,
}
A heading's spaces become -, so each name is one word.
autodoc.run(id, …)
Runs a command of AutoDoc's command catalog by its id: AutoDoc's own (todos.open, file.save,
terminal.toggle), a feature's, or a Lua command (lua.<name>). F1's COMMANDS lists every id,
with its : names and keys. The arguments after the id are the command's words, as if typed after
its name: strings, numbers or booleans.
- It returns
true, orfalseand why, and never raises, so a script can try a command another may not have:unknown: <id>for an id no command has, the command's own error, or a refusal. A plugin's command (plugin.<name>.<id>) is never Lua's to run. - It runs now, on AutoDoc's loop, as a key does. A Lua command it runs gets
ed, the editor's, and its edits join the running command's one undo. autodoc.run("buffer.new", name, text)opens a new buffer holdingtext, an unsaved draft calledname("q.sql", read as SQL; never a path), for Ctrl+S to name. Both are optional;textis at most 256 KiB.
-- again.lua: :again <id> [args] runs a command of the catalog by its id, and says how it went
autodoc.command{
name = "again",
args = "<id> [args]",
help = "run a command by its id",
run = function(ed, args)
local ok, err = autodoc.run(unpack(args))
if ok then
autodoc.notify("again: ran " .. args[1])
else
autodoc.notify("again: " .. err)
end
end,
}
The editor object, ed
ed reaches the editor inside a command's run and inside a daemon callback. Elsewhere (at a
file's top level, as it loads) its methods raise the editor is only reachable inside a command's run.
| Method | Returns | Notes |
|---|---|---|
ed:value() |
string | The whole text, lines joined by "\n". |
ed:set_value(text) |
nothing | Replaces the whole text, as one edit: u brings the old text back. |
ed:line_count() |
number | How many lines. A text ending in "\n" has an empty last line. |
ed:line(n) |
string | Line n's text, without its line break. Raises no such line for n outside 1 to ed:line_count(). |
ed:lines() |
list of strings | Every line, a copy. |
ed:cursor() |
line, col | Where the cursor is. |
ed:set_cursor(line, col) |
nothing | Moves the cursor, clamped, and scrolls to it. |
ed:insert(text) |
line, col | Inserts at the cursor, and returns the position just after the text. That position is exact even when Vim's Normal mode then rests the cursor on the last character (see Positions). "\n", "\r\n" and "\r" start lines. In Vim's Normal mode the text goes before the character under the cursor. |
ed:insert_at(line, col, text) |
line, col | ed:insert at a position. |
ed:replace(l1, c1, l2, c2, text) |
line, col | Puts text in place of the range; returns the position just after it. |
ed:delete(l1, c1, l2, c2) |
nothing | Removes the range. The cursor goes to its start. |
ed:text(l1, c1, l2, c2) |
string | The range's text, line breaks as "\n". |
ed:selection() |
l1, c1, l2, c2, or nil |
The selection's range. A line-wise selection (V) runs from column 1 of its first line to the end of its last. nil outside Visual mode. |
ed:select(l1, c1, l2, c2) |
nothing | Selects the range, as Visual mode does, with the cursor on its last character: ed:mode() is then "visual", whatever the keys. An empty range leaves the selection as it was. |
ed:unselect() |
nothing | Ends the selection; the cursor stays where it is. Nothing without one. |
ed:find(term, opts) |
l1, c1, l2, c2, or nil |
The next match of term after the cursor. It does not move the cursor. opts is optional: {ignore_case = true, whole_word = true, backward = true, wrap = true, at_cursor = true}. |
ed:find_all(term, opts) |
list of {l1, c1, l2, c2} |
Every match of term, in order: {} with none. opts as ed:find's; only ignore_case and whole_word apply. |
ed:replace_all(term, text, opts) |
number | Puts text in place of every match of term, as one edit, and returns how many. opts as ed:find_all's. |
ed:mode() |
string | The editor's mode: "normal", "insert", "replace", "visual", "visual_line" or "visual_block". Without Vim's keys it is "insert", or "visual" with a selection. |
ed:read_only() |
boolean | Whether edits are refused: a PDF's or DOCX's derived text, say. |
ed:revision() |
number | Names the text as it is now: every edit and every new document gives a new one, and an undo gives back the one the text had. A callback compares the revision its command read with ed:revision() to know whether the page changed while it waited. |
ed:modified() |
boolean | Whether the text has changed since it was opened or last saved, as :x asks before it writes. |
ed:run(line) |
nothing | Runs a command line as the prompt does, without the :: "12", "find TODO", "table 2 3". Raises the command's error. |
Edits. ed:set_value, ed:insert, ed:insert_at, ed:replace, ed:delete and
ed:replace_all edit.
- An edit ends the selection. Read
ed:selection()before the first edit. - On a read-only page (a PDF's or DOCX's derived text) every edit raises
widget: the editor is read-only, and changes nothing.
ed:find.
- Forward, the match is the first that starts after the cursor, so finding again from a match
moves on. Backward (
backward = true), it is the last that starts before the cursor. - With
at_cursor = truea match starting under the cursor counts too: the word the cursor is on, found forward from where it starts. - With
wrap = trueit goes round from the other end; without, it isnilat the end. - A term matches within one line, and literally: no patterns.
ignore_casefolds case.whole_wordmatches only whole words, as Vim's*does: where the term starts with a letter, digit or_, the match must start a word, and where it ends with one, end a word. ed:findneither marks the matches nor setsnandN. For that,ed:run("find " .. term).
ed:run. ed:run("x") raises widget: not an editor command: x for a command that does not
exist. It runs AutoDoc's commands and the editor's alike, and each does what it does from the
prompt. "find …" is AutoDoc's find in the page: its matches are marked, n and N step through
them, and its jump to the first match comes just after your command ends.
Arguments. The method's own errors count ed as argument 1, so a method's first argument is
#2: ed:line("3") raises bad argument #2 to line (number expected, got string). A position is
a number; args are strings, so convert them with tonumber. A fraction is cut to a whole number.
A text argument may be a string or a number.
Buffers
Every open file, and the untitled draft, is a buffer; the editor shows one. :b <n|name> and the
Buffers picker switch, :bd closes the one shown.
autodoc.buffers()lists them:{n, path, workspace, kind, modified, active}, in opening order.autodoc.buffer(n)is an editor object for buffern, withed's methods. A buffer not shown is edited by a headless editor of its own, through the code the user's keys run, so a command gives the same result on any buffer. Each call is one undo step in its buffer, and makes it modified.- In a gated script, a buffer's
runneeds thecommandsgrant, as itsed:rundoes.
-- todos.lua: :todos counts TODO in every open buffer
autodoc.command{
name = "todos",
help = "count TODO in every open buffer",
run = function()
for _, b in ipairs(autodoc.buffers()) do
local n = #autodoc.buffer(b.n):find_all("TODO", {})
autodoc.notify(b.n .. " " .. (b.path or "untitled") .. ": " .. n)
end
end,
}
Events
autodoc.on(event, fn) calls fn(ed, info) each time event happens:
| Event | When | info |
|---|---|---|
open |
a file was loaded into a buffer (shown or not), or a buffer became the draft | {buffer, path, workspace}; path is nil for the draft |
enter |
a buffer is shown in the editor: switched to, or opened | {buffer, path, workspace} |
leave |
the buffer shown is about to be replaced by another | {buffer, path, workspace} |
close |
a buffer is closed (after the unsaved question, if any) | {buffer, path, workspace} |
save |
a save wrote a buffer's text | {buffer, path, workspace} |
mode |
the editor's mode changed | {mode, previous}, as ed:mode() names them |
change |
a buffer's text changed, once typing pauses (300 ms): a burst of keys is one change, per buffer | {buffer, revision}, as ed:revision() |
-
openis a load;enteris a switch. Opening a file shows its buffer (enter) and loads it (open); switching back to it later isenteronly. A script that sets something up for each file keepsopen; one that follows the page on screen addsenter. -
A handler runs as a callback does: on AutoDoc's loop, with 2 seconds,
edreachable and its edits one undo step. Its error is a notification,a Lua handler failed: …; the other handlers still run, and it stays. -
A handler's own edits do not emit
changeagain, so achangehandler may tidy the page. -
:lua-reloaddrops every handler with the scripts that set them; loading them again sets them again.
-- lastsaved.lua: :lastsaved <path> says when a page was last saved in this run
local saved = {}
autodoc.on("save", function(ed, info)
saved[info.path] = autodoc.date("%H:%M:%S")
end)
autodoc.command{
name = "lastsaved",
args = "<path>",
help = "say when a page was last saved",
run = function(ed, args)
local p = args[1] or (autodoc.document().path or "")
autodoc.notify("lastsaved: " .. p .. " " .. (saved[p] or "not saved yet"))
end,
}
Keys and menus
autodoc.map binds a key, and autodoc.menu adds a menu row. Each runs its command line as :
does: a command's name and its arguments, "table 2 3". Its error is a notification.
autodoc.map field |
What it is |
|---|---|
mode |
"normal" or "visual": a key of the page in Vim's Normal or Visual mode; "leader": a letter of SPC u, your Lua keys' card |
keys |
Normal and Visual: one character, "Q", or a control chord, "<C-k>". Leader: a letter or digit, not q (it closes the card) |
command |
the command line it runs |
title |
how SPC u's card names a leader key; the command line when left out |
- A page key runs only with nothing pending: after a count (
3), an operator (d) or an armed key (f,m,q,"), the key is Vim's as always. It is not repeated by.or recorded in a macro. - A page key replaces Vim's for that key. Pick one you don't use (
Q, a<C-…>chord). :lua-reloaddrops every key and row with the scripts; loading them again binds them again.
autodoc.menu field |
What it is |
|---|---|
where |
"editor", the page's right-click menu, or "plugins", the Plugins menu |
command |
the command line it runs |
title |
its text, & before the mnemonic letter: "&Word count". In the editor's menu, also a function title(ctx), answered each time the menu opens: "Execute on " .. conn |
menu |
a submenu to show it under: rows with the same menu share one, a plugin's rows included (they sit under the plugin's title) |
files |
editor rows: globs over the open file's base name, {"*.sql"}; the row shows only for a match |
when |
editor rows: a function when(ctx), asked as the menu opens; the row shows only when it returns true |
id |
a name for the row: the same id from the same script again replaces the row in its place |
ctxis{path, workspace, kind, selection}: the open file's path in its workspace, the workspace, its kind ("markdown", …) and whether text is selected. A folder's scope is awhenonctx.path(ctx.path:find("^notes/")):filessees the base name only.autodoc.menureturns the row's handle:row:remove()takes it out. A handle reaches only the row it added: once theidis added again, the old handle removes nothing.- One budget per opening: every
whenandtitlefunction of one opening of the menu shares 200 ms, commands they run included. A row the budget doesn't reach is left out that time, and one notification says so. Keep them quick: read a variable, not a server. - An error in
whenortitle, or a title that isn't a string, leaves that row out with a notification; the menu still opens. - A Plugins-menu row takes no
files,whenor title function: that menu isn't rebuilt as it opens. - A script's words are its own: a row's title is shown as written in every language.
-- keys.lua: :stamp on Q, on SPC u s, and in the right-click menu
autodoc.command{
name = "stamp",
help = "insert the date",
run = function(ed) ed:insert(autodoc.date("%Y-%m-%d")) end,
}
autodoc.map{ mode = "normal", keys = "Q", command = "stamp" }
autodoc.map{ mode = "leader", keys = "s", command = "stamp", title = "today's date" }
autodoc.menu{ where = "editor", title = "Insert the &date", command = "stamp" }
-- under a "Notes" submenu, only in notes/ and only with a selection
local row = autodoc.menu{
where = "editor", menu = "Notes", id = "quote",
when = function(ctx) return ctx.selection and ctx.path:find("^notes/") ~= nil end,
title = function(ctx) return "&Quote in " .. ctx.workspace end,
command = "stamp",
}
-- row:remove() takes it out again
Characters: autodoc.text
Lua's #s and string.sub count bytes; the editor's columns count characters (grapheme clusters).
autodoc.text counts as the editor does, so a column worked out from a string is right for
ed:set_cursor and the ranges, whatever the script the text is in.
| Function | Returns | Notes |
|---|---|---|
autodoc.text.columns(s) |
number | How many characters (grapheme clusters) s has: the columns it takes in the editor. #s counts bytes. |
autodoc.text.sub(s, i, j) |
string | Characters i to j of s, both included, as string.sub takes bytes: j is the last by default, and a negative position counts from the end (-1 the last). |
autodoc.text.chars(s) |
list of strings | s's characters, in order: é written as e and an accent is one. |
-- tail.lua: :tail <n> selects the last n characters of the cursor's line
autodoc.command{
name = "tail",
args = "<n>",
help = "select the end of the line",
run = function(ed, args)
local l = ed:cursor()
local cols = autodoc.text.columns(ed:line(l))
local n = math.min(tonumber(args[1]) or 1, cols)
if n < 1 then error("tail: an empty line", 0) end
ed:select(l, cols - n + 1, l, cols + 1)
end,
}
Callbacks
A daemon call never waits. autodoc.call and the autodoc.<domain>.<verb> functions return at
once; the daemon's answer goes to the callback later, as cb(result, err):
errisnil, andresultis the answer as Lua values. A verb that answers nothing givesresultnil.erris a string, andresultisnil: the daemon's code and message ("-32061 not found"), or what went wrong before the daemon answered (no connection, say).- The callback runs as a command does: on AutoDoc's loop, with 2 seconds,
edreachable, and its edits one undo step of their own. The page may have changed since the call: keeped:revision()from before the call, and compare it in the callback. Equal, the text is the one the command read, positions included; else read again, or give up. - Its error is a notification,
a Lua callback failed: <verb>'s callback: <error>, since no prompt waits for it. - With no callback, a failed call is a notification:
autodoc.call(<verb>): <err>. A call that succeeds is silent.
Params.
autodoc.<domain>.<verb>(…)takes the verb's params in order. Optional ones at the end may be left out, or passed asnil. The wrong count raises at the call, naming them:autodoc.search.query takes 2 to 3 params (workspace, query, opts?), then a callback.autodoc.call(verb, params, cb)takes the params as one list:{"kb", "a.md"}. A table that is not a list raisesparams is a list, the verb's params in order. A wrong count reachescbas the daemon's-32602(invalid params).- The protocol's own verbs (
sys.hello,sys.shutdown) are refused before any call: they have noautodoc.sysfunction, andautodoc.callgivescbthe refusal.
Values cross as msgpack carries them:
| Lua | The daemon |
|---|---|
nil, a boolean, a string |
nil, a boolean, a string |
| a whole number / any other number | an integer / a float |
| a table with keys 1 to n | a list |
| any other table | a map, its keys as strings |
{} |
an empty map, or an empty list where the verb's param is a list |
a function, ed |
cannot cross: the call raises cannot cross between Lua and Go |
Answers come back the same way: a list is a table with keys 1 to n, a map a table with string keys.
-- peek.lua: :peek <path> shows a document's first line, read through autodoc.call
autodoc.command{
name = "peek",
args = "<path>",
help = "show the first line of a document of the workspace",
run = function(ed, args)
local workspace = autodoc.current_workspace()
if not workspace or not args[1] then error("peek <path>, in a workspace", 0) end
autodoc.call("doc.read", {workspace, args[1]}, function(res, err)
if err then
autodoc.notify("peek: " .. err)
return
end
autodoc.notify("peek: " .. (res.content:match("[^\n]*")))
end)
end,
}
The program's own functions
Beside the daemon's verbs, AutoDoc gives scripts its clipboard, a picker, a card, its agents and what the app itself uses. They are
there from the start, connected or not. As the daemon's do, each cb runs later on the loop as
cb(result, err), never inside the call: a refusal known at once is a callback on the next turn.
| Domain | Functions |
|---|---|
agents |
list, send, resume |
app |
stats |
clipboard |
read, write |
plugin |
run |
ui |
card, charts, pick, width |
| Call | Answers |
|---|---|
autodoc.clipboard.write(text) |
ok, err at once: the system clipboard set (a terminal's, by OSC 52) |
autodoc.clipboard.read(cb) |
cb(text, err): "" for an empty clipboard or one holding no text; err "unsupported" where AutoDoc cannot read it (in a terminal, its own paste is the way in), "timeout", "closed" |
autodoc.ui.pick({title, items}, cb) |
a picker over items, each a string or {label, detail, value} (detail is previewed, value given to cb, the item itself without one): cb(value); err "cancelled" (Esc), or "busy" while another script's picker is open |
autodoc.ui.card{title, text, every, tick} |
at once, the card's handle: the card panel (at the right) shows text, Markdown, drawn as it reads in the window's Rendered view, where a ```vega-lite fence is a chart; a terminal shows the Markdown. tick(card) runs every every ms (250 at least; none without every) while the panel is open, its first on the next turn. One card at a time: a new one replaces the one shown. :lua-reload ends it and closes the panel |
card:set(text) |
the panel shows text now; a replaced or ended card's does nothing |
card:close() |
the panel closes and the card ends: its tick stops, and set and close do nothing after |
card:open() |
true while this card is the panel's and the panel is open |
autodoc.ui.charts() |
at once: true where the window draws a vega-lite fence as a chart (--gui), false in a terminal |
autodoc.ui.width(s) |
at once: the terminal columns s takes, as AutoDoc measures its own tables (a wide character two, a combining mark none), to pad text so it lines up |
autodoc.app.stats() |
at once: the app's own process, in the keys sys.stats answers for the daemon's: {pid, version, started_at, uptime, rss, peak_rss, cpu_seconds, go_memory, go_heap, goroutines, cpus}. Sizes are bytes (rss 0 where the system cannot say: macOS), cpu_seconds the CPU used so far: a rate is two readings apart |
autodoc.agents.list() |
at once: {{codename, name, running, closing, instance, orphan}, …}, the saved profiles first; a closing one refuses a send |
autodoc.plugin.run(id, args…) |
at once, ok, err: from a plugin's own Lua package only (its lua/, loaded as plugin:<name>, docs/plugins.md), one of its plugin's [[commands]] run with args (strings), the plugin started when it is not running. err "autodoc.plugin.run: not a plugin's package" for any other script, "unknown: <id>" for an id its manifest does not declare; arguments need the plugin's protocol = 7 |
autodoc.agents.resume([agent]) |
at once, ok, err: a SIGCONT to the agent's tab, agent as send's, none for the agents panel's tab now. An agent CLI stopped by a suspend runs again; a running one isn't disturbed. err "stale", "unavailable", or why |
autodoc.agents.send(agent, text, cb) |
text pasted into the agent's prompt, no Enter added after it: agent a codename, or {codename, instance} for that exact run. A stopped agent is started once the user agrees. cb(true) once it is typed; err "cancelled", "stale" (that run is gone), "unavailable", "unbracketed", or why |
The text goes as one bracketed paste, or not at all. An agent CLI that asked for bracketed paste
gets it whole, held at its prompt for Enter. A program that didn't gets nothing, and err is
"unbracketed": text you didn't type never arrives as keys, whose newlines would be Enters. An
agent the send started is given up to 10 seconds to ask for it. AutoDoc's own usage.lua is :usage: a card of the daemon's and the app's memory and CPU and the workspace's index, ticking each second, with charts in the window. Its agents.lua uses the others: :agent-buffer sends the
page, and :agent-forward the selection, else the clipboard, to the agent running (with several,
or none, a picker asks which; one not running is started once you agree).
The daemon's verbs
Each verb the daemon offers scripts is a function autodoc.<domain>.<verb>. They are made from
the daemon's own list (sys.contracts) each time AutoDoc connects, so they are this daemon's
verbs; until it has connected, autodoc.<domain> is nil. autodoc --contracts prints each
verb's params and result, and autodoc --contracts 'todos.*' --format md one domain's, as
tables. The codes an err carries are listed at the end of --contracts.
| Domain | Verbs |
|---|---|
doc |
outline, read, read_raw, remove, rename, validate, write, write_raw |
embedding |
add, cancel_switch, catalog, estimate, log, model_context, models, providers, pull, pull_cancel, pull_status, remove, update, usage, use |
file |
locate, read, read_raw, write, write_raw |
host |
probe |
graph |
backlinks, links, map, neighborhood, resolve, unresolved |
index |
changes, documents, list, models, purge_model, reindex, status |
preference |
list, set |
ranker |
add, list, log, models, remove, update, usage, use, window |
search |
query |
sys |
capabilities, contracts, events, stats |
todos |
ack, add, assign, changes, claim, get, list, migrate_refs, move_store, open, refresh, register, release, remove, remove_malformed, resolve, scaffold, set_dir, set_kb, set_var, status, stores, update, vars |
workspace |
add, configure, embedding_policy, focus, list, of, remove, rename, scaffold, section_size, set_patterns, set_provider, set_schema, set_text_extensions, set_var, vars |
The ones a command reaches for most:
| Call | Answers |
|---|---|
autodoc.search.query(workspace, query, opts?, cb) |
{hits = {{path, breadcrumb, snippet, line_start, …}, …}, …}; opts such as {limit = 5} |
autodoc.doc.read(workspace, path, cb) |
{content, version} |
autodoc.doc.outline(workspace, path, cb) |
{version, headings = {{level, text, line, …}, …}} |
autodoc.graph.backlinks(workspace, path, options?, cb) |
{{path, raw, kind, …}, …}: the documents that link to it |
autodoc.workspace.list(cb) |
{{name, root, state, …}, …} |
autodoc.todos.add(store, task, cb) |
the task filed, with its id (Help › Tutorial…, page 11) |
Packages and capabilities
A package (lua/<name>/ with a package.toml, Lua in AutoDoc)
runs gated: each kind of call below needs its capability, granted by you once. Without it, the
call answers nil and needs <capability>: not granted, and does nothing.
| Capability | Lets a package |
|---|---|
workspace.read |
call the daemon's reading verbs: autodoc.search, autodoc.doc.read, autodoc.graph, … |
workspace.write |
call its writing verbs: write, rename and remove documents and tasks |
settings |
change AutoDoc's settings: workspaces, models, rankers, preferences |
clipboard |
autodoc.clipboard.read and .write |
agents |
autodoc.agents.list, .send and .resume |
commands |
run commands: autodoc.run, ed:run, and the keys and menu rows it binds (autodoc.map, autodoc.menu: they register either way, and say they need it when pressed) |
- Every daemon verb says which of the first three it needs (its effect, in
autodoc --contracts). A verb whose effect the daemon does not say (an older daemon) is refused to a package whatever it was granted. - A package declares what it needs in
package.toml'srequires, or as it loads,autodoc.requires{"workspace.read"}. AutoDoc asks after the scripts load. - Its own calls are its own: a function a package defines runs with the package's grants wherever it runs from (a callback, an event, a key).
require(name)loads the package'sname.luaorname/init.luaonce;a.bisa/b.lua.- A plugin's package (its
lua/) is gated the same way, underplugin:<name>; the prompt names the plugin. A package of yours with the plugin's name replaces it, and it never runs.
The sandbox
| There | Not there |
|---|---|
string, table, math |
os, io, coroutine, debug, package |
assert, error, pcall, xpcall, select, type, tostring, tonumber |
require, module |
pairs, ipairs, next, unpack, rawget, rawset, rawequal |
load, loadstring, dofile, loadfile |
setmetatable, getmetatable, collectgarbage |
getfenv, setfenv, newproxy, _printregs |
print: a notification, its values joined by tabs, as autodoc.notify |
os.time, os.date: autodoc.now and autodoc.date instead |
_G, _VERSION |
The deadline. A command's run, a callback and a file's loading each have 2 seconds. Past
them the script is stopped: <command>: the script ran past 2s and was stopped.
Errors. error("text") puts the file and line first (wc.lua:7: text); error("text", 0)
does not. A command's error shows in the prompt as <command>: <error>, and a file's that failed
to load in a notification as <file>: <error>, neither with Lua's stack trace.
- A table raised as the error is said by its
__tostring, else itsmessagefield, else its fields:error({code = 7})isan error {code = 7}. error(nil)isan error with no message.