A stop takes the window, because the next key is aimed at it

This commit is contained in:
Joseph Ferano 2026-09-22 11:12:53 +07:00
parent 45cc4d20e8
commit 9d7da2ba7a
2 changed files with 60 additions and 13 deletions

View File

@ -1,5 +1,5 @@
#+TITLE: Flan
#+TODO: TODO NEXT WAIT | DONE CANCELLED
#+TODO: TODO WAIT | DONE CANCELLED
#+ARCHIVE: ::* Archive
Every decision, open question and known gap, one =**= heading each under the
@ -470,7 +470,7 @@ an ordinary =defn= declares no environment and is byte-for-byte what it was. The
static side does not pay for the dynamic side. Rejected names: Closure, Proc, Fun,
Func, Fnptr.
** NEXT Escaping closures, allocated on the GC side
** TODO Escaping closures, allocated on the GC side
The second half of "do both". What changes is where the environment points — a
frame slot today, a collector allocation then — and the escape check goes away
with it, along with the refusals on returning, storing, pointing at and pushing a
@ -1731,6 +1731,51 @@ specification's own branch — the flag is the command and the printed shape is
error pattern, so anyone who wants one has the four lines, and the manual carries
them.
** TODO The break buffer prints a condition's fields, not its message
=ArithError — op 4, lhs -2147483648, rhs 2147483647= where the runtime's own
sentence for that op is "this value does not fit the integer type it is cast
to" (=runtime/flan_rt.c:986=). The good sentences exist and go to stderr on an
unhandled stop; the buffer renders the struct generically instead. Either put
the rendered sentence on the wire, or let a condition type carry a printer.
=op= being a bare number is the same gap — it is an enum spelled as =i32=.
** TODO A backtrace frame names the function, not the call
=fninfo= (=lib/emit.ml:185=) holds one static =loc=, the =defn='s own, and
=flan_frame= (=runtime/flan_dev.c:1011=) adds no per-call location — so two
calls to the same function from one caller are indistinguishable in the stack.
Wants the caller storing its call site into the frame before the call, which is
a field and a store on every dev-build call.
** TODO The condition buffer cannot jump to the source
It prints the source line and carets for the stop (=flan-cnr.el:197=) and lists
frames, but no key opens the file at that line. Wants RET-on-a-frame, or =M-.=,
and =next-error= over the frame list.
** TODO loop's bindings should be sequential, like let's
=check_loop= (=lib/check.ml:4741=) checks every initialiser before binding any,
so =(loop [curr-r r next-r (inc curr-r)] ...)= cannot see =curr-r= and the
refusal reads as an unknown name. Every binding form is sequential — there is
no =let*= here and there is not going to be one. Check the other binding forms
for the same gap while fixing it.
** TODO C-c C-c reports one error, not every error in the form
Whole-file paths use =Check.program_all= and report every bad declaration. The
daemon asks for the sink off (=lib/loc.ml:185=) and gets one exception, so a
function with three bad expressions takes three round trips. The sink is
per-phase; making it per-form would need a resync point inside a body.
** TODO A session should start before a program compiles
=flan dev= builds the program first, so a =main= that does not compile gives no
session. Want SBCL/nREPL order: empty image, then load a file into it. Needs a
host built with no user program, and a load-file op — the reload machinery
already builds a file as a module. Open: what =flan-rerun= does with no =main=,
and a half-loaded file. =C-c C-k= is taken by the inspector.
** TODO The daemon buffer is navigable but not coloured
=*flan*= is all plain text. =compilation-minor-mode= is on (=emacs/flan.el:822=)
so =next-error= works, but a minor mode installs no font-lock. Open: whether the
program's output should look different from the compiler's.
** TODO compilation-mode steps over the notes
Nothing sets the skip threshold, so =next-error= walks the errors and steps over
the notes, which are still parsed, coloured and clickable. Labelling them as

View File

@ -381,14 +381,13 @@ open, above its prompt, which is where whoever is typing there is looking."
;;
;; Three things had to be settled to build it, and they are settled here.
;;
;; **It displays, it does not select.** A program stops on its own clock, not
;; the editor's: the likeliest moment is in a frame of its own game loop while
;; someone is typing in another buffer. Taking the window would send the next
;; keystrokes somewhere they were not aimed, and `q' in a break buffer is not
;; what a half-typed word wanted to be. So `display-buffer': the buffer
;; appears, point does not move, and the window that had focus keeps it.
;; `flan-break-on-stop' can be set to `focus' by anyone who disagrees, and
;; to nil to go back to the mode line alone.
;; **It selects the window.** A stop is a question, the answer is a restart
;; taken in that buffer, and the next keystroke is aimed there — so going there
;; is what it wanted. This was `display' at first, on the argument that a
;; program stops on its own clock and taking the window would send keystrokes
;; somewhere they were not aimed; in use that argument lost, because every stop
;; ended in the same two keys to get to the buffer anyway. `display' is still
;; there for anyone who prefers it, and nil goes back to the mode line alone.
;;
;; **`(pause)' is not a special case.** It was worth asking — a breakpoint is
;; deliberate where an error is not, so it could be argued it has earned the
@ -409,10 +408,13 @@ open, above its prompt, which is where whoever is typing there is looking."
;; stopped and `C-c C-b' still works, so nothing is lost but the automatic
;; part.
(defcustom flan-break-on-stop 'display
(defcustom flan-break-on-stop 'focus
"What to do when the program stops.
`display' shows the break buffer without taking focus, `focus' shows it and
selects its window, and nil leaves it to the mode line and `C-c C-b'."
`focus' shows the break buffer and selects its window, `display' shows it
without taking focus, and nil leaves it to the mode line and `C-c C-b'.
A stop is the program asking a question, and the answer is a restart taken in
that buffer, so going there is what the next keystroke wants anyway."
:type '(choice (const :tag "Show it" display)
(const :tag "Show it and go there" focus)
(const :tag "Only the mode line" nil)))