lua

package
v0.14.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package lua hosts nem's configuration script.

The config file is a real Lua program, not a declarative format, so the same mechanism serves rebinding a key and defining a new command. A command defined in Lua registers into the same command.Registry as the built-ins and is indistinguishable from one at the call site: M-x finds it, a key can be bound to it, and describe-key reports it.

The error boundary

A script error must never take the editor down, because a panic escaping into the event loop costs the user whatever they had not saved. Every entry into Lua — loading the config, invoking a Lua-defined command, firing a hook — goes through callProtected, which runs under gopher-lua's protected call. That converts both Lua errors and arbitrary Go panics into error values carrying a Lua traceback, and the editor continues with the built-in default for whatever failed.

Concurrency

A Host is not safe for concurrent use and deliberately carries no locks. The editor runs all Lua on the input goroutine, which is the same goroutine that dispatches commands, so an LState is never touched from two places. Adding a mutex here would imply a guarantee the design does not make; if Lua ever needs to run elsewhere, the fix is to marshal it back onto the input goroutine rather than to lock the interpreter.

Capability boundary

Scripts get the nem table and the pure-computation half of the Lua standard library: base, string, table and math. They do not get io, os, debug or package, and dofile and loadfile are removed from base. A script therefore cannot open a file, spawn a process, or load more Lua from disk, and cannot reach the screen or a raw command.Env under any name. Starting strict is deliberate: relaxing a capability boundary later is harmless, while tightening one breaks scripts people have already written.

Index

Constants

View Source
const APIVersion = 1

APIVersion is the version of the nem table exposed to scripts, readable as nem.api_version. It exists so that a script written against a future, incompatible surface can detect the mismatch and say so, rather than failing in pieces.

Variables

View Source
var ErrNoEnv = errors.New("no editor context: nem.buf and nem.run are only available inside a command or hook")

ErrNoEnv reports that a script reached for editor state outside a command or hook, where there is no active buffer to act on.

Functions

func DefaultConfigPath

func DefaultConfigPath() (string, error)

DefaultConfigPath returns ~/.config/nem/init.lua.

Types

type Host

type Host struct {
	// contains filtered or unexported fields
}

Host owns the Lua interpreter and the nem table.

func New

func New(opts Options) (*Host, error)

New creates a Host with the nem table installed. It does not load the config; call LoadConfig for that, so a caller can distinguish a broken host from a broken script.

func (*Host) Close

func (h *Host) Close()

Close releases the interpreter.

func (*Host) FireHook

func (h *Host) FireHook(name string, e command.Env, b *text.Buffer) error

FireHook runs every callback registered under name, passing a table describing b. The editor calls this around dispatch, keyed on command name; the host does not interpret hook names, so which names exist is the editor's decision rather than this package's.

Every callback runs even if an earlier one fails, because one broken hook should not silently disable the others. The returned error joins whatever failed.

func (*Host) HookCount

func (h *Host) HookCount(name string) int

HookCount reports how many callbacks are registered under name.

func (*Host) HookNames

func (h *Host) HookNames() []string

HookNames lists every hook a script registered, sorted.

func (*Host) LoadConfig

func (h *Host) LoadConfig() error

LoadConfig runs the config file.

A missing file is not an error: running without a config is the normal case for a new user. A file that fails to parse or throws at top level returns an error carrying the Lua traceback, and whatever the script managed to do before failing stands — a config that binds ten keys and then has a typo on line eleven keeps its ten bindings.

func (*Host) Settings

func (h *Host) Settings() Settings

Settings returns the settings after any nem.set calls. The editor reads this once the config has loaded and applies the values itself.

func (*Host) UnresolvedBindings

func (h *Host) UnresolvedBindings() []string

UnresolvedBindings lists bindings whose command is not registered, as "spec -> command" strings, sorted.

Binding a command that does not exist yet is not rejected at bind time: a script may legitimately bind a key before defining the command, and emacs allows it too. But a key bound to nothing is a dead key, so the editor calls this after loading the config and reports whatever is left dangling. Silence would be the worst outcome — the user presses the key, nothing happens, and the config looks correct.

type Options

type Options struct {
	// Registry receives commands defined with nem.command. Required.
	Registry *command.Registry

	// Keymap receives bindings made with nem.bind. Required.
	Keymap *keymap.Map

	// ModeKeymaps are the keymaps of the editor's modes, by name, for
	// nem.bind's optional third argument: nem.bind("k", "dired-do-delete",
	// "dired"). Optional; a mode not listed here is an error to bind in.
	ModeKeymaps map[string]*keymap.Map

	// ConfigPath is the script to load. Injectable so tests never touch a real
	// ~/.config.
	ConfigPath string

	// Timeout bounds a single entry into Lua. Zero means unlimited.
	//
	// It defaults to unlimited because a legitimate hook may be slow, and an
	// editor that abandons a user's formatting hook halfway is worse than one
	// that waits. Set it if you run scripts you do not trust: without it, a
	// `while true do end` in a config hangs the editor, which the protected
	// call cannot help with because a spin is not an error.
	Timeout time.Duration
}

Options configures a Host.

type ScriptError

type ScriptError struct {
	// Msg is the Lua error message.
	Msg string

	// Traceback is the Lua stack at the point of failure, empty if unavailable.
	Traceback string

	// Kind names the failure class gopher-lua reported, such as a syntax error
	// or a runtime panic, for callers that want to distinguish them.
	Kind string
}

ScriptError is a failure inside a config script, carrying the Lua traceback.

The traceback is the whole point: a config error reported as "attempt to index a nil value" without a location is nearly useless to someone with a fifty-line init.lua, and the editor shows Error() in the echo area.

func (*ScriptError) Brief

func (e *ScriptError) Brief() string

Brief returns the message without the traceback, for a one-line echo area.

func (*ScriptError) Error

func (e *ScriptError) Error() string

type Settings

type Settings struct {
	// TabWidth is the display width of a tab stop.
	TabWidth int

	// ScrollMargin is how many lines of context to keep above and below point.
	ScrollMargin int

	// UndoStyle selects the undo model. Only "linear" exists; the field is here
	// because the setting is documented, so an unknown value must be rejected
	// with a useful message rather than silently accepted.
	UndoStyle string

	// CompletionStyle is "bottom" (the prompt at the foot of the screen with
	// its candidates below it, as emacs and Vertico draw it) or "popup" (a
	// centred panel). Same prompt state either way; only the renderer differs.
	CompletionStyle string

	// CompletionRows is how many candidates a completion panel shows at once.
	CompletionRows int

	// WhichKeyDelay is how long a prefix must stay pending before the
	// continuation panel appears, in milliseconds. Zero disables it.
	WhichKeyDelay int

	// AutosaveIdle is how many seconds of idleness trigger an autosave of every
	// modified buffer. Zero disables it.
	AutosaveIdle int

	// Backup says whether to keep a copy of a file's previous contents the first
	// time it is saved in a session.
	Backup bool

	// Clipboard is "osc52" or "off". When on, kills also reach the system
	// clipboard.
	Clipboard string

	// LineNumbers says whether each window shows a line-number gutter.
	LineNumbers bool

	// DeleteSelection makes typing or deleting replace an active region, as
	// every modern editor does. Emacs ships this off; nem ships it on.
	DeleteSelection bool

	// Syntax says whether code is coloured.
	Syntax bool

	// Theme picks the syntax palette: "dark", "light", or "auto" to guess from
	// the terminal. One palette cannot serve both grounds - colours with enough
	// contrast on black wash out on white - so nem carries two.
	Theme string

	// OpenBinary decides what opening a file that is not text does: "ask"
	// each time, hand it to the "system" app, or open it as "text" anyway.
	OpenBinary string

	// FillColumn is the width M-q fills paragraphs to.
	FillColumn int

	// AutoPair makes an opening bracket or quote insert its partner too.
	AutoPair bool

	// Icons is "on", "off" or "auto": whether file icons show in dired and the
	// file and buffer prompts. They need a Nerd Font, or a terminal that ships
	// its symbols, and "auto" turns them on only where that looks likely. A
	// script sets it with true, false or "auto".
	Icons string

	// Shell is the program M-!, M-| and compile run commands with, given the
	// command after -c. Empty means /bin/sh, as Makefiles use.
	Shell string

	// AutoRevert reads a buffer without edits again when its file changes on
	// disk.
	AutoRevert bool

	// HighlightLine lays a faint band under the line point is on.
	HighlightLine bool

	// LineWrap folds a line wider than its window into rows, rather than
	// cutting it off at the edge.
	LineWrap bool

	// Bidi is "on", "off" or "auto": whether nem lays out right-to-left
	// text itself. "auto" does unless the terminal does it; a script sets it
	// with true, false or "auto".
	Bidi string

	// KeyboardLayout is "auto", "arabic" or "off": whether keys typed with
	// another keyboard layout - C-ط for C-x on a Persian one - are read as
	// the keys they sit on. "arabic" follows the Arabic keyboard for the
	// letters it puts elsewhere than the Persian one does. A script sets it
	// with "auto", "arabic" or false.
	KeyboardLayout string
}

Settings holds the values a config script may change with nem.set.

The host validates and stores them here rather than writing them straight into their eventual homes. Two reasons: text.TabWidth is a package-level variable, so a script mutating it directly would make settings global state that tests cannot isolate; and the editor needs to know when a setting changed so it can invalidate layout caches and redraw. The editor therefore reads Settings after loading the config and applies them itself.

func DefaultSettings

func DefaultSettings() Settings

DefaultSettings returns the built-in defaults, which are what the editor uses when there is no config file or when the config failed to load.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL