The break buffer hides the prelude's frames and opens the source behind a frame
This commit is contained in:
parent
9c0e659209
commit
2e58880160
20
TODO.org
20
TODO.org
@ -1797,9 +1797,12 @@ its slots in scope. The slots are already on the frame and already readable
|
|||||||
(=flan_dev_frame_slot=); what is missing is checking an expression against that
|
(=flan_dev_frame_slot=); what is missing is checking an expression against that
|
||||||
frame's names and types.
|
frame's names and types.
|
||||||
|
|
||||||
** TODO The stack lists prelude frames
|
** DONE The stack lists prelude frames
|
||||||
=0: pause <prelude>:151:7= is the breakpoint the author wrote, not a step in
|
CLOSED: [2026-09-25]
|
||||||
their program. Prelude frames want hiding by default, with a key to show them.
|
A frame whose location is =<prelude>= is hidden by default, and a line in its
|
||||||
|
place counts the hidden run; =P= shows them. A hidden frame keeps its index,
|
||||||
|
because =locals= and the inspector are asked by it. Rules out renumbering the
|
||||||
|
visible frames.
|
||||||
|
|
||||||
** TODO There is no stepper
|
** TODO There is no stepper
|
||||||
=(pause)= stops and offers restarts, frames, locals and the inspector, but
|
=(pause)= stops and offers restarts, frames, locals and the inspector, but
|
||||||
@ -1832,10 +1835,13 @@ 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
|
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.
|
a field and a store on every dev-build call.
|
||||||
|
|
||||||
** TODO The condition buffer cannot jump to the source
|
** DONE The condition buffer cannot jump to the source
|
||||||
It prints the source line and carets for the stop (=flan-cnr.el:197=) and lists
|
CLOSED: [2026-09-25]
|
||||||
frames, but no key opens the file at that line. Wants RET-on-a-frame, or =M-.=,
|
RET (and =v=) on a frame or on the stop's =at= line opens the file there; TAB
|
||||||
and =next-error= over the frame list.
|
alone folds a frame's locals. The buffer is a =next-error= buffer, made current
|
||||||
|
when it opens, so =M-g M-n= walks the stop and then each frame with a file.
|
||||||
|
Refusals — the prelude, a relative path, a missing file — are one function
|
||||||
|
shared with =M-.=.
|
||||||
|
|
||||||
** TODO loop's bindings should be sequential, like let's
|
** TODO loop's bindings should be sequential, like let's
|
||||||
=check_loop= (=lib/check.ml:4741=) checks every initialiser before binding any,
|
=check_loop= (=lib/check.ml:4741=) checks every initialiser before binding any,
|
||||||
|
|||||||
@ -343,11 +343,13 @@ Keys in that buffer:
|
|||||||
|
|
||||||
| Key | Does |
|
| Key | Does |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `RET` | take the restart at point |
|
| `RET` | take the restart at point; on a frame or on the `at` line, visit the source |
|
||||||
| `0`–`9` | take that restart by number |
|
| `0`–`9` | take that restart by number |
|
||||||
| `TAB` / `n` | next restart |
|
| `TAB` / `n` | next restart |
|
||||||
| `S-TAB` / `p` | previous |
|
| `S-TAB` / `p` | previous |
|
||||||
| `f` | fold a stack frame open or closed |
|
| `f` | fold a stack frame open or closed |
|
||||||
|
| `v` | visit the source of the frame at point |
|
||||||
|
| `P` | show or hide the prelude's frames |
|
||||||
| `i` | inspect the local or global at point |
|
| `i` | inspect the local or global at point |
|
||||||
| `a` | abort |
|
| `a` | abort |
|
||||||
| `g` | read the program again |
|
| `g` | read the program again |
|
||||||
@ -377,6 +379,17 @@ takeable only when the program itself was already stopped — abandon the
|
|||||||
evaluation and the program's own break comes back with them on offer. If the
|
evaluation and the program's own break comes back with them on offer. If the
|
||||||
program was running, abandon and call the code again.
|
program was running, abandon and call the code again.
|
||||||
|
|
||||||
|
**The stack.** Frames are numbered innermost first. A frame of a prelude
|
||||||
|
function — `pause` is one, so every breakpoint has one — is hidden, and a line
|
||||||
|
in its place says how many were hidden; `P` shows them. A hidden frame keeps
|
||||||
|
its number, so the numbers either side of it have a gap.
|
||||||
|
|
||||||
|
A frame's location is where its function is written, and the `at` line under
|
||||||
|
the condition is the expression that stopped. `RET` on either opens that file
|
||||||
|
at that line. `next-error` (`M-g M-n`) walks the same list from any buffer
|
||||||
|
while the program is stopped: the stop first, then each frame outward, skipping
|
||||||
|
frames that have no file.
|
||||||
|
|
||||||
**`C-c C-M-b`** is the same choice as a quick one-key prompt, when you already
|
**`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.
|
know which restart you want and do not need the buffer.
|
||||||
|
|
||||||
|
|||||||
@ -51,8 +51,9 @@
|
|||||||
;; expand in place, TAB to fold, everything reachable from the keyboard,
|
;; expand in place, TAB to fold, everything reachable from the keyboard,
|
||||||
;; `q' to go. What was not taken from it is the cause chain — CIDER walks
|
;; `q' to go. What was not taken from it is the cause chain — CIDER walks
|
||||||
;; `ex-cause' because a JVM exception wraps another one, and a Flan condition
|
;; `ex-cause' because a JVM exception wraps another one, and a Flan condition
|
||||||
;; wraps nothing — and its filters, which exist because a JVM backtrace is
|
;; wraps nothing. Of its filters one was taken: the prelude's frames are
|
||||||
;; mostly frames nobody wrote.
|
;; hidden until `P' shows them, because `(pause)' is itself a prelude function
|
||||||
|
;; and its frame is the breakpoint, not a step of the program.
|
||||||
;;
|
;;
|
||||||
;; Everything this buffer cannot fill in is drawn as a section that says so, by
|
;; Everything this buffer cannot fill in is drawn as a section that says so, by
|
||||||
;; name, with what it would take. A missing section is indistinguishable from
|
;; name, with what it would take. A missing section is indistinguishable from
|
||||||
@ -65,6 +66,7 @@
|
|||||||
(require 'subr-x)
|
(require 'subr-x)
|
||||||
|
|
||||||
(declare-function flan--request "flan" (form))
|
(declare-function flan--request "flan" (form))
|
||||||
|
(declare-function flan-visit-loc "flan" (loc subject))
|
||||||
(declare-function flan-inspect "flan-inspect" (expr))
|
(declare-function flan-inspect "flan-inspect" (expr))
|
||||||
(declare-function flan-inspect-slot "flan-inspect" (frame slot name))
|
(declare-function flan-inspect-slot "flan-inspect" (frame slot name))
|
||||||
|
|
||||||
@ -147,6 +149,10 @@ so does a list long enough to have been truncated."
|
|||||||
"The plist this buffer was last drawn from.")
|
"The plist this buffer was last drawn from.")
|
||||||
(defvar-local flan-cnr--open nil
|
(defvar-local flan-cnr--open nil
|
||||||
"Indices of the frames whose locals are showing.")
|
"Indices of the frames whose locals are showing.")
|
||||||
|
(defvar-local flan-cnr--show-prelude nil
|
||||||
|
"Whether the stack section draws the prelude's own frames.
|
||||||
|
Nil by default: a prelude frame is library code the program called, most
|
||||||
|
often `pause' itself, and `P' shows them.")
|
||||||
|
|
||||||
(defun flan-cnr--unavailable (key)
|
(defun flan-cnr--unavailable (key)
|
||||||
(propertize (concat " not available — " (flan-cnr--why key) "\n")
|
(propertize (concat " not available — " (flan-cnr--why key) "\n")
|
||||||
@ -193,7 +199,8 @@ The frame lines below say where each call was; this is the only record of the
|
|||||||
indexing or the division itself, so it sits directly under the headline."
|
indexing or the division itself, so it sits directly under the headline."
|
||||||
(let ((site (plist-get state :site)))
|
(let ((site (plist-get state :site)))
|
||||||
(when site
|
(when site
|
||||||
(insert (propertize (format "at %s\n" site) 'face 'shadow))
|
(insert (propertize (format "at %s\n" site) 'face 'shadow
|
||||||
|
'flan-cnr-loc site 'mouse-face 'highlight))
|
||||||
(let ((source (plist-get state :source))
|
(let ((source (plist-get state :source))
|
||||||
(lc (flan-cnr--site-line-col site)))
|
(lc (flan-cnr--site-line-col site)))
|
||||||
(when (and source lc)
|
(when (and source lc)
|
||||||
@ -362,26 +369,61 @@ indexing or the division itself, so it sits directly under the headline."
|
|||||||
(list 'flan-cnr-abort t 'mouse-face 'highlight))))
|
(list 'flan-cnr-abort t 'mouse-face 'highlight))))
|
||||||
(insert "\n"))
|
(insert "\n"))
|
||||||
|
|
||||||
|
(defun flan-cnr--prelude-frame-p (fr)
|
||||||
|
"Whether FR is a frame of a prelude function.
|
||||||
|
The prelude is compiled into every program and its functions report their
|
||||||
|
location as `<prelude>:LINE:COL'. `(pause)' is one: the frame it pushes is the
|
||||||
|
breakpoint the program's author wrote, not a step of the program."
|
||||||
|
(let ((loc (plist-get fr :loc)))
|
||||||
|
(and (stringp loc) (string-prefix-p "<prelude>:" loc))))
|
||||||
|
|
||||||
|
(defun flan-cnr--insert-hidden (n)
|
||||||
|
"The line standing in for N consecutive prelude frames that are hidden."
|
||||||
|
(let ((start (point)))
|
||||||
|
(insert (propertize
|
||||||
|
(format " %d prelude frame%s hidden — P shows %s\n"
|
||||||
|
n (if (= n 1) "" "s") (if (= n 1) "it" "them"))
|
||||||
|
'face 'shadow))
|
||||||
|
(add-text-properties start (point)
|
||||||
|
(list 'flan-cnr-hidden t 'mouse-face 'highlight))))
|
||||||
|
|
||||||
(defun flan-cnr--insert-stack (state)
|
(defun flan-cnr--insert-stack (state)
|
||||||
(flan-cnr--section "Stack (innermost first) — TAB folds a frame's locals:")
|
(flan-cnr--section
|
||||||
|
"Stack (innermost first) — RET visits a frame's source, TAB folds its locals:")
|
||||||
(let ((frames (plist-get state :stack)))
|
(let ((frames (plist-get state :stack)))
|
||||||
(if (null frames)
|
(if (null frames)
|
||||||
(insert (flan-cnr--unavailable 'stack))
|
(insert (flan-cnr--unavailable 'stack))
|
||||||
(let ((i -1))
|
(let ((i -1) (hidden 0))
|
||||||
(dolist (fr frames)
|
(dolist (fr frames)
|
||||||
(setq i (1+ i))
|
(setq i (1+ i))
|
||||||
(let ((start (point))
|
;; A hidden frame keeps its number. The index is what `locals' and
|
||||||
(open (memq i flan-cnr--open)))
|
;; the inspector are asked by, so the frames either side of a hidden
|
||||||
(insert (format " %2d: %s %s%s\n" i
|
;; run are numbered with a gap, and the line in the gap says why.
|
||||||
(if open "v" ">")
|
(if (and (not flan-cnr--show-prelude) (flan-cnr--prelude-frame-p fr))
|
||||||
(propertize (or (plist-get fr :fn) "?")
|
(setq hidden (1+ hidden))
|
||||||
'face 'font-lock-function-name-face)
|
(when (> hidden 0)
|
||||||
(if (plist-get fr :loc)
|
(flan-cnr--insert-hidden hidden)
|
||||||
(propertize (format " %s" (plist-get fr :loc))
|
(setq hidden 0))
|
||||||
'face 'shadow)
|
(flan-cnr--insert-frame i fr)))
|
||||||
"")))
|
(when (> hidden 0) (flan-cnr--insert-hidden hidden)))))
|
||||||
(add-text-properties start (point)
|
(insert "\n"))
|
||||||
(list 'flan-cnr-frame i 'mouse-face 'highlight)))
|
|
||||||
|
(defun flan-cnr--insert-frame (i fr)
|
||||||
|
"Draw frame I, FR, and its locals when it is open."
|
||||||
|
(let ((start (point))
|
||||||
|
(open (memq i flan-cnr--open)))
|
||||||
|
(insert (format " %2d: %s %s%s\n" i
|
||||||
|
(if open "v" ">")
|
||||||
|
(propertize (or (plist-get fr :fn) "?")
|
||||||
|
'face 'font-lock-function-name-face)
|
||||||
|
(if (plist-get fr :loc)
|
||||||
|
(propertize (format " %s" (plist-get fr :loc))
|
||||||
|
'face 'shadow)
|
||||||
|
"")))
|
||||||
|
(add-text-properties start (point)
|
||||||
|
(append (list 'flan-cnr-frame i 'mouse-face 'highlight)
|
||||||
|
(when (plist-get fr :loc)
|
||||||
|
(list 'flan-cnr-loc (plist-get fr :loc))))))
|
||||||
;; Collapsed by default. A stopped program has as many locals as it
|
;; Collapsed by default. A stopped program has as many locals as it
|
||||||
;; has frames, and all of them at once is the backtrace problem again
|
;; has frames, and all of them at once is the backtrace problem again
|
||||||
;; one level down.
|
;; one level down.
|
||||||
@ -413,8 +455,7 @@ indexing or the division itself, so it sits directly under the headline."
|
|||||||
(add-text-properties
|
(add-text-properties
|
||||||
start (point)
|
start (point)
|
||||||
(list 'flan-cnr-inspect (list :slot i (nth 3 l) (nth 0 l))
|
(list 'flan-cnr-inspect (list :slot i (nth 3 l) (nth 0 l))
|
||||||
'mouse-face 'highlight)))))))))))
|
'mouse-face 'highlight))))))))
|
||||||
(insert "\n"))
|
|
||||||
|
|
||||||
(defun flan-cnr--insert-globals (state)
|
(defun flan-cnr--insert-globals (state)
|
||||||
"Draw the globals the stopped stack reaches.
|
"Draw the globals the stopped stack reaches.
|
||||||
@ -493,7 +534,7 @@ puts the likely culprit on top."
|
|||||||
;; an entry is annotated with have to be on screen above it to read.
|
;; an entry is annotated with have to be on screen above it to read.
|
||||||
(flan-cnr--insert-globals state)
|
(flan-cnr--insert-globals state)
|
||||||
(insert (propertize
|
(insert (propertize
|
||||||
"RET/0-9 take a abort TAB fold a frame i inspect g refresh q quit\n"
|
"RET/0-9 take RET on a frame visits it TAB fold P prelude frames i inspect a abort g refresh q quit\n"
|
||||||
'face 'shadow))
|
'face 'shadow))
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
;; Point starts on the restart that abandons the evaluation, when there is
|
;; Point starts on the restart that abandons the evaluation, when there is
|
||||||
@ -535,9 +576,77 @@ puts the likely culprit on top."
|
|||||||
((get-text-property (point) 'flan-cnr-restart)
|
((get-text-property (point) 'flan-cnr-restart)
|
||||||
(flan-cnr--invoke (get-text-property (point) 'flan-cnr-index)
|
(flan-cnr--invoke (get-text-property (point) 'flan-cnr-index)
|
||||||
(get-text-property (point) 'flan-cnr-restart)))
|
(get-text-property (point) 'flan-cnr-restart)))
|
||||||
((get-text-property (point) 'flan-cnr-frame) (flan-cnr-toggle-frame))
|
((get-text-property (point) 'flan-cnr-hidden) (flan-cnr-toggle-prelude))
|
||||||
|
((or (get-text-property (point) 'flan-cnr-frame)
|
||||||
|
(get-text-property (point) 'flan-cnr-loc))
|
||||||
|
(flan-cnr-visit))
|
||||||
(t (user-error "flan: nothing to take on this line"))))
|
(t (user-error "flan: nothing to take on this line"))))
|
||||||
|
|
||||||
|
(defun flan-cnr--loc-subject (pos)
|
||||||
|
"What the location at POS is the location of, for a refusal."
|
||||||
|
(let ((i (get-text-property pos 'flan-cnr-frame)))
|
||||||
|
(if i
|
||||||
|
(format "frame %d, %s," i
|
||||||
|
(or (plist-get (nth i (plist-get flan-cnr--state :stack)) :fn)
|
||||||
|
"?"))
|
||||||
|
"the stop")))
|
||||||
|
|
||||||
|
(defun flan-cnr-visit ()
|
||||||
|
"Visit the source of the frame, or of the stop, on this line.
|
||||||
|
A frame's location is where its function is written; the stop's is the
|
||||||
|
expression that stopped."
|
||||||
|
(interactive)
|
||||||
|
(let ((loc (get-text-property (point) 'flan-cnr-loc)))
|
||||||
|
(unless loc
|
||||||
|
(user-error
|
||||||
|
(if (get-text-property (point) 'flan-cnr-frame)
|
||||||
|
"flan: this frame has no location; the program did not report one"
|
||||||
|
"flan: point is not on a frame or on the stop")))
|
||||||
|
(flan-visit-loc loc (flan-cnr--loc-subject (point)))))
|
||||||
|
|
||||||
|
(defun flan-cnr-toggle-prelude ()
|
||||||
|
"Show or hide the prelude's frames in the stack section."
|
||||||
|
(interactive)
|
||||||
|
(setq flan-cnr--show-prelude (not flan-cnr--show-prelude))
|
||||||
|
(let ((line (line-number-at-pos)))
|
||||||
|
(flan-cnr--render flan-cnr--state)
|
||||||
|
(goto-char (point-min))
|
||||||
|
(forward-line (1- line)))
|
||||||
|
(message "flan: prelude frames %s"
|
||||||
|
(if flan-cnr--show-prelude "shown" "hidden")))
|
||||||
|
|
||||||
|
(defun flan-cnr--visitable-line-p (pos)
|
||||||
|
"Whether the line at POS carries a location `next-error' can visit.
|
||||||
|
A location in angle brackets, `<prelude>', names no file, so the walk steps
|
||||||
|
over it rather than stopping on a refusal."
|
||||||
|
(let ((loc (get-text-property pos 'flan-cnr-loc)))
|
||||||
|
(and loc (not (string-prefix-p "<" loc)))))
|
||||||
|
|
||||||
|
(defun flan-cnr-next-error (&optional n reset)
|
||||||
|
"The break buffer's `next-error-function': walk the stop and its frames.
|
||||||
|
Moves N lines that carry a visitable location, from the top when RESET, and
|
||||||
|
visits the source of the one it lands on."
|
||||||
|
(setq n (or n 1))
|
||||||
|
(let ((dir (if (< n 0) -1 1))
|
||||||
|
(left (abs n))
|
||||||
|
(found nil))
|
||||||
|
(save-excursion
|
||||||
|
(if reset (goto-char (point-min)) (beginning-of-line))
|
||||||
|
;; From the top, the first step lands on the first location rather than
|
||||||
|
;; the second; and a count of zero means the line point is on.
|
||||||
|
(when (and (or reset (zerop left)) (flan-cnr--visitable-line-p (point)))
|
||||||
|
(setq left (max 0 (1- left)) found (point)))
|
||||||
|
(while (and (> left 0) (zerop (forward-line dir)) (not (eobp)))
|
||||||
|
(when (flan-cnr--visitable-line-p (point))
|
||||||
|
(setq left (1- left) found (point)))))
|
||||||
|
(when (or (> left 0) (null found))
|
||||||
|
(user-error "flan: no more frames with a source location"))
|
||||||
|
(goto-char found)
|
||||||
|
(let ((win (get-buffer-window (current-buffer))))
|
||||||
|
(when win (set-window-point win found)))
|
||||||
|
(flan-visit-loc (get-text-property found 'flan-cnr-loc)
|
||||||
|
(flan-cnr--loc-subject found))))
|
||||||
|
|
||||||
(defun flan-cnr--invoke (index name)
|
(defun flan-cnr--invoke (index name)
|
||||||
"Take restart INDEX, named NAME.
|
"Take restart INDEX, named NAME.
|
||||||
By index, because the index is the identity — two frames can offer `retry'
|
By index, because the index is the identity — two frames can offer `retry'
|
||||||
@ -668,6 +777,7 @@ drawn from."
|
|||||||
(get-text-property pos 'flan-cnr-shadowed)
|
(get-text-property pos 'flan-cnr-shadowed)
|
||||||
(get-text-property pos 'flan-cnr-abort)
|
(get-text-property pos 'flan-cnr-abort)
|
||||||
(get-text-property pos 'flan-cnr-frame)
|
(get-text-property pos 'flan-cnr-frame)
|
||||||
|
(get-text-property pos 'flan-cnr-hidden)
|
||||||
(get-text-property pos 'flan-cnr-inspect)))
|
(get-text-property pos 'flan-cnr-inspect)))
|
||||||
|
|
||||||
(defun flan-cnr-tab ()
|
(defun flan-cnr-tab ()
|
||||||
@ -691,6 +801,8 @@ anyone who would rather TAB always moved."
|
|||||||
(define-key map "n" #'flan-cnr-next)
|
(define-key map "n" #'flan-cnr-next)
|
||||||
(define-key map "p" #'flan-cnr-previous)
|
(define-key map "p" #'flan-cnr-previous)
|
||||||
(define-key map "f" #'flan-cnr-toggle-frame)
|
(define-key map "f" #'flan-cnr-toggle-frame)
|
||||||
|
(define-key map "v" #'flan-cnr-visit)
|
||||||
|
(define-key map "P" #'flan-cnr-toggle-prelude)
|
||||||
(define-key map "i" #'flan-cnr-inspect)
|
(define-key map "i" #'flan-cnr-inspect)
|
||||||
(define-key map "a" #'flan-cnr-abort)
|
(define-key map "a" #'flan-cnr-abort)
|
||||||
(define-key map "g" #'flan-cnr-refresh)
|
(define-key map "g" #'flan-cnr-refresh)
|
||||||
@ -704,7 +816,10 @@ anyone who would rather TAB always moved."
|
|||||||
|
|
||||||
(define-derived-mode flan-cnr-mode special-mode "flan-break"
|
(define-derived-mode flan-cnr-mode special-mode "flan-break"
|
||||||
"What a stopped Flan program is offering."
|
"What a stopped Flan program is offering."
|
||||||
(setq buffer-read-only t))
|
(setq buffer-read-only t)
|
||||||
|
;; `next-error' walks the stop and then the frames, innermost first, the way
|
||||||
|
;; it walks a compiler's messages.
|
||||||
|
(setq-local next-error-function #'flan-cnr-next-error))
|
||||||
|
|
||||||
(defun flan-cnr-state-from-reply (reply &optional fields stack globals)
|
(defun flan-cnr-state-from-reply (reply &optional fields stack globals)
|
||||||
"The buffer's state, out of a `break' REPLY.
|
"The buffer's state, out of a `break' REPLY.
|
||||||
@ -914,7 +1029,11 @@ walk from a running program."
|
|||||||
(flan-cnr-condition-fields
|
(flan-cnr-condition-fields
|
||||||
(plist-get r :condition))
|
(plist-get r :condition))
|
||||||
(flan-cnr-backtrace)
|
(flan-cnr-backtrace)
|
||||||
(flan-cnr-globals))))
|
(flan-cnr-globals)))
|
||||||
|
;; So `M-g M-n' from the source buffer walks this stack rather than
|
||||||
|
;; the last compilation's errors, for as long as the program is
|
||||||
|
;; stopped here.
|
||||||
|
(setq next-error-last-buffer buf))
|
||||||
(pop-to-buffer buf)
|
(pop-to-buffer buf)
|
||||||
buf)))
|
buf)))
|
||||||
|
|
||||||
|
|||||||
@ -1317,6 +1317,41 @@ than being told so."
|
|||||||
(string-to-number (match-string 2 loc))
|
(string-to-number (match-string 2 loc))
|
||||||
(string-to-number (match-string 3 loc)))))
|
(string-to-number (match-string 3 loc)))))
|
||||||
|
|
||||||
|
(defun flan--visitable-loc (loc subject)
|
||||||
|
"LOC as (FILE LINE COL) when FILE can be visited, or a `user-error'.
|
||||||
|
SUBJECT names what LOC is the location of, for the refusal. One set of
|
||||||
|
refusals for every place that jumps to a location the daemon sent: M-. on a
|
||||||
|
definition, and RET on a frame or on the stop in the break buffer."
|
||||||
|
(let ((parts (flan--parse-loc loc)))
|
||||||
|
(cond
|
||||||
|
((null parts)
|
||||||
|
(user-error "flan: the daemon gave %s an unreadable location: %s"
|
||||||
|
subject loc))
|
||||||
|
((string-match-p "\\`<.*>\\'" (nth 0 parts))
|
||||||
|
;; The prelude is a string inside the compiler (lib/prelude.ml) and
|
||||||
|
;; names itself <prelude>; anything in angle brackets is a placeholder
|
||||||
|
;; the frontend made up, not a path.
|
||||||
|
(user-error "flan: %s is defined in %s, which is not a file on disk"
|
||||||
|
subject (nth 0 parts)))
|
||||||
|
((not (file-name-absolute-p (nth 0 parts)))
|
||||||
|
;; The daemon makes its own source path absolute, so anything relative
|
||||||
|
;; arriving here came from somewhere that did not, and the directory it
|
||||||
|
;; is relative to is the daemon's, not this one's.
|
||||||
|
(user-error "flan: %s is at %s, relative to a directory this end does not know"
|
||||||
|
subject (nth 0 parts)))
|
||||||
|
((not (file-exists-p (nth 0 parts)))
|
||||||
|
(user-error "flan: %s is defined in %s, which is not a file on disk"
|
||||||
|
subject (nth 0 parts)))
|
||||||
|
(t parts))))
|
||||||
|
|
||||||
|
(defun flan-visit-loc (loc subject)
|
||||||
|
"Visit LOC in another window, refusing by `flan--visitable-loc's rules.
|
||||||
|
Returns the buffer visited."
|
||||||
|
(let ((parts (flan--visitable-loc loc subject)))
|
||||||
|
(find-file-other-window (nth 0 parts))
|
||||||
|
(goto-char (flan--position (nth 1 parts) (nth 2 parts)))
|
||||||
|
(current-buffer)))
|
||||||
|
|
||||||
(defun flan--position (line col)
|
(defun flan--position (line col)
|
||||||
"Position of LINE and byte-column COL in the current buffer."
|
"Position of LINE and byte-column COL in the current buffer."
|
||||||
(save-excursion
|
(save-excursion
|
||||||
@ -2136,37 +2171,15 @@ in this program; C-c C-v describes it" (car d)))
|
|||||||
(user-error "flan: %s is a %s, and the daemon reports no location for one"
|
(user-error "flan: %s is a %s, and the daemon reports no location for one"
|
||||||
(car d) (nth 1 d)))
|
(car d) (nth 1 d)))
|
||||||
(t
|
(t
|
||||||
(let ((parts (flan--parse-loc (nth 3 d))))
|
(let ((parts (flan--visitable-loc (nth 3 d) (car d))))
|
||||||
(cond
|
(list (xref-make
|
||||||
((null parts)
|
(nth 2 d)
|
||||||
(user-error "flan: the daemon gave %s an unreadable location: %s"
|
(xref-make-file-location
|
||||||
(car d) (nth 3 d)))
|
(nth 0 parts) (nth 1 parts)
|
||||||
((string-match-p "\\`<.*>\\'" (nth 0 parts))
|
;; A byte column, like every other one the daemon sends, but
|
||||||
;; The prelude is a string inside the compiler (lib/prelude.ml) and
|
;; a top-level definition starts at column 1 and anything
|
||||||
;; names itself <prelude>; anything in angle brackets is a
|
;; indenting it is ASCII, so the two agree here.
|
||||||
;; placeholder the frontend made up, not a path.
|
(max 0 (1- (nth 2 parts)))))))))))
|
||||||
(user-error "flan: %s is defined in %s, which is not a file on disk"
|
|
||||||
(car d) (nth 0 parts)))
|
|
||||||
((not (file-name-absolute-p (nth 0 parts)))
|
|
||||||
;; The daemon makes its own source path absolute, so anything
|
|
||||||
;; relative arriving here came from somewhere that did not, and the
|
|
||||||
;; directory it is relative to is the daemon's, not this one's.
|
|
||||||
(user-error "flan: %s is at %s, relative to a directory this end does not know"
|
|
||||||
(car d) (nth 0 parts)))
|
|
||||||
((not (file-exists-p (nth 0 parts)))
|
|
||||||
;; The prelude is a string inside the compiler (lib/prelude.ml), so
|
|
||||||
;; its location names a file nobody can visit.
|
|
||||||
(user-error "flan: %s is defined in %s, which is not a file on disk"
|
|
||||||
(car d) (nth 0 parts)))
|
|
||||||
(t
|
|
||||||
(list (xref-make
|
|
||||||
(nth 2 d)
|
|
||||||
(xref-make-file-location
|
|
||||||
(nth 0 parts) (nth 1 parts)
|
|
||||||
;; A byte column, like every other one the daemon sends, but
|
|
||||||
;; a top-level definition starts at column 1 and anything
|
|
||||||
;; indenting it is ASCII, so the two agree here.
|
|
||||||
(max 0 (1- (nth 2 parts)))))))))))))
|
|
||||||
|
|
||||||
;;; Documentation
|
;;; Documentation
|
||||||
|
|
||||||
|
|||||||
@ -1028,7 +1028,8 @@ would be overwritten. Look again and re-do the edit")
|
|||||||
(test-flan--check "the stack says the program is running, not that it is unbuilt"
|
(test-flan--check "the stack says the program is running, not that it is unbuilt"
|
||||||
(string-match-p "Stack.*\n not available.*running"
|
(string-match-p "Stack.*\n not available.*running"
|
||||||
(substring text (string-match "--- Stack" text))))
|
(substring text (string-match "--- Stack" text))))
|
||||||
(test-flan--check "and the keys are shown" (string-match-p "TAB fold a frame" text)))
|
(test-flan--check "and the keys are shown" (and (string-match-p "TAB fold" text)
|
||||||
|
(string-match-p "P prelude frames" text))))
|
||||||
|
|
||||||
;; A stopped program with nothing on offer between the error and the top. It
|
;; A stopped program with nothing on offer between the error and the top. It
|
||||||
;; is a real state — spec-conditions §2's `error' with no `restart-case' above
|
;; is a real state — spec-conditions §2's `error' with no `restart-case' above
|
||||||
@ -1229,6 +1230,82 @@ would be overwritten. Look again and re-do the edit")
|
|||||||
(test-flan--check "and TAB again closes it"
|
(test-flan--check "and TAB again closes it"
|
||||||
(not (string-match-p "i32 i = 7" (buffer-string))))))
|
(not (string-match-p "i32 i = 7" (buffer-string))))))
|
||||||
|
|
||||||
|
;; The prelude's frames, and the source behind a frame. `(pause)' is a prelude
|
||||||
|
;; function, so the innermost frame of every breakpoint is the prelude's own and
|
||||||
|
;; not a step of the program. It is hidden and keeps its number, because the
|
||||||
|
;; number is what `locals' and the inspector are asked by.
|
||||||
|
(let* ((dir (make-temp-file "flan-cnr-src" t))
|
||||||
|
(src (expand-file-name "game.flan" dir)))
|
||||||
|
(with-temp-file src
|
||||||
|
(insert "(defn tick [] ()\n (pause))\n\n(defn main [] ()\n (tick))\n"))
|
||||||
|
(let* ((state (list :condition "Pause" :restarts '("continue")
|
||||||
|
:site (format "%s:2:3" src) :source " (pause))"
|
||||||
|
:stack (list (list :fn "pause" :loc "<prelude>:151:7"
|
||||||
|
:fetched t :locals nil)
|
||||||
|
(list :fn "tick" :loc (format "%s:1:1" src)
|
||||||
|
:fetched t :locals nil)
|
||||||
|
(list :fn "main" :loc (format "%s:4:1" src)
|
||||||
|
:fetched t :locals nil))))
|
||||||
|
(buf (test-flan--cnr state)))
|
||||||
|
(with-current-buffer buf
|
||||||
|
(setq flan-cnr--show-prelude nil)
|
||||||
|
(flan-cnr--render state)
|
||||||
|
(let ((text (buffer-string)))
|
||||||
|
(test-flan--check "a prelude frame is hidden to begin with"
|
||||||
|
(not (string-match-p "0: > pause" text)))
|
||||||
|
(test-flan--check "and a line in its place says so"
|
||||||
|
(string-match-p "1 prelude frame hidden — P shows it" text))
|
||||||
|
(test-flan--check "the program's frames keep their numbers"
|
||||||
|
(and (string-match-p " 1: > tick" text)
|
||||||
|
(string-match-p " 2: > main" text))))
|
||||||
|
(goto-char (point-min))
|
||||||
|
(search-forward "prelude frame hidden")
|
||||||
|
(flan-cnr-take)
|
||||||
|
(test-flan--check "RET on that line shows the prelude frames"
|
||||||
|
(string-match-p " 0: > pause +<prelude>:151:7"
|
||||||
|
(buffer-string)))
|
||||||
|
(goto-char (point-min))
|
||||||
|
(search-forward " 0: > pause")
|
||||||
|
(let ((msg (test-flan--caught #'flan-cnr-visit)))
|
||||||
|
(test-flan--check "visiting a prelude frame refuses, naming the prelude"
|
||||||
|
(and msg (string-match-p "<prelude>, which is not a file" msg))))
|
||||||
|
(flan-cnr-toggle-prelude)
|
||||||
|
(test-flan--check "and P hides them again"
|
||||||
|
(not (string-match-p "0: > pause" (buffer-string))))
|
||||||
|
;; RET on a frame goes to where its function is written.
|
||||||
|
(goto-char (point-min))
|
||||||
|
(search-forward " 2: > main")
|
||||||
|
(save-window-excursion
|
||||||
|
(let ((visited (progn (flan-cnr-take) (current-buffer))))
|
||||||
|
(test-flan--check "RET on a frame visits its source"
|
||||||
|
(equal (buffer-file-name visited) src))
|
||||||
|
(test-flan--check "at the frame's line"
|
||||||
|
(= (line-number-at-pos) 4))
|
||||||
|
(kill-buffer visited)))
|
||||||
|
(test-flan--check "RET on a frame no longer folds it"
|
||||||
|
(not (memq 2 flan-cnr--open)))
|
||||||
|
;; next-error: the stop first, then each frame with a file behind it.
|
||||||
|
(test-flan--check "the break buffer is a next-error buffer"
|
||||||
|
(eq next-error-function #'flan-cnr-next-error))
|
||||||
|
(let ((lines nil))
|
||||||
|
(save-window-excursion
|
||||||
|
(with-current-buffer buf
|
||||||
|
(goto-char (point-min))
|
||||||
|
(dotimes (k 3)
|
||||||
|
(let ((b (flan-cnr-next-error 1 (zerop k))))
|
||||||
|
(push (with-current-buffer b (line-number-at-pos)) lines)
|
||||||
|
(set-buffer buf)))
|
||||||
|
(test-flan--check "and past the last frame it says so"
|
||||||
|
(test-flan--caught
|
||||||
|
(lambda () (flan-cnr-next-error 1))))
|
||||||
|
(flan-cnr-next-error -1)
|
||||||
|
(test-flan--check "and walks back"
|
||||||
|
(= (line-number-at-pos) 1))))
|
||||||
|
(test-flan--check "next-error walks the stop, then tick, then main"
|
||||||
|
(equal (nreverse lines) '(2 1 4))))
|
||||||
|
(let ((b (get-file-buffer src))) (when b (kill-buffer b))))
|
||||||
|
(delete-directory dir t)))
|
||||||
|
|
||||||
;; The two buffers meet, and this is the fixture the bug lived in. `i' used
|
;; The two buffers meet, and this is the fixture the bug lived in. `i' used
|
||||||
;; to send the local's *name* to be evaluated, which resolves wherever the
|
;; to send the local's *name* to be evaluated, which resolves wherever the
|
||||||
;; evaluator stands: right on the innermost frame by luck, and on any other
|
;; evaluator stands: right on the innermost frame by luck, and on any other
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user