Documentation
¶
Overview ¶
Package editor wires nem together and owns the event loop.
It is the only package that knows about every other one: it implements command.Env so commands can act, drives keymap lookup over decoded tcell events, arranges windows through view, and draws through ui. Nothing imports it, which is what lets every other package stay testable without a terminal.
Two responsibilities live here and nowhere else, both because putting them anywhere else means someone eventually forgets one:
- Cross-command bookkeeping at dispatch. Breaking the kill run, resetting the goal column and recording the last command name all happen in one place, so no command has to remember them. See dispatch.
- The minibuffer. A prompt is a real text.Buffer in a real view.Window, so C-a, C-e, C-k and the kill ring work inside prompts with no extra code. See minibuffer.go.
Index ¶
- Constants
- Variables
- func DecodeKey(ev *tcell.EventKey, treatCtrlHAsBackspace bool) keymap.Key
- func InstallDefaultBindings(m *keymap.Map) error
- type ClipboardMode
- type Editor
- func (e *Editor) Active() *view.Window
- func (e *Editor) AfterCommand(name string, fn func())
- func (e *Editor) Arg() (int, bool)
- func (e *Editor) AutosaveAvailable(path string) (bool, time.Time)
- func (e *Editor) AutosaveDue(now time.Time) bool
- func (e *Editor) BackupRoot() string
- func (e *Editor) BeforeCommand(name string, fn func())
- func (e *Editor) Bindings() map[string]string
- func (e *Editor) BranchOf(b *text.Buffer) string
- func (e *Editor) Buf() *text.Buffer
- func (e *Editor) BufferByName(name string) (*text.Buffer, bool)
- func (e *Editor) BufferName(b *text.Buffer) string
- func (e *Editor) Buffers() []*text.Buffer
- func (e *Editor) ClipboardMode() ClipboardMode
- func (e *Editor) CloseConfig()
- func (e *Editor) CommandNames() []string
- func (e *Editor) DeleteOtherWindows()
- func (e *Editor) DeleteSelection() bool
- func (e *Editor) DeleteWindow() error
- func (e *Editor) Dired(dir string) (*text.Buffer, error)
- func (e *Editor) DiredKeymap() *keymap.Map
- func (e *Editor) Echo(format string, a ...any)
- func (e *Editor) EmergencySave() (saved []string, err error)
- func (e *Editor) FileType(b *text.Buffer) string
- func (e *Editor) HandleEvent(ev tcell.Event)
- func (e *Editor) HandleKey(k keymap.Key)
- func (e *Editor) Keymap() *keymap.Map
- func (e *Editor) KillBackward(s string)
- func (e *Editor) KillBuffer(b *text.Buffer) error
- func (e *Editor) KillForward(s string)
- func (e *Editor) LanguageOf(b *text.Buffer) *syntax.Language
- func (e *Editor) LastCommand() string
- func (e *Editor) LoadConfig(path string) error
- func (e *Editor) Loop() error
- func (e *Editor) Message() string
- func (e *Editor) NewBuffer(name string) *text.Buffer
- func (e *Editor) NoteInput(now time.Time)
- func (e *Editor) OpenFile(path string) (*text.Buffer, error)
- func (e *Editor) OtherWindow(n int)
- func (e *Editor) Quit(force bool) error
- func (e *Editor) Quitting() bool
- func (e *Editor) ReadChar(prompt string, valid []rune) (rune, error)
- func (e *Editor) ReadKey(prompt string) (keymap.Key, error)
- func (e *Editor) ReadString(opts command.ReadOpts) (string, error)
- func (e *Editor) RecoverFile(b *text.Buffer) error
- func (e *Editor) Redraw()
- func (e *Editor) Registry() *command.Registry
- func (e *Editor) RequestClipboard()
- func (e *Editor) Ring() *command.KillRing
- func (e *Editor) Run(name string) error
- func (e *Editor) RunAutosave(now time.Time) error
- func (e *Editor) SaveBuffer(b *text.Buffer, path string) error
- func (e *Editor) SaveMemory()
- func (e *Editor) Seq() *command.Seq
- func (e *Editor) SetAutosaveIdle(d time.Duration)
- func (e *Editor) SetBackupEnabled(on bool)
- func (e *Editor) SetBackupRoot(root string)
- func (e *Editor) SetClipboardMode(m ClipboardMode)
- func (e *Editor) SetClipboardReply(data []byte)
- func (e *Editor) SetCompletionRows(n int)
- func (e *Editor) SetCompletionStyle(name string)
- func (e *Editor) SetDeleteSelection(on bool)
- func (e *Editor) SetIcons(on bool)
- func (e *Editor) SetLineWrap(on bool)
- func (e *Editor) SetOpenBinary(name string)
- func (e *Editor) SetSignals(ch <-chan os.Signal)
- func (e *Editor) SetWhichKeyDelay(d time.Duration)
- func (e *Editor) ShowStartup()
- func (e *Editor) SplitWindow(vertical bool) error
- func (e *Editor) TextHeight() int
- func (e *Editor) Tree() *view.Tree
- func (e *Editor) UseStateDir(dir string) error
- func (e *Editor) Where(cmd string) []string
- func (e *Editor) Win() *view.Window
- func (e *Editor) WrapWidth() int
- func (e *Editor) Yank() (string, error)
- func (e *Editor) YankPop() (string, error)
- type SignalError
Constants ¶
const DefaultAutosaveIdle = 30 * time.Second
DefaultAutosaveIdle is how long a modified buffer may sit untouched before its contents reach the autosave store.
Variables ¶
var ErrTooDeep = errors.New("minibuffer recursion too deep")
ErrTooDeep reports that minibuffer recursion hit its limit. It exists so a runaway Lua hook that prompts from inside a prompt fails cleanly instead of growing the Go stack until the process dies.
Functions ¶
func DecodeKey ¶
DecodeKey translates a tcell key event into a canonical keymap.Key.
This is the only place in nem where tcell meets keymap. The boundary is deliberate: keymap depends on nothing but the standard library and is therefore testable without a terminal, and every terminal-specific irregularity is absorbed here.
The returned key is always already normalized, so it can be appended to a pending sequence and looked up directly.
An event this function does not recognise — tcell's KeyInsert, mouse and paste keys, function keys past F12 — decodes to the zero Key, which keymap.Normalize can never produce. Callers should treat a zero Key as "no binding could name this" and ignore the event.
Escape versus Meta ¶
This function does NOT implement escape-sequence timing, because tcell already owns it. A terminal delivers Meta+x as ESC followed by x, which is indistinguishable from Escape then x except by arrival time. tcell's input parser resolves it with a 50ms expiry and a 60ms timer (input.go: it sets ip.expire to now+50ms and arms time.AfterFunc(60ms, ip.escTimeout)), then rewrites an ESC-prefixed key by adding ModAlt. So by the time an event reaches here the ambiguity is gone. Do not reimplement it.
Ctrl+H, and why treatCtrlHAsBackspace barely matters ¶
tcell merges the Backspace key and Ctrl+H in two independent places: its input parser maps both 0x08 and 0x7F to KeyBackspace ("case '\b', '\x7F'"), and NewEventKey rewrites KeyBackspace2 to KeyBackspace unconditionally. A real terminal therefore cannot deliver C-h as distinct from <backspace>, which means emacs's C-h help prefix is unreachable under tcell's legacy input path regardless of this flag.
The flag is honoured for the one event that can still express the difference: an explicitly constructed KeyCtrlH (tcell numbers it 72, well clear of KeyBackspace at 8). When false that decodes to C-h; when true it folds to <backspace>.
func InstallDefaultBindings ¶
InstallDefaultBindings populates m with nem's built-in keymap. It is called before the Lua config loads, so user bindings override these.
Types ¶
type ClipboardMode ¶
type ClipboardMode int
ClipboardMode selects whether kills also reach the system clipboard.
const ( // ClipboardOSC52 mirrors every kill to the terminal's clipboard, and lets // C-y take in text copied elsewhere. The zero value, so this is on unless // something turns it off. ClipboardOSC52 ClipboardMode = iota // ClipboardOff leaves the system clipboard alone, in both directions. ClipboardOff )
type Editor ¶
type Editor struct {
// contains filtered or unexported fields
}
Editor is one editing session: the buffers, the windows onto them, and the machinery that turns keystrokes into commands.
It is not safe for concurrent use, deliberately. Everything — command dispatch, keymap mutation and Lua execution — runs on the goroutine that polls for events, which is why keymap.Map and command.KillRing need no locks.
func New ¶
New returns an editor with every built-in command registered, the default bindings installed, and a single window on an empty *scratch* buffer.
scr may be nil for tests that drive dispatch without drawing; Run requires a real screen.
func (*Editor) AfterCommand ¶
AfterCommand registers fn to run immediately after name is dispatched, whether or not the command returned an error — an after-save hook still wants to know a save was attempted.
func (*Editor) AutosaveAvailable ¶
AutosaveAvailable reports whether path has an autosave holding work its saved file does not.
func (*Editor) AutosaveDue ¶
AutosaveDue reports whether enough idle time has passed, since the last keystroke, to be worth writing recovery files - or enough keystrokes, for someone who never pauses that long.
It is false when nothing has been typed since the previous autosave, so an editor left alone writes once and then stays quiet.
func (*Editor) BackupRoot ¶
BackupRoot reports where recovery files are kept, or "" when there is no store. Worth surfacing: a user who is told an autosave exists needs to know where to look.
func (*Editor) BeforeCommand ¶
BeforeCommand registers fn to run immediately before name is dispatched. Several hooks on one name run in registration order.
func (*Editor) Bindings ¶
Bindings maps key sequences to command names, for describe-bindings. In a buffer with a mode of its own - a dired listing - the mode's keys are included and win, since they are what those keys do there.
func (*Editor) BranchOf ¶
BranchOf reports the git branch b's file sits on, or empty when it is not in a repository. It is the BranchFunc handed to the renderer.
func (*Editor) BufferByName ¶
BufferByName finds a buffer by display name.
func (*Editor) BufferName ¶
BufferName returns b's display name.
func (*Editor) ClipboardMode ¶
func (e *Editor) ClipboardMode() ClipboardMode
ClipboardMode reports the current setting.
func (*Editor) CloseConfig ¶
func (e *Editor) CloseConfig()
CloseConfig releases the Lua interpreter.
func (*Editor) CommandNames ¶
CommandNames lists interactive command names, sorted, for M-x completion.
func (*Editor) DeleteOtherWindows ¶
func (e *Editor) DeleteOtherWindows()
DeleteOtherWindows makes the active window fill the frame.
func (*Editor) DeleteSelection ¶
DeleteSelection reports whether typing replaces an active region.
func (*Editor) DeleteWindow ¶
DeleteWindow removes the active window, refusing to remove the sole one.
func (*Editor) Dired ¶ added in v0.1.3
Dired returns the buffer listing dir, reading it afresh. A directory already listed reuses its buffer, so asking for it twice does not pile up copies.
func (*Editor) DiredKeymap ¶ added in v0.1.3
DiredKeymap exposes the dired keymap so the Lua layer can rebind keys in it.
func (*Editor) EmergencySave ¶ added in v0.10.0
EmergencySave writes every buffer with unsaved changes where it will be found again, and reports where: a file's to its autosave - opening the file again says so, and M-x recover-file brings it back - and a buffer with no file to a file of its own among the rescued. It is for when nem is about to end, or may be, without anyone to ask what to do with them.
func (*Editor) FileType ¶
FileType reports b's file type for the modeline: "go", "lua", "json", "md", or empty for text nem has no grammar for.
It asks the highlight cache rather than looking at the extension itself, so the segment and the colouring cannot disagree - a modeline claiming "go" over uncoloured text would read as the highlighter having failed.
func (*Editor) HandleEvent ¶
HandleEvent processes one terminal event from the top level, recording it when a keyboard macro is being defined. Exported so tests can drive the editor a keystroke at a time without a real loop.
func (*Editor) Keymap ¶
Keymap exposes the global keymap so the Lua layer can rebind keys. Mutating it is safe only from the input goroutine, which is where config loading runs.
func (*Editor) KillBackward ¶
func (*Editor) KillBuffer ¶
KillBuffer removes b from the live list, refusing to remove the last one.
Any window showing b is moved to another buffer first: leaving a window pointing at a dead buffer would be a nil-buffer panic on the next redraw.
func (*Editor) KillForward ¶
KillForward and KillBackward push onto the ring and mirror the resulting entry to the system clipboard, so C-w and M-w reach other applications. See noteKill for why the whole accumulated entry is sent rather than the fragment.
func (*Editor) LanguageOf ¶ added in v0.13.0
LanguageOf reports the language a buffer is written in, or nil: what M-; asks, to write a comment the way the language does.
func (*Editor) LastCommand ¶
func (*Editor) LoadConfig ¶
LoadConfig loads the Lua config at path, applies its settings, and wires its hooks into dispatch. An empty path uses the default location.
A broken config never takes the editor down. A script error is reported in the echo area and the editor carries on with built-in defaults for whatever failed, because losing a session to a typo in init.lua is a far worse outcome than starting unconfigured. The returned error is informational: callers normally ignore it, having already seen it echoed.
func (*Editor) Loop ¶
Loop reads events and dispatches commands until the session ends.
It is not called Run because Env.Run invokes a command by name; this is the event loop.
It selects over a channel of events and a timer rather than blocking in PollEvent, because prefix-key discovery needs to notice that a prefix has sat pending for a while - a blocking read cannot express "or nothing happened for 300ms". tcell fills the channel from its own goroutine, but there is still exactly ONE consumer, which is what lets keymap.Map, command.KillRing and the Lua interpreter stay lock-free. Nothing in here may be moved onto another goroutine without revisiting that.
The timer is rebuilt each iteration and stopped as soon as an event wins the select, so a fluent user who never pauses arms and discards a timer per keystroke and never sees a panel.
func (*Editor) NewBuffer ¶
NewBuffer creates a file-less buffer under name, or returns the existing one if that name is taken — so list-buffers reuses its buffer instead of piling up a new one per invocation.
func (*Editor) NoteInput ¶
NoteInput records that a keystroke arrived, restarting the idle clock. The event loop calls this for every key event.
func (*Editor) OpenFile ¶
OpenFile returns the buffer visiting path, reading it if it is not open yet. A path that does not exist yields an empty buffer carrying it, which is how find-file creates a new file. A directory is listed in dired, so find-file and the command line can both be pointed at one.
func (*Editor) OtherWindow ¶
OtherWindow moves the selection n windows along the cycle, wrapping.
func (*Editor) ReadChar ¶
ReadChar prompts for a single keystroke, accepting only the runes in valid.
query-replace's y/n/!/q loop is built from this: a ReadString would force the user to press RET after every answer.
func (*Editor) ReadString ¶
ReadString prompts in the minibuffer and returns what was typed, or ErrQuit if the user pressed C-g.
It enters a nested event loop rather than returning a continuation, which is what lets a prompting command read as straight-line code: find-file is ReadString then OpenFile then Visit, not a three-state machine.
Incremental search ¶
A repeated C-s inside the prompt must advance to the next match, which the prompt's keymap owns. The session it advances arrives in ReadOpts.Session, passed by the search command itself, so the editor never has to guess which prompts are searches. Session and OnChange are independent hooks and both fire; both run against the text window, not the prompt's.
func (*Editor) RecoverFile ¶
RecoverFile replaces b's contents with its autosave.
The buffer is left modified on purpose: recovered text is not what is on disk, and marking it clean would invite the user to quit believing it had been saved. The whole replacement is one undo group, so a mistaken recovery is a single C-/ away.
func (*Editor) Registry ¶
Registry exposes the command table so the Lua layer can register commands into the same table the built-ins live in.
func (*Editor) RequestClipboard ¶
func (e *Editor) RequestClipboard()
RequestClipboard asks the terminal for its clipboard contents.
The answer is asynchronous and may never come: OSC 52 reads are far less widely implemented than writes, and terminals that do support writing often refuse to read on the grounds that a program should not be able to exfiltrate whatever you last copied. So this cannot be made to look synchronous, and yanking does not wait on it — C-y reads through the desktop's clipboard tools instead (see takeSystemClipboard). When a reply does arrive, SetClipboardReply puts it at the front of the ring so the next C-y picks it up.
func (*Editor) Run ¶
Run invokes another command by name. This is M-x's mechanism and Lua's nem.run.
It routes through dispatch so the invoked command gets the same bookkeeping as a keystroke would — otherwise M-x kill-line would leave the kill run open and fuse with the next kill.
func (*Editor) RunAutosave ¶
RunAutosave writes every modified buffer that has a file to the autosave store.
Errors are reported through the echo area and returned for tests; the event loop can ignore the return. A failure must be visible, because a user who believes they have recovery files and does not is worse off than one who knows they have none.
func (*Editor) SaveBuffer ¶
SaveBuffer writes b to disk. An empty path saves to b's own path; a non-empty path saves there and adopts it.
On failure the buffer must come out exactly as it went in: still modified, and still pointing at its old path. text.Buffer.SaveAs assigns the path before attempting the write, so a failed write would otherwise leave the buffer claiming a file it was never written to — and the user would then believe a later successful C-x C-s had saved somewhere it had not. Two data-safety steps happen here, in this order and for these reasons. A file something else has changed is not overwritten without asking, because silently discarding an external edit is the worst thing this function could do. And the file's previous contents are copied to the backup store before the write, never after — a backup taken afterwards holds the new contents and preserves nothing. See safety.go.
func (*Editor) SaveMemory ¶ added in v0.4.0
func (e *Editor) SaveMemory()
SaveMemory is for the end of a session.
func (*Editor) SetAutosaveIdle ¶
SetAutosaveIdle sets how long a modified buffer may sit untouched before it is autosaved. Zero disables autosave.
func (*Editor) SetBackupEnabled ¶
SetBackupEnabled turns backups and autosaves on or off.
func (*Editor) SetBackupRoot ¶
SetBackupRoot points the backup and autosave store at root. Tests use this to keep out of the real state directory.
func (*Editor) SetClipboardMode ¶
func (e *Editor) SetClipboardMode(m ClipboardMode)
SetClipboardMode chooses whether kills reach the system clipboard.
func (*Editor) SetClipboardReply ¶
SetClipboardReply accepts the terminal's answer to RequestClipboard. The event loop routes *tcell.EventClipboard here.
The reply becomes the newest kill-ring entry, which is what makes it reachable by the ordinary C-y rather than needing a second yank command. Text nem itself put on the clipboard is ignored: terminals echo it straight back, and adding it again would duplicate an entry the ring already holds.
func (*Editor) SetCompletionRows ¶
SetCompletionRows sets how many candidates a panel shows at once.
func (*Editor) SetCompletionStyle ¶
SetCompletionStyle selects the emacs-shaped bottom rendering or the popup. The value has already been validated by the config host; an unrecognised one here is a programming error rather than a user's typo, so it is ignored rather than reported to someone who cannot act on it.
func (*Editor) SetDeleteSelection ¶
SetDeleteSelection turns delete-selection behaviour on or off. It is on by default; off restores emacs's own behaviour, where a selection is inert.
func (*Editor) SetIcons ¶ added in v0.1.5
SetIcons turns file icons on or off, in the prompts and in every listing, open ones included. They need a Nerd Font, or a terminal that carries its symbols; without one each icon is an empty box, which is what this is for.
func (*Editor) SetLineWrap ¶ added in v0.11.0
SetLineWrap turns line wrapping on or off. The goal column each window keeps means a column of the line unwrapped and of the row wrapped, so it is let go of; and a window scrolled sideways or part way down a line starts its view afresh.
func (*Editor) SetOpenBinary ¶ added in v0.1.4
SetOpenBinary sets what happens to a file that is not text: "ask", "system" or "text". The config host has validated the value; anything else is ignored.
func (*Editor) SetSignals ¶ added in v0.10.0
SetSignals has the event loop end the session when a signal arrives on ch. The caller chooses the signals, with signal.Notify.
func (*Editor) SetWhichKeyDelay ¶
SetWhichKeyDelay sets how long a prefix sits pending before the panel appears. Zero disables prefix-key discovery entirely.
func (*Editor) ShowStartup ¶
func (e *Editor) ShowStartup()
ShowStartup asks for the welcome panel on the next frame.
The caller decides rather than the editor, because only the caller knows whether a file was named on the command line - the editor sees an empty *scratch* either way, and showing a welcome over a file someone asked for would be an interruption rather than a greeting.
func (*Editor) SplitWindow ¶
SplitWindow splits the active window and selects the new half.
func (*Editor) TextHeight ¶
TextHeight reports the rows of buffer text the active window shows. A prompt is a single row.
func (*Editor) UseStateDir ¶ added in v0.4.0
UseStateDir loads what was remembered in dir and keeps remembering there. Until it is called nothing persists, which is what keeps a test from reading or writing anyone's real history.
func (*Editor) Where ¶
Where lists the sequences bound to a command, for describe-key. A global key the current mode has taken over is left out: in dired, C-n no longer runs next-line.
func (*Editor) Win ¶
Win returns the minibuffer's window while a prompt is active, and the active text window otherwise.
This one substitution is what makes editing commands work inside a prompt: C-a, C-e, C-k and yank all act on whatever Win reports, so a prompt needs no parallel implementation of any of them.
func (*Editor) WrapWidth ¶ added in v0.11.0
WrapWidth reports the width lines are wrapped at in the active window: its text area's, or 0 when lines are not wrapped. A prompt's one line never is.
type SignalError ¶ added in v0.10.0
type SignalError struct {
Signal os.Signal
// Saved lists where the unsaved buffers were written.
Saved []string
// Err says what could not be written, if anything.
Err error
}
SignalError is what Loop returns when a signal ended the session.
func (*SignalError) Error ¶ added in v0.10.0
func (s *SignalError) Error() string
Source Files
¶
- arg.go
- bidi.go
- bindings.go
- classify.go
- clipboard.go
- compile.go
- completion.go
- config.go
- decodekey.go
- delsel.go
- dired.go
- display.go
- editor.go
- external.go
- fault.go
- finders.go
- grep.go
- highlight.go
- hooks.go
- imenu.go
- indent.go
- kmacro.go
- languages.go
- layout.go
- locations.go
- loop.go
- memory.go
- minibuffer.go
- paste.go
- process.go
- process_unix.go
- project.go
- recent.go
- revert.go
- safety.go
- shell.go
- sysclip.go
- vcs.go
- wdired.go
- whichkey.go