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.
../../../adr/ADR-0016-nemacs-repl-input-model.md— current input model, keymap-as-Lisp-alist, context-polymorphic dispatch, Nokia multi-tap, double-tap / long-press event model.../../../adr/ADR-0008-nemacs-ux.md— original UX design (palette concept, structural primitives, error model). Partially superseded by ADR-0016 for navigation and row allocation; retained for design history.../../../adr/ADR-0002-player-facing-lisp.md— original commitment to nEmacs (slip canceled by ADR-0016).repl.md,token-prediction.md,../fe-lisp/,README.md.
Subsystem identity
Section titled “Subsystem identity”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.
Buffer model
Section titled “Buffer model”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.
Keymap and dispatch
Section titled “Keymap and dispatch”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 scopeModes are distinct keymap scopes; the cursor’s position selects the active mode without an explicit mode toggle.
| Mode | When active | Primary keys |
|---|---|---|
:nemacs-nav | Cursor on a form node | CAR descend, CDR next-sibling, BACK ascend, CONS insert via palette, EVAL evaluate, QUOTE grab |
:nemacs-literal | Cursor inside a literal-entry slot | Numpad → Nokia multi-tap; primitive keys commit-and-return or are suppressed |
:repl-prompt | Cursor at REPL input line (repl.md) | Keys build input sexp; EVAL submits |
:repl-history | Cursor browsing prior REPL forms | CDR advance; EVAL re-execute |
:grab | Subtree captured for cut/copy | Navigate 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).
Structural editing primitives
Section titled “Structural editing primitives”These are the verbs :nemacs-nav mode dispatches to.
Navigation
Section titled “Navigation”| Verb | Triggered by | Effect |
|---|---|---|
descend | CAR | Move cursor to first child of current node. Leaf → beep. |
next-sibling | CDR | Move cursor to next sibling. Last → beep. |
prev-sibling | (long-press CDR or context-bound) | Move cursor to previous sibling. |
ascend | BACK | Move cursor to parent. Root → beep. |
descend-to-leaf | (double-tap CAR) | Drill all the way down the leftmost spine. |
Insertion
Section titled “Insertion”| Verb | Triggered by | Effect |
|---|---|---|
open-palette | CONS | Open 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-literal | LAMBDA | Open literal-entry slot; mode shifts to :nemacs-literal. |
commit-literal | ENT (in literal mode) | Commit buffered text as identifier / number / string; return to :nemacs-nav. |
cancel-literal | LAMBDA (in literal mode) | Discard buffer; return to :nemacs-nav. |
Manipulation
Section titled “Manipulation”| Verb | Triggered by | Effect |
|---|---|---|
delete-node | NIL | Cut current subtree to clipboard; cursor moves to previous sibling or parent. |
grab | QUOTE | Mode shifts to :grab; current subtree is mark. |
paste | CONS (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.
System
Section titled “System”| Verb | Triggered by | Effect |
|---|---|---|
info | INFO | Display current node’s type, parent, position in CIPHER-LINE modeline. |
eval-current | EVAL | Evaluate current top-level form in cart context; errors highlight offending node. |
save | SYS → Save | Write buffer to deck-state Lisp-source storage; clears modified flag. |
exit | SYS → Exit | Tear down editor arena; return to deck home (per ADR-0002). |
Display layout
Section titled “Display layout”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).
Main grid (Rows 1–23)
Section titled “Main grid (Rows 1–23)”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.
CIPHER-LINE (4-row OLED)
Section titled “CIPHER-LINE (4-row OLED)”| Row | Purpose |
|---|---|
| 1 | Modeline: [NEMACS] buffer-name L12/45 * [TERM:SAVE] |
| 2 | Palette Row A: 8 candidates numbered [1]..[8] |
| 3 | Palette Row B: continuation / selection indicator |
| 4 | Echo area / Nokia multi-tap buffer |
Cipher voice scrollback pauses during nEmacs editing (ADR-0016 §4). Scrollback resumes on exit.
Mode-line conventions
Section titled “Mode-line conventions”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).
Literal entry: Nokia multi-tap
Section titled “Literal entry: Nokia multi-tap”Per ADR-0016 §6, literal text input uses T9-style multi-tap on numpad keys 2–9:
| Key | Letters |
|---|---|
| 2 | A B C |
| 3 | D E F |
| 4 | G H I |
| 5 | J K L |
| 6 | M N O |
| 7 | P Q R S |
| 8 | T U V |
| 9 | W X Y Z |
| 0 | space |
| 1 | punctuation cycle (. , ' " : ; !) |
- Timeout-based commit: ~600 ms default after last keypress auto-commits the current letter.
- Explicit commit:
ENTcommits 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 viaemacs-extend-grammarandemacs-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 isSYS → Testor a dedicated key. Per ADR-0016 §“Known Unknowns” #6, this is TBD pending implementation; finalize during the foundation task. - Shared keymap machinery —
define-key, mode-scoping, and event dispatch are common code; the only difference is which buffer type is currently in focus.
Save / load to cartridge SD
Section titled “Save / load to cartridge SD”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 → Savewrites 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 → Loadlists 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.
Error display
Section titled “Error display”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).
What’s TBD
Section titled “What’s TBD”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).