command

package
v0.9.2 Latest Latest
Warning

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

Go to latest
Published: Sep 27, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package command defines nem's command layer: the named commands the editor can run, the registry that holds them, and the Env through which a command touches editor state.

Env is an interface declared here and implemented by the editor package. A command may move point, edit the buffer, kill and yank, prompt in the minibuffer, and manage windows and buffers. It may never reach the tcell screen or the layout tree: there is deliberately no method that exposes either. That boundary is what lets every command be tested headlessly against commandtest.Fake, and it is why this package imports text, keymap and view but neither ui nor editor.

Env is an interface rather than a struct because a struct holding editor state would force this package to import editor, which imports this package.

Three things are deliberately absent from Env. They are recorded here so that their absence is not later mistaken for an oversight:

  • Lua hooks. The editor fires hooks around dispatch, keyed on command name, so save-buffer need not know that before-save exists. A RunHook method here would leak the scripting layer into every command; do not add one.
  • Paren highlighting. show-paren is computed by ui from point at render time. Nothing highlight-related belongs on Env, because Env cannot reach the screen — that is this boundary working, not a gap in it.
  • Buffer naming in text. A text.Buffer has only a Path; display names live in the editor's name-to-buffer map. That is why *scratch* and *Buffer List* are editor concepts, and why BufferName and BufferByName are methods here rather than on the buffer itself.

Index

Constants

View Source
const DefaultCapacity = 60

DefaultCapacity is the number of entries the kill ring retains, matching emacs's kill-ring-max.

Variables

View Source
var (
	// ErrQuit reports that the user pressed C-g. ReadString, ReadChar and
	// ReadKey return it when a prompt is abandoned, and commands should
	// propagate it rather than treating it as a failure.
	ErrQuit = errors.New("quit")

	// ErrNoMark is returned by commands needing a region when the buffer has
	// no mark set.
	ErrNoMark = errors.New("no mark set in this buffer")

	// ErrUnknownCommand is returned by Env.Run and Registry.Run for a name
	// that is not registered.
	ErrUnknownCommand = errors.New("no such command")

	// ErrOpenedElsewhere is returned by OpenFile when the file went to the
	// operating system's own app - a PDF to the viewer - instead of into a
	// buffer. It is not a failure: there is simply no buffer to visit, so a
	// caller stops, and nothing is reported beyond what OpenFile said itself.
	ErrOpenedElsewhere = errors.New("opened with the system app")

	// ErrSearchFailed is an incremental search ended on a pattern it could
	// not find. The search has said so already, so nothing more is reported;
	// it is returned so that a keyboard macro stops there, as in emacs.
	ErrSearchFailed = errors.New("search failed")

	// ErrBeginningOfBuffer and ErrEndOfBuffer report that point could not move
	// because it already sits at a boundary.
	//
	// These are conditions rather than faults: a caller normally reports one
	// through Echo and carries on rather than treating it as a failure. They
	// are declared here, exported, rather than privately in whichever file
	// raises them, so that the dispatcher can tell a harmless boundary from a
	// genuine error with errors.Is across package boundaries.
	ErrBeginningOfBuffer = errors.New("beginning of buffer")
	ErrEndOfBuffer       = errors.New("end of buffer")
)
View Source
var (
	// ErrKillRingEmpty is returned by Yank when nothing has been killed.
	ErrKillRingEmpty = errors.New("kill ring is empty")

	// ErrNotAfterYank is returned by YankPop when the preceding operation was
	// not a Yank or YankPop. Emacs reports this as "Previous command was not a
	// yank".
	ErrNotAfterYank = errors.New("previous command was not a yank")
)
View Source
var (
	// ErrEmptyName rejects a command with no name.
	ErrEmptyName = errors.New("command name is empty")

	// ErrNilFunc rejects a command with no implementation.
	ErrNilFunc = errors.New("command has no function")

	// ErrDuplicateCommand rejects a second registration of the same name.
	ErrDuplicateCommand = errors.New("command already registered")
)
View Source
var AutoPair = true

AutoPair turns automatic pairs on. It is set from the auto-pair setting.

View Source
var FillColumn = 70

FillColumn is the width M-q fills paragraphs to, in columns. It is set from the fill-column setting; seventy is emacs's default, and it leaves room for a diff's markers or an email's quoting inside eighty columns.

View Source
var IndentFor = defaultIndentFor

IndentFor is how b indents. The default asks, in order, the .editorconfig files that apply to its file, the indentation its text already has, and the convention of its language, taking each thing - tabs or spaces, and the width - from the first that says. It is a variable, as FillColumn is a setting, so the editor or a test can decide differently.

Functions

func CanReplace added in v0.6.0

func CanReplace(b *text.Buffer, start, end text.Pos, to string) bool

CanReplace reports whether b would let the text between start and end be replaced with to.

func CompleteDirectory added in v0.1.3

func CompleteDirectory(prefix string) []string

CompleteDirectory completes a directory name: the directory the input names, then the directories inside it. Dired's prompts use it, for where to list or where to move something to.

func ExpandPath added in v0.9.0

func ExpandPath(p string) string

ExpandPath turns a path typed at a prompt into one the filesystem knows: restarted as RestartPath says, then ~ read as the home directory and ~name as that user's. A ~ nothing can be made of is left alone - a file may be called that.

func FoldCase

func FoldCase(pat string) bool

FoldCase reports whether pat should match case-insensitively.

This is emacs's smart case: a pattern is folded while it is entirely lowercase, and becomes case-sensitive as soon as it contains an uppercase letter. So "hello" finds "Hello", but "Hello" does not find "hello".

func FoldCaseRegexp added in v0.8.0

func FoldCaseRegexp(pat string) bool

FoldCaseRegexp is FoldCase for a regular expression: a capital letter makes the search case-sensitive only where it stands for itself. The one in \S or \W is part of an escape, and that in \p{Lu} or a group's name part of the syntax; none says anything about the case wanted.

func IsDirCandidate added in v0.1.3

func IsDirCandidate(s string) bool

IsDirCandidate reports whether a filename candidate is a directory to walk into, which the completers mark with a trailing separator. It is the Descend hook for the filename prompts, so RET on a directory lists it.

The ./ entry is the exception. It names the directory already listed, so walking into it would change nothing; RET takes it as the answer instead. (The directory itself under any other name is equal to the input, and the minibuffer already takes a candidate equal to the input as the answer.)

func PairFor added in v0.2.0

func PairFor(r rune) (rune, bool)

PairFor returns the character that closes r, when r opens a pair.

func RegisterAll

func RegisterAll(r *Registry) error

RegisterAll registers every built-in command group into r.

This is the one place the groups are assembled. A group missing from here does not exist as far as the rest of the editor is concerned: M-x will not find its commands, the Lua config cannot bind them, and the default keymap will point at names that resolve to nothing. So adding a group means adding it here, and the cross-check test in the editor package fails if a bound key names a command no group registers.

func RegisterBuffers

func RegisterBuffers(r *Registry) error

RegisterBuffers adds the file, buffer, window and session commands.

func RegisterComment added in v0.2.0

func RegisterComment(r *Registry) error

RegisterComment adds the comment commands to r.

func RegisterDabbrev added in v0.2.0

func RegisterDabbrev(r *Registry) error

RegisterDabbrev adds dabbrev-expand to r.

func RegisterEdit

func RegisterEdit(r *Registry) error

RegisterEdit adds the editing commands to r.

func RegisterIndent added in v0.8.0

func RegisterIndent(r *Registry) error

RegisterIndent adds the commands that shift lines' indentation to r. indent-for-tab-command is registered with the editing commands, where TAB has always been.

func RegisterInfo added in v0.2.0

func RegisterInfo(r *Registry) error

RegisterInfo adds the commands that report on the text without changing it.

func RegisterLines

func RegisterLines(r *Registry) error

RegisterLines adds the line-moving commands.

These are nem going past emacs rather than following it: emacs has no native line move, so there is no traditional behaviour to be faithful to and these work the way the equivalent does in any modern editor.

func RegisterMotion

func RegisterMotion(r *Registry) error

RegisterMotion adds nem's motion commands to r.

Two rules govern this file and are easy to break by accident:

Character motion moves by grapheme cluster, never by rune. A combining sequence or a ZWJ emoji is one cursor stop however many runes it holds, so forward-char and backward-char go through text.Line's grapheme helpers rather than incrementing a rune index.

Vertical motion preserves the goal column and every other command clears it. The goal column is the display column the cursor is trying to keep, so that descending through a short line and out the other side returns to the original column rather than to the short line's end. It is established on the first vertical move of a run, preserved by later ones, and cleared by everything else — which is why a new command added here must call clearGoal unless it is itself vertical motion.

func RegisterParagraph added in v0.2.0

func RegisterParagraph(r *Registry) error

RegisterParagraph adds the paragraph commands to r.

func RegisterRegion

func RegisterRegion(r *Registry) error

RegisterRegion adds the mark, region, kill-ring and undo commands.

func RegisterReplace added in v0.8.0

func RegisterReplace(r *Registry) error

RegisterReplace adds the replace commands to r. query-replace itself is registered with the search commands, where it has always been.

func RegisterSearch

func RegisterSearch(r *Registry) error

RegisterSearch adds the search, replace and help commands to r.

It takes the registry rather than reading commands through Env because describe-key must report a command's documentation, and Env deliberately exposes no way to retrieve it: Env is what a command may touch at run time, while the registry is the table itself.

func RegisterSexp added in v0.2.0

func RegisterSexp(r *Registry) error

RegisterSexp adds the balanced-expression commands to r.

func RegisterSpace added in v0.2.0

func RegisterSpace(r *Registry) error

RegisterSpace adds the whitespace commands to r.

func RegisterTransform added in v0.8.0

func RegisterTransform(r *Registry) error

RegisterTransform adds the commands that rewrite the region's text to r.

func ReplaceMatch added in v0.6.0

func ReplaceMatch(b *text.Buffer, start, end text.Pos, to string) (text.Pos, error)

ReplaceMatch replaces the text between start and end with to, as query-replace does, and returns where searching goes on from: just past the replacement. Without that, replacing "a" with "aa" would find what it had written and never finish. A replacement the buffer refuses leaves the text as it was rather than half replaced.

func RestartPath added in v0.9.0

func RestartPath(p string) string

RestartPath is what a path typed at a prompt means once ~/ or // follows a directory in it: the path starts over there, at home or at the root. So find-file, which opens on the current directory, reaches a file in the home directory by typing ~/ straight after it, as emacs's minibuffer does.

func SearchBackward

func SearchBackward(b *text.Buffer, pat string, from text.Pos, fold bool) (start, end text.Pos, ok bool)

SearchBackward finds the last occurrence of pat beginning strictly before from, scanning backward. It reports the match's bounds.

func SearchForward

func SearchForward(b *text.Buffer, pat string, from text.Pos, fold bool) (start, end text.Pos, ok bool)

SearchForward finds the first occurrence of pat starting at or after from. It reports the match's bounds, and ok is false when there is none.

A pattern containing a newline never matches: v1 searches within single lines only.

func TrimTrailingWhitespace added in v0.8.0

func TrimTrailingWhitespace(b *text.Buffer, first, last int) (int, error)

TrimTrailingWhitespace deletes the spaces and tabs at the ends of lines first to last of b, as one undo step, and reports how many lines changed. It is delete-trailing-whitespace's work, and the editor's when a file's .editorconfig asks for it on saving.

A read-only buffer refuses outright. One read-only only in parts - a directory listing whose names are being edited - has the lines it keeps fixed passed over, as query-replace passes over matches it may not change.

Types

type Command

type Command struct {
	// Name is the command's identity, in kebab-case.
	Name string

	// Doc is a one-line description shown by describe-key and M-x.
	Doc string

	// Fn implements the command.
	Fn Func

	// Interactive reports whether the command appears in M-x completion.
	// Commands driven only by the event loop, such as self-insert-command,
	// are registered but not interactive.
	Interactive bool
}

Command is one named, invocable operation.

The name is the single identity a command has: M-x searches these names, the Lua config binds keys to them, and describe-bindings reports them. Keep them in emacs's kebab-case ("kill-line", not "KillLine") so a user's existing muscle memory for M-x transfers.

type CompleteFunc

type CompleteFunc func(input string) []string

CompleteFunc returns the candidates available for what has been typed so far.

It supplies the candidate UNIVERSE, not a filtered result: the minibuffer filters and ranks with fuzzy matching, so a CompleteFunc that pre-filtered by prefix would defeat it. Typing "fwc" to reach forward-char returns no prefix matches at all, and the list handed back would be empty.

The argument is still the current contents, because for some completions it selects which universe applies rather than narrowing one: a filename completion reads the directory the input names, and then offers everything in it rather than only the entries whose base matches.

func CompleteFrom

func CompleteFrom(names []string) CompleteFunc

CompleteFrom returns a CompleteFunc offering every one of names.

It does not filter. The minibuffer ranks candidates with fuzzy matching, so narrowing here would defeat it: typing "fwc" for forward-char has no prefix match, and a pre-filtered list would come back empty.

func DirectoryCompleter added in v0.1.8

func DirectoryCompleter() CompleteFunc

DirectoryCompleter is CompleteDirectory reading each directory once, for one prompt. See cachedPaths.

type Env

type Env interface {

	// Win returns the active window: the buffer being edited, where point is,
	// and which lines are on screen.
	//
	// While a prompt is open, Win reports the MINIBUFFER's window, not the
	// text window. That is deliberate and is what makes prompts editable for
	// free: C-a, C-e, C-k, M-b and the kill ring are ordinary commands, and
	// they must act on the prompt the user is typing into.
	//
	// The exception is ReadOpts.OnChange and ReadOpts.Session, which run
	// against the pre-prompt text window — see the note on OnChange. Anything
	// else that needs the text window while a prompt is open is asking for
	// trouble; there is no accessor for it, by design.
	Win() *view.Window

	// Buf returns the active window's buffer. Shorthand for Win().Buf.
	Buf() *text.Buffer

	// TextHeight reports how many rows of buffer text the active window shows,
	// excluding its modeline. Needed by every command that moves by screenfuls
	// or repositions the viewport: scroll-up-command, scroll-down-command and
	// recenter-top-bottom cannot be written without it.
	TextHeight() int

	// Arg reports the prefix argument. n is 1 when none was given, and
	// explicit distinguishes a bare command from C-u 1, which some commands
	// treat differently.
	Arg() (n int, explicit bool)

	// KillForward records text killed forward of point; KillBackward records
	// text killed backward of it. The ring handles kill-run accumulation, so a
	// command states only the direction.
	KillForward(s string)
	KillBackward(s string)

	// Yank returns the current kill-ring entry without consuming it.
	Yank() (string, error)

	// YankPop rotates to the next-older entry and returns the full replacement
	// text. It is valid only immediately after a yank.
	YankPop() (string, error)

	// LastCommand names the command that ran immediately before this one, or
	// "" at the start of a session. This is emacs's last-command.
	LastCommand() string

	// Seq returns the sequencing state shared across consecutive commands.
	//
	// The pointer is stable for the life of the session, so commands mutate
	// the struct in place: e.Seq().RecenterCycle++ is the intended idiom.
	Seq() *Seq

	// ReadString prompts in the minibuffer and returns what the user typed,
	// or ErrQuit if they pressed C-g.
	ReadString(opts ReadOpts) (string, error)

	// ReadChar prompts for a single keystroke, accepting only the runes in
	// valid (all runes when valid is empty), and returns it. This is what
	// query-replace's y/n/!/q loop and save-some-buffers are built from; a
	// ReadString would force the user to press RET after every answer.
	ReadChar(prompt string, valid []rune) (rune, error)

	// ReadKey prompts for one raw keystroke and returns it undecoded, for
	// describe-key.
	ReadKey(prompt string) (keymap.Key, error)

	// Echo shows a message in the echo area.
	Echo(format string, a ...any)

	// Buffers lists every live buffer, most recently visited first.
	Buffers() []*text.Buffer

	// BufferName is the buffer's display name: the basename of its file, or a
	// generated name such as "*scratch*" for one with no file.
	BufferName(b *text.Buffer) string

	// BufferByName finds a buffer by display name, for switch-to-buffer.
	BufferByName(name string) (*text.Buffer, bool)

	// NewBuffer creates a live, file-less buffer under the given display name.
	// list-buffers renders into one of these, as does the startup *scratch*.
	NewBuffer(name string) *text.Buffer

	// OpenFile returns the buffer visiting path, reading it from disk if it is
	// not already open. A path that does not exist yields an empty buffer, as
	// find-file does.
	OpenFile(path string) (*text.Buffer, error)

	// KillBuffer removes a buffer from the live list. It is an error to kill
	// the last remaining buffer.
	KillBuffer(b *text.Buffer) error

	// SaveBuffer writes b to disk. An empty path saves to b's own path; a
	// non-empty path saves there and adopts it, which is write-file.
	//
	// Commands go through Env rather than calling text.Buffer.Save directly so
	// that tests can intercept saves and inject failures. That matters more
	// here than anywhere else in this interface: saving is the one operation
	// where a bug costs the user the work they were trying to protect.
	SaveBuffer(b *text.Buffer, path string) error

	// SplitWindow splits the active window, side by side when vertical is
	// true and stacked otherwise, and makes the new window active.
	SplitWindow(vertical bool) error

	// OtherWindow moves the selection n windows forward in cycle order,
	// wrapping; negative n moves backward.
	OtherWindow(n int)

	// DeleteWindow removes the active window. It is an error to remove the
	// sole window.
	DeleteWindow() error

	// DeleteOtherWindows makes the active window fill the frame.
	DeleteOtherWindows()

	// Run executes another command by name, which is how M-x and the Lua
	// nem.run bridge invoke commands.
	Run(name string) error

	// CommandNames lists every interactive command name, sorted, for M-x
	// completion.
	CommandNames() []string

	// Bindings maps every bound key sequence to its command name, for
	// describe-bindings.
	Bindings() map[string]string

	// Where lists the key sequences bound to a command, for describe-key and
	// for showing a command's binding in M-x.
	Where(command string) []string

	// Quit ends the editing session. Without force it fails if any buffer has
	// unsaved changes.
	Quit(force bool) error
}

Env is the whole of the editor a command may touch.

type Func

type Func func(Env) error

Func is the signature every command implementation has. A returned error is reported to the user in the echo area; returning ErrQuit is not an error condition but an abandoned operation.

type Indent added in v0.8.0

type Indent struct {
	Tabs  bool
	Width int
}

Indent is how a buffer indents: with tabs, or with spaces, and how many columns one level takes. For tabs that is how wide a tab is taken to be, so a tab in the indentation is always exactly one level.

type Isearch

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

Isearch is one incremental search session.

It exists as an exported type because the session outlives any single call: the minibuffer keymap in the editor binds C-s and C-r while the prompt is open, and stepping to the next match is a property of the session, not of the pattern. Update is the ReadOpts.OnChange hook; Step is what C-s and C-r call; Abandon is the C-g path.

It behaves as emacs's does. A step goes on from the current match, in the direction of the key - so C-r in a forward search turns round. At the last match a step fails, and says so; the next one wraps round the end of the buffer and carries on from the other end. Typing more of the pattern extends the current match where it can; any other edit searches again from where the session opened, so shortening the pattern walks point back toward that origin rather than leaving it stranded at a match the shorter pattern no longer justifies.

func NewIsearch

func NewIsearch(e Env, backward bool) *Isearch

NewIsearch opens a session searching forward, or backward when backward is set, from the active window's current point.

func (*Isearch) Abandon

func (s *Isearch) Abandon()

Abandon restores point to where the session opened. This is C-g, and it is the behaviour users rely on most.

func (*Isearch) Advance

func (s *Isearch) Advance()

Advance steps on in the direction the search was opened in, as a repeated C-s does in a forward search.

func (*Isearch) Pattern

func (s *Isearch) Pattern() string

Pattern returns the pattern currently being searched for.

func (*Isearch) Prompt added in v0.9.0

func (s *Isearch) Prompt() string

Prompt is the search's prompt as it stands, in emacs's words: "I-search:", with "Failing" or "Wrapped" in front when that is how it is going and "backward" after when it is.

func (*Isearch) Step added in v0.9.0

func (s *Isearch) Step(backward bool)

Step goes to the next match forward, or backward, from the current one: C-s and C-r inside the prompt. Where there is none it fails, and the step after that wraps round the end of the buffer.

func (*Isearch) Update

func (s *Isearch) Update(pat string)

Update re-runs the search for a changed pattern and moves point to the match. It is ReadOpts.OnChange.

type KillRing

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

KillRing holds killed text, newest first, as a fixed-capacity ring.

Two pieces of state make its behaviour emacs-like, and both are owned here rather than by the caller so that command implementations cannot get them wrong:

  • A kill run. Consecutive kill commands accumulate into the single newest entry instead of pushing new ones, so C-k C-k C-k then C-y restores all three lines as one block. Any other command calls BreakRun to end it.
  • A yank pointer. Yank reads the entry the pointer rests on; YankPop advances it to successively older entries, wrapping around. The pointer persists after a yank run ends, so a later C-y yanks from wherever M-y left it, as emacs does. Pushing or extending an entry resets it.

The run-awareness deliberately lives here rather than in the command layer, and that is why this API is a KillForward/KillBackward pair rather than a direct port of emacs's kill-new/kill-append. Emacs makes each command decide whether it is starting a kill or extending one, which means every new kill command is one forgotten check away from silently breaking C-k C-k C-y. Here a command states only the direction it killed in and cannot get accumulation wrong. Do not "simplify" this back into a push-only Kill plus a separate Append: that reintroduces exactly the bug this shape prevents.

KillRing is not safe for concurrent use; the editor drives it from the input goroutine only.

func NewKillRing

func NewKillRing(capacity int) *KillRing

NewKillRing returns an empty ring. A capacity of zero or less is coerced to DefaultCapacity rather than rejected, so a bad config value degrades to the emacs default instead of breaking the editor.

func (*KillRing) BreakRun

func (k *KillRing) BreakRun()

BreakRun ends any kill run and invalidates YankPop. Every command that is neither a kill nor a yank calls this, which is what makes "kill, move, kill" produce two entries rather than one.

func (*KillRing) Capacity

func (k *KillRing) Capacity() int

Capacity reports the maximum number of entries retained.

func (*KillRing) KillBackward

func (k *KillRing) KillBackward(s string)

KillBackward records text killed backward of point — M-DEL, and anything else that consumes text to the left.

During a kill run it extends the newest entry on the left, so killing backward word by word yields text in reading order rather than reversed; otherwise it pushes a new entry. As with KillForward, the caller states only the direction.

func (*KillRing) KillForward

func (k *KillRing) KillForward(s string)

KillForward records text killed forward of point — C-k, M-d, or a direction-neutral kill such as kill-region.

During a kill run it extends the newest entry on the right; otherwise it pushes a new entry. Callers never decide which: they state the direction and the ring handles accumulation.

func (*KillRing) Len

func (k *KillRing) Len() int

Len reports the number of distinct entries held.

func (*KillRing) Yank

func (k *KillRing) Yank() (string, error)

Yank returns the entry the yank pointer rests on, without consuming it. It ends any kill run, so a kill after a yank starts a fresh entry, and it makes YankPop valid.

func (*KillRing) YankPop

func (k *KillRing) YankPop() (string, error)

YankPop advances the yank pointer to the next-older entry, wrapping around to the newest, and returns that entry. The result is the full replacement text: the caller removes what it last yanked and inserts this instead.

It is valid only immediately after Yank or another YankPop.

type ReadOpts

type ReadOpts struct {
	// Prompt is shown at the start of the minibuffer line, e.g. "Find file: ".
	Prompt string

	// Initial pre-fills the minibuffer with editable text.
	Initial string

	// Complete supplies completion candidates. Nil means no completion at all:
	// no candidate panel is built and TAB does nothing, which is what an
	// incremental search prompt wants.
	Complete CompleteFunc

	// RequireMatch governs what RET does when no candidate is highlighted.
	//
	// Whenever one is highlighted, RET takes it, whatever this says: that is
	// what the highlight is for, and a prompt that returned the fragment typed
	// instead would open a new file called "rea" with readme.txt highlighted
	// right under it. A candidate typed out in full is always ranked first, so
	// typing main.go never opens domain.go.
	//
	// The flag decides only the case where the typed text matches nothing. When
	// false, RET returns it as typed, so find-file and switch-to-buffer can name
	// something that does not exist yet. When true, RET refuses it with a
	// message; execute-extended-command sets this, because inventing a command
	// name is meaningless.
	//
	// M-RET returns the typed text whatever is highlighted, which is how a new
	// name is given when it happens to fuzzy-match an existing one. It is
	// refused where RequireMatch holds.
	RequireMatch bool

	// History names the kind of prompt this is - "command", "file",
	// "search" - so what is entered here is remembered with what was entered
	// at others of its kind, across sessions, and M-p and M-n bring it back.
	// Empty keeps no history.
	History string

	// HistoryFirst lists the candidates entered here before first, most
	// recent first, while nothing is typed: M-x opens on the commands used
	// last. Not for prompts whose candidates are already in a meaningful
	// order - switch-to-buffer's is by recency, and history would put the
	// current buffer first.
	HistoryFirst bool

	// Annotate, when non-nil, gives each candidate a note to show after it,
	// quietly and in a column: M-x shows each command's keys. Display only,
	// like Icon.
	Annotate func(candidate string) string

	// Rewrite, when non-nil, rewrites what has been typed after every edit:
	// a path prompt sets RestartPath, so ~/ or // typed after the directory
	// the prompt opened on starts the path over, the old part gone from view.
	Rewrite func(input string) string

	// Icon, when non-nil, gives each candidate an icon to show beside it: a
	// folder, the Go mark, a picture. Display only - the candidate is still
	// the answer - and ignored when the icons setting is off.
	Icon func(candidate string) icons.Icon

	// Descend, when non-nil, reports whether candidate is somewhere to walk
	// into rather than an answer. RET on such a candidate puts it in the prompt
	// and keeps the prompt open, so the list shows what is inside it.
	//
	// find-file sets it for directories, so RET on a highlighted src/ lists
	// src/ instead of trying to visit a directory. It is a hook rather than a
	// rule about trailing slashes because the minibuffer cannot know what its
	// candidates are: a buffer name may end in a slash and mean nothing by it.
	// M-RET ignores it, as it ignores the highlight.
	Descend func(candidate string) bool

	// OnChange, when non-nil, is called with the full contents after every
	// edit of the minibuffer — typed, backspaced, killed or yanked alike.
	//
	// This is the hook that makes search-as-you-type fall out of the ordinary
	// prompt mechanism rather than needing one of its own.
	//
	// It runs with Win reporting the PRE-PROMPT TEXT WINDOW, not the
	// minibuffer's, because a hook that reacts to the pattern needs to move
	// point in the buffer being searched. Write an OnChange that assumes the
	// minibuffer window and it will move point in the prompt instead, which
	// reads as a rendering bug rather than as the mistake it is.
	OnChange func(string)

	// Session, when non-nil, is the incremental-search session this prompt
	// drives. The minibuffer calls its Update after every edit and its Advance
	// when the search key is pressed again inside the prompt, so the prompt and
	// the search share one session.
	//
	// It exists because Advance cannot be expressed as a callback: a repeated
	// C-s must step to the next match, which is a property of the session
	// rather than of the pattern, and is not recoverable from an OnChange
	// closure. Passing the session explicitly is what keeps the editor from
	// having to guess which prompts are searches from their command names.
	//
	// Like OnChange, it runs against the pre-prompt text window. Session and
	// OnChange are independent: set both and both are called.
	Session *Isearch
}

ReadOpts configures a minibuffer prompt.

type Registry

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

Registry holds every command the editor knows.

It is the single table with three consumers: M-x searches it, the Lua config binds keys against it, and the universal argument feeds it. Lua-defined commands register here alongside the built-ins and are indistinguishable from them at the call site.

Registry is not safe for concurrent use. The editor registers at startup and runs commands from the input goroutine only, and Lua config reloads happen on that same goroutine.

func NewDefaultRegistry

func NewDefaultRegistry() (*Registry, error)

NewDefaultRegistry returns a Registry holding every built-in command.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty registry.

func (*Registry) All

func (r *Registry) All() []Command

All returns every registered command, interactive or not, in unspecified order. describe-bindings uses this to resolve names to documentation.

func (*Registry) Lookup

func (r *Registry) Lookup(name string) (Command, bool)

Lookup returns the command registered under name.

func (*Registry) Names

func (r *Registry) Names() []string

Names lists the interactive command names, sorted.

Sorted because it feeds M-x completion, where a non-deterministic order would reshuffle the candidate list between invocations.

func (*Registry) Register

func (r *Registry) Register(c Command) error

Register adds a command. It rejects an empty name, a nil Fn, and a name already registered, naming the offender in every case so a bad Lua config says which command it got wrong.

func (*Registry) Run

func (r *Registry) Run(name string, e Env) error

Run looks up name and invokes it with e, returning ErrUnknownCommand if no such command is registered and otherwise whatever the command returned.

type Seq

type Seq struct {
	// LastYankFrom and LastYankTo bound the text the most recent yank
	// inserted; HasLastYank reports whether they are meaningful.
	//
	// yank-pop cannot work without them: it must delete what the preceding
	// yank inserted before putting the rotated entry in its place.
	LastYankFrom, LastYankTo text.Pos
	HasLastYank              bool

	// RecenterCycle is recenter-top-bottom's position in its centre, top,
	// bottom cycle across successive C-l presses.
	RecenterCycle int

	// LastRune is the rune of the key that triggered the current command. The
	// event loop sets it immediately before dispatch.
	//
	// It is meaningful only for a command invoked by a self-inserting key, and
	// self-insert-command is the one command that needs it: it must insert the
	// character that was typed, and nothing else in Env reports which key ran
	// the command. ReadKey would prompt the user, which is not the same thing
	// at all.
	//
	// Carrying this here rather than as an Env method is what lets
	// self-insert-command be an ordinary registered command, reachable from
	// M-x and bindable from Lua, instead of a stub that only the event loop
	// can call.
	LastRune rune
	// contains filtered or unexported fields
}

Seq holds state that spans consecutive commands.

Commands read and mutate it in place; the editor resets whichever fields need resetting at dispatch. It is a concrete struct rather than an untyped scratch slot so that every field is type-checked — a failed type assertion inside a command is a panic, and a panic costs the user unsaved work.

A future sequencing need is a new field here, not a new method on Env.

Directories

Path Synopsis
Package commandtest provides a headless implementation of command.Env.
Package commandtest provides a headless implementation of command.Env.

Jump to

Keyboard shortcuts

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