From 64ce9ec05b8aec79de08a473a890afb7b2c28db3 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 12 Sep 2026 09:31:27 +0700 Subject: [PATCH] A manual for the Emacs side, in plain language The site's Emacs section is a key table and four paragraphs -- a reference for someone who already knows the shape. This is the other document: how to set it up, what the loop actually is, what each buffer's own keys do, and what to do when something is refused. The three buffer keymaps were never written down anywhere a user would look; they were only in the define-key calls. --- emacs/MANUAL.md | 323 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 emacs/MANUAL.md diff --git a/emacs/MANUAL.md b/emacs/MANUAL.md new file mode 100644 index 0000000..95c5b98 --- /dev/null +++ b/emacs/MANUAL.md @@ -0,0 +1,323 @@ +# Flan in Emacs + +A manual for the editor side. It assumes you know Emacs and nothing about how +Flan's dev loop is built — if you want that, `BUILT.md` has it. + +The short version: you start a program, you keep it running, and you change it +while it runs. Everything below is a variation on that. + +--- + +## Setting up + +Put `emacs/` on your load path and require the mode. Nothing else is needed — +`flan-mode` pulls in the rest as you use it. + +```elisp +(add-to-list 'load-path "~/Development/flan/emacs") +(require 'flan-mode) +``` + +`.flan` files open in `flan-mode` after that. The client, the REPL, the +inspector and the conditions buffer all load on first use, so requiring the mode +does not drag them in. + +One optional extra: `flan-dape.el` gives you lldb through +[dape](https://github.com/svaante/dape). It is separate on purpose — `flan-mode` +works without dape installed, and `C-c C-g` only exists once you load it. + +```elisp +(require 'flan-dape) ; only if you have dape +``` + +You also need the `flan` binary on your `PATH`. If it is somewhere else, set +`flan-dev-command`. + +--- + +## Starting a program + +Two ways in, and they are different. + +**`M-x flan-dev`** starts the program for you. It runs `flan dev` on a file, +waits for it to come up, and connects. This is the normal way. + +**`C-c C-z`** (`flan-connect`) attaches to a program that is *already* running — +one you started in a terminal, say. It looks for a `.flan-dev.sock` file in the +current directory and upward, so from anywhere in the project it finds the one +program you have running. + +Either way, when you are connected the modeline says so and Emacs tells you what +it found: + +``` +flan dev: connected to ~/Development/flan/.flan-dev.sock (47 functions, 2 globals) +``` + +**`C-c C-q`** disconnects without stopping the program. **`M-x flan-dev-quit`** +stops the program too — but only one this Emacs started. A daemon you launched +in a terminal is not Emacs' to kill, and it will say so rather than do something +surprising. + +--- + +## The loop + +This is the part the whole project exists for. + +### `C-c C-c` — change one function + +Put point anywhere in a top-level form and press it. The form is recompiled and +installed into the running program, which does not stop, restart, or lose +anything. The next time that function is called, the new one runs. + +It works on the *buffer text*, not the saved file, so you do not have to save +first. + +### `C-x C-e` — evaluate an expression + +The expression before point is compiled, run **inside the running program**, and +its value printed in the echo area. Not a copy of the program, not a simulation — +the actual process, with its actual state. + +So in a game you can type `(len enemies)` and get the real number. + +### `C-c C-k` — the whole buffer + +The whole buffer, sent as **one** module rather than as a form at a time. That +matters: a `defvar` and the function that uses it have to arrive together, or the +function refers to storage that does not exist yet. + +Use this when you have changed several things at once, or when you have added a +new global. + +### When it lands + +Changes install at a frame boundary — the program finishes what it is doing and +picks up the new code at a clean point. You do not have to think about this +except to know that a change is not necessarily live the same *millisecond* you +press the key. + +--- + +## The REPL + +**`C-c C-r`** opens `*flan-repl*`. It is a comint buffer; every line goes through +the same machinery `C-x C-e` uses, so anything you can evaluate there you can +evaluate here. + +One thing to know: it is **program-scoped**, not buffer-scoped. Names are the +running program's names. In sand you write `sim/settle`, not `settle`, because +that is what the program calls it. + +**`C-c C-o`** shows `*flan-output*` — whatever the program itself has printed. +That is separate from the REPL, because the program's stdout belongs to the +program. + +--- + +## When the program stops + +If the program hits an error nobody handled, it does not die. It stops, on the +frame where the error happened, and waits. + +The modeline says `stopped`. Everything else in Emacs behaves normally — a +stopped program looks like a running one from anywhere else. + +### `C-c C-b` — the conditions buffer + +This is where you decide what to do. It shows three things, in this order: + +1. **the condition** — what went wrong +2. **the restarts** — your choices +3. **the stack** — the explanation + +That order is deliberate. The decision in front of you is which restart to take; +the stack is why. A debugger that opens with forty frames has buried the decision +under the explanation. + +Keys in that buffer: + +| Key | Does | +|---|---| +| `RET` | take the restart at point | +| `0`–`9` | take that restart by number | +| `TAB` / `n` | next restart | +| `S-TAB` / `p` | previous | +| `f` | fold a stack frame open or closed | +| `i` | inspect a local variable | +| `a` | abort | +| `g` | read the program again | +| `q` | close the buffer | + +**Why restarts are numbered.** A restart is taken by *position*, not by name. +Two frames can offer a restart with the same name — `retry` is common — and a +name resolves to the innermost one. So an outer `retry` is real, is on the list, +and cannot be reached by name. The numbers are how you reach it. Restarts that +genuinely cannot be taken are shown and refused with a reason rather than +silently omitted. + +**`C-c C-M-b`** is the same choice as a quick one-key prompt, when you already +know which restart you want and do not need the buffer. + +After you choose, the program carries on from the restart. It never unwound, so +everything it had is still there. + +--- + +## Looking at values + +**`C-c C-i`** inspects a value. Give it an expression; you get its fields, one +per line. + +| Key | Does | +|---|---| +| `RET` | go into the field at point | +| `l` | back out one level | +| `g` | read it again | +| `TAB` / `n` | next field | +| `S-TAB` / `p` | previous field | +| `q` | close | + +Two things worth knowing, because they are unlike other inspectors. + +**The view is never stale.** Every step reads the program as it is *now*. Most +inspectors show you the object as it was when you opened it. + +**The root expression runs again on every step.** Going into a field sends a new +expression — `(.pos b)` where the last one was `b`. Appending a field name is +harmless, but the root need not be: if you inspect `(spawn-enemy)`, you spawn one +per keystroke. That is why there is no auto-refresh and why `g` is a key you +press rather than a timer. + +--- + +## When a change is refused + +Some changes cannot be made to a running program. Change a struct's layout, or a +function's signature, and the daemon refuses and tells you to restart. It is +being honest: the session's idea of your types has to describe the memory of the +process it is talking to, and after a layout change it no longer does. + +**`C-c C-x`** (`flan-dev-restart-program`) is the way out. It stops the program, +rebuilds from source, starts it again, and reconnects. + +The program's state goes with it. That is the whole cost, and it is why this is a +separate key rather than something `C-c C-c` quietly falls back to — losing your +game's state should be something you asked for. + +--- + +## Under the debugger + +**`C-c C-g`** (`flan-debug`, needs `flan-dape.el`) builds the current file with +DWARF and stops it at `main` under lldb. + +Breakpoints are ordinary dape breakpoints set in the `.flan` buffer — the line +table names your Flan file, not the generated LLVM IR. `dape-breakpoint-toggle` +on a line, or `dape-breakpoint-global` to break on a function without hunting for +its first line. + +lldb needs no plugin to read Flan values. A Flan struct *is* a C struct, a local +is an ordinary stack slot, and there are no tag words or object headers anywhere, +so lldb's own C support prints them correctly with nothing taught to it. + +Local variables show under their real names. One caveat: if you shadow a name — +a `let` inside a `let`, both called `v` — both appear, the inner one as `v~2`, +but plain `v` still answers with the *outer* one. Read `v~2` when you are inside +the inner binding. + +--- + +## Getting around + +| Key | Does | +|---|---| +| `M-.` | jump to where a name is written | +| `M-,` | jump back | +| `C-c C-d` | what the running program currently defines | +| `C-c C-v` | help on the name at point | +| `C-c C-a` | disassemble a function; `C-u` first for its LLVM IR | + +Completion, eldoc and `M-.` all read one cached answer rather than asking the +program per keystroke. It refreshes at the two moments the answer can have +changed: when you connect, and after an evaluation the daemon accepted. + +--- + +## When something is wrong + +**An error draws an overlay** where it happened, with the message. It clears the +next time that buffer's evaluation is accepted — so it disappears when you fix +the thing rather than when you dismiss it. + +**"No .flan-dev.sock found above this buffer"** — nothing is running, or you are +outside the project. Start one with `M-x flan-dev`. + +**The modeline says nothing about a program** — you are not connected. `C-c C-z`. + +**A restart you picked did nothing** — it should not happen silently any more, +but if a restart is genuinely unreachable the buffer marks it and refuses with a +reason. Read the reason. + +**The stack pane says it cannot show frames** — that is a real limit, not a bug. +The conditions buffer reaches the program over a socket, and a socket cannot read +another process's stack. The program stopped itself; it is not being debugged. +Use `C-c C-g` if you need frames. + +--- + +## Full key reference + +| Key | Does | +|---|---| +| `C-c C-c` | the top-level form at point, recompiled and installed | +| `C-c C-k` | the whole buffer, as one module | +| `C-x C-e` | the expression before point, evaluated in the running program | +| `C-c C-z` | connect (finds `.flan-dev.sock` upward) | +| `C-c C-q` | disconnect | +| `C-c C-o` | the running program's own output | +| `C-c C-r` | a prompt on the running program | +| `C-c C-b` | a stopped program: condition, restarts, stack | +| `C-c C-M-b` | the same restarts, as a one-key prompt | +| `C-c C-i` | inspect a value | +| `C-c C-a` | disassemble; `C-u` first for LLVM IR | +| `C-c C-g` | debug under lldb, through dape | +| `C-c C-d` | what the running program defines | +| `C-c C-v` | help on the name at point | +| `C-c C-x` | rebuild, relaunch, reconnect | +| `M-.` / `M-,` | where a name is written / back | + +Commands with no key: `M-x flan-dev` (start a program), `M-x flan-dev-quit` +(stop it). + +--- + +## Settings + +| Variable | Default | What it is | +|---|---|---| +| `flan-dev-command` | `"flan"` | the compiler binary | +| `flan-dev-socket-name` | `".flan-dev.sock"` | what `C-c C-z` searches for | +| `flan-dev-echo-result` | `t` | print `C-x C-e`'s value in the echo area | +| `flan-dev-names-shown` | `4` | how many names to list before summarising | +| `flan-dev-output-buffer` | `"*flan-output*"` | where the program's output goes | +| `flan-dev-poll-interval` | `1.0` | seconds between checks for whether it stopped | +| `flan-dev-daemon-buffer` | `"*flan-dev*"` | the daemon's own log | +| `flan-dev-start-timeout` | `60` | seconds to wait for a program to come up | + +--- + +## The files + +| File | What it is | +|---|---| +| `flan-mode.el` | the major mode: syntax, indentation, imenu, the keymap | +| `flan-dev.el` | the client — the socket, evaluation, xref, eldoc, completion | +| `flan-repl.el` | the `*flan-repl*` buffer | +| `flan-cnr.el` | the conditions-and-restarts buffer | +| `flan-inspect.el` | the value inspector | +| `flan-dape.el` | lldb through dape; optional | + +There is no Flan parser in any of them. The client sends text and the compiler +answers; anything that needs to know what a form means asks.