Skip to content

nEmacs

nEmacs is the structural editor for Lisp on KN-86, ships in firmware v1.0 alongside the REPL (per ADR-0016, the post-CIPHER-LINE redesign that supersedes ADR-0008’s row allocation and navigation model). This page is the implementer-facing reference for keymaps, buffer model, structural primitives, and integration with the REPL and token prediction. Cartridge authors writing snippets and runtime engineers building the editor read this.


Per ADR-0016, nEmacs and the REPL are one subsystem with two buffer types — an editor buffer (nEmacs) and a REPL buffer (repl.md). Same keymap machinery, same cursor model, same FFI. Many primitives below apply equally to both buffer types; differences are flagged.


nEmacs operates on trees of s-expression nodes, not lines of text. The buffer’s invariant is that the tree is always well-formed — there are no half-typed parens, no unmatched quotes. Every editing primitive preserves the invariant.

A buffer holds:

  • Root form sequence. Top-level forms in source order. Each is a tree.
  • Cursor. A reference to the currently-focused node (could be the root sequence itself, a list, a leaf atom, or a literal-entry slot).
  • Mark / clipboard. A subtree captured by QUOTE (grab mode), available for paste.
  • Buffer name. Persisted to deck-state Lisp-source storage.
  • Modified flag. Set on any structural change; cleared on save.

Per ADR-0002, the editor arena is 16 KB while nEmacs is active, released on exit. The buffer cannot exceed that envelope; large scripts split across multiple files.


Per ADR-0016, the keymap is a Lisp alist, not a C switch. The command loop is:

scancode → input.c classifies tap/double-tap/long-press → keymap lookup
→ eval the bound handler in current mode scope

Modes are distinct keymap scopes; the cursor’s position selects the active mode without an explicit mode toggle.

ModeWhen activePrimary keys
:nemacs-navCursor on a form nodeCAR descend, CDR next-sibling, BACK ascend, CONS insert via palette, EVAL evaluate, QUOTE grab
:nemacs-literalCursor inside a literal-entry slotNumpad → Nokia multi-tap; primitive keys commit-and-return or are suppressed
:repl-promptCursor at REPL input line (repl.md)Keys build input sexp; EVAL submits
:repl-historyCursor browsing prior REPL formsCDR advance; EVAL re-execute
:grabSubtree captured for cut/copyNavigate to paste site; CONS to insert

Each keymap entry has up to three handler slots: :tap, :double-tap, :long-press (per ADR-0016 §9). Most keys bind only :tap. Example:

(define-key map 'CAR
:tap 'descend
:double-tap 'descend-to-leaf
:long-press 'show-context-path)

Default timings: double-tap window 300 ms, long-press threshold 400 ms. Both are firmware-tunable constants. Event detection is in C (input.c); Lisp handlers see only the three derived events.

Not user-rebindable. The device is a game appliance; per-key user customization is intentionally not supported. Cartridges bind their own keys but do not rebind nOSh-runtime-owned keys (notably SYS, which is firmware-reserved per ADR-0012).


These are the verbs :nemacs-nav mode dispatches to.

VerbTriggered byEffect
descendCARMove cursor to first child of current node. Leaf → beep.
next-siblingCDRMove cursor to next sibling. Last → beep.
prev-sibling(long-press CDR or context-bound)Move cursor to previous sibling.
ascendBACKMove cursor to parent. Root → beep.
descend-to-leaf(double-tap CAR)Drill all the way down the leftmost spine.
VerbTriggered byEffect
open-paletteCONSOpen predictive palette (token-prediction.md) at current position.
insert-token(palette numeric key 1–8)Insert selected token; cursor moves to inserted node; palette recomputes.
enter-literalLAMBDAOpen literal-entry slot; mode shifts to :nemacs-literal.
commit-literalENT (in literal mode)Commit buffered text as identifier / number / string; return to :nemacs-nav.
cancel-literalLAMBDA (in literal mode)Discard buffer; return to :nemacs-nav.
VerbTriggered byEffect
delete-nodeNILCut current subtree to clipboard; cursor moves to previous sibling or parent.
grabQUOTEMode shifts to :grab; current subtree is mark.
pasteCONS (in :grab)Insert clipboard at current position.
wrap(context-bound)Wrap current node in a new parent form selected from palette.
splice(context-bound)Replace current parent with its children inline.
lift(context-bound)Replace parent with current child.
transpose(context-bound)Swap current node with next sibling.

wrap, splice, lift, transpose are the standard structural-edit set. Specific key bindings finalize during the foundation implementation task (see ADR-0016 implementation queue); the verbs are stable.

VerbTriggered byEffect
infoINFODisplay current node’s type, parent, position in CIPHER-LINE modeline.
eval-currentEVALEvaluate current top-level form in cart context; errors highlight offending node.
saveSYS → SaveWrite buffer to deck-state Lisp-source storage; clears modified flag.
exitSYS → ExitTear down editor arena; return to deck home (per ADR-0002).

Per ADR-0016 §4, CIPHER-LINE hosts the editor’s modeline, palette, and echo area while nEmacs is active. This recovers the full 23 content rows of the main grid for the buffer view (gain over ADR-0008’s main-grid palette allocation).

The buffer’s tree, rendered structurally:

  • Indentation by tree depth (2 spaces per level).
  • Brackets are visual glyphs, not literal characters.
  • Cursor highlighted with reverse video on the focused node.
  • Nodes with children show a vertical guide on the left.

Rows 0 and 24 remain firmware-owned per Spec Hygiene Rule 5.

RowPurpose
1Modeline: [NEMACS] buffer-name L12/45 * [TERM:SAVE]
2Palette Row A: 8 candidates numbered [1]..[8]
3Palette Row B: continuation / selection indicator
4Echo area / Nokia multi-tap buffer

Cipher voice scrollback pauses during nEmacs editing (ADR-0016 §4). Scrollback resumes on exit.

The modeline (Row 1) displays:

  • [NEMACS] or [REPL] mode tag.
  • buffer-name — the cart-saved file name, or *scratch* if unsaved.
  • Ln/Total — current line within total form count (or REPL history position).
  • * — modified flag.
  • [TERM:VERB] — current TERM-key binding (context-sensitive per CLAUDE.md / ADR-0015).

Per ADR-0016 §6, literal text input uses T9-style multi-tap on numpad keys 2–9:

KeyLetters
2A B C
3D E F
4G H I
5J K L
6M N O
7P Q R S
8T U V
9W X Y Z
0space
1punctuation cycle (. , ' " : ; !)
  • Timeout-based commit: ~600 ms default after last keypress auto-commits the current letter.
  • Explicit commit: ENT commits and exits literal mode.
  • Different letter advances: pressing a different letter-key commits the current letter and moves to the next position.
  • Case toggle: dedicated key (proposal / numpad; finalize during implementation).
  • Delete: dedicated key (proposal * numpad; finalize during implementation).

CIPHER-LINE Row 4 echoes the string in progress with a cycle indicator ([A_][B_][C_] mid-cycle, then [AB_] after commit).

In :nemacs-literal and :prompt-text scopes, KEY_DOUBLE_TAP events are suppressed on numpad keys so multi-tap cycles cleanly. KEY_LONG_PRESS remains available (e.g., hold 2 for rapid A-B-C cycle).


Integration with REPL and token prediction

Section titled “Integration with REPL and token prediction”
  • Token prediction (token-prediction.md) drives the palette in :nemacs-nav. The same engine drives REPL completion. Cartridges contribute via emacs-extend-grammar and emacs-extend-vocabulary (NoshAPI; see ../nosh-api/primitives-by-category.md).
  • REPL handoff (repl.md) — if the player edits a snippet in nEmacs and wants to test it in the REPL, the invocation is SYS → Test or a dedicated key. Per ADR-0016 §“Known Unknowns” #6, this is TBD pending implementation; finalize during the foundation task.
  • Shared keymap machinerydefine-key, mode-scoping, and event dispatch are common code; the only difference is which buffer type is currently in focus.

Edit buffers persist as Lisp source to deck state, indexed by buffer name (per ADR-0002). Per ADR-0019, cartridge save data lives on the cartridge’s own SD card under /save/, while operator-level snippets live on the device’s microSD as part of Universal Deck State. nEmacs’s per-deck snippet library (also per ADR-0002) is in for v1 and uses the device microSD.

Save semantics:

  • SYS → Save writes the buffer’s Lisp source, line-broken at structural boundaries.
  • Buffer names are unique within a save scope; conflict prompts overwrite/rename.
  • The write is atomic — partial writes do not leave a malformed buffer on disk.
  • Loading: SYS → Load lists saved snippets; selection opens a fresh editor buffer with the loaded source.

The on-disk format is plain Lisp source (not Fe bytecode) — readable in the cart-shell SD if pulled and inspected on a host machine.


Per ADR-0008 §“Mock 3 — Error State” (carried forward by ADR-0016): structural editor errors are node-scoped, not line-scoped. The offending node renders with a box-glyph border (╌ ┌─ ┘); the modeline (CIPHER-LINE Row 1) shows the error message; the palette (CIPHER-LINE Rows 2–3) is filtered to legal tokens that fix the error.

Because the buffer invariant guarantees well-formedness, “syntax errors” in the traditional sense do not exist — errors here are semantic (unbound symbol, type mismatch from eval-current) or recoverable (literal-entry buffer didn’t parse as a number).


Per ADR-0016 §“Known Unknowns / Follow-ups”:

  • Multi-tap timeout final default (starting at 600 ms; tunable via firmware constant).
  • Case-toggle key final binding (proposal /; finalize during implementation).
  • Delete key final binding (proposal *; finalize during implementation).
  • 1-key punctuation cycle expansion if a cart needs symbols not in the cycle.
  • Cart grammar arena scope (shared 8 KB with Cipher grammar, or separate — probably separate).
  • REPL ↔ nEmacs buffer handoff key binding.
  • Cart opt-in patterns for double-tap / long-press multi-level bindings (let cart authors decide; revisit after a few carts ship).