The editor batch: results at line end, inspect at point, a buffer that keeps
This commit is contained in:
commit
6ef0f46bea
@ -170,21 +170,31 @@ one point is in, and only then is it asked whether it is a definition.
|
||||
### `C-x C-e` — evaluate an expression
|
||||
|
||||
The expression before point is compiled, run **inside the running program**, and
|
||||
its value shown beside it, as `=> 2` after the form you evaluated. Not a copy of
|
||||
its value shown as `=> 2` at the end of the line the form is on. 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.
|
||||
|
||||
The value goes away on your next keystroke, like every other bit of feedback
|
||||
about a command that just ran. Set `flan-inline-result` to `nil` and it goes to
|
||||
the echo area instead; that is also where it goes when there is nowhere in a
|
||||
buffer to draw it, as at the REPL.
|
||||
**Where it appears.** At the end of the *line*, which is where the watch buffer's
|
||||
ghost text goes too — the two are the same thing seen twice and they are drawn
|
||||
the same way. It used to sit at the end of the form instead, so that the two
|
||||
features could be told apart by position; what that cost is a value wedged into
|
||||
the middle of a line whenever the form was not the last thing on it, reflowing
|
||||
everything after it while you read.
|
||||
|
||||
The two are different questions and the settings are not two spellings of one
|
||||
thing. `flan-echo-result` is about the sentence an *install* reports — which
|
||||
names landed and what it cost — and an install has no value to draw. Where they
|
||||
would overlap, on an expression, the inline value wins and the echo is not also
|
||||
written: the same number twice is how a reader learns to skip both.
|
||||
**And in the echo area, always.** The overlay goes away on your next keystroke,
|
||||
like every other bit of feedback about a command that just ran — so the same
|
||||
value is written to the echo area as well, where it is still there afterwards.
|
||||
The echo used to be suppressed whenever the overlay drew, on the argument that
|
||||
the same number twice teaches a reader to skip both; between saying it twice and
|
||||
losing it before it was read, saying it twice is the smaller cost.
|
||||
|
||||
The two settings are still different questions, and neither now suppresses the
|
||||
other. `flan-inline-result` decides the overlay: set it to `nil` and the echo
|
||||
area is the only place a value appears, which is also what happens when there is
|
||||
nowhere in a buffer to draw one, as at the REPL. `flan-echo-result` decides the
|
||||
echo area, and it also covers the sentence an *install* reports — which names
|
||||
landed and what it cost — since an install has no value to draw.
|
||||
|
||||
The form before point can also be a declaration — a `defvar` typed at the top of
|
||||
a file — in which case `C-x C-e` installs it rather than refusing it, and says
|
||||
@ -366,8 +376,16 @@ 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.
|
||||
**`C-c C-i`** inspects a value: the expression point is on, with no prompt. You
|
||||
get its fields, one per line. It takes the innermost thing point is on rather
|
||||
than only the one behind point, so the middle of a name works as well as the end
|
||||
of one — point anywhere in `enemies` takes `enemies`, and on the open paren of
|
||||
`(len enemies)` takes the whole call.
|
||||
|
||||
**`C-u C-c C-i`** opens the minibuffer instead, pre-filled with whatever was at
|
||||
point — for inspecting something that is not written in the buffer, or is
|
||||
written somewhere else. That is also what you get when there is nothing at point
|
||||
to take.
|
||||
|
||||
| Key | Does |
|
||||
|---|---|
|
||||
@ -847,6 +865,23 @@ if one is written, and the body alike.
|
||||
`{.x 1.0}`. The colon is still a constant too; it means an enum member, `:green`,
|
||||
and a key in a map.
|
||||
|
||||
**The keywords are the parser's.** Both lists — the forms that introduce a
|
||||
top-level name, and the forms with meaning to the checker — are read off
|
||||
`Parse.decl` and `Parse.form` in `lib/parse.ml` and kept whole, rather than
|
||||
topped up whenever somebody notices a gap. So `defmacro`, `defclass`,
|
||||
`defgeneric`, `defmulti`, `defmethod` and `declare-c` colour as definitions, and
|
||||
`when`, `cond`, `and`, `or`, `break`, `continue`, `recur`, `fn`, `quote`,
|
||||
`array`, `signal`, `error` and the four condition forms — `handler-bind`,
|
||||
`handler-case`, `restart-case`, `invoke-restart` — colour as keywords. A macro
|
||||
*you* define is still drawn as an ordinary call: macro-ness is erased by the
|
||||
time the daemon can be asked about a name, so there is nothing to ask.
|
||||
|
||||
**`#_` greys out the form after it**, the way it reads: the discarded form is
|
||||
given the comment syntax class, so it is drawn as a comment and skipped by
|
||||
everything else that skips comments. Chaining works as the reader's does —
|
||||
`#_#_ a b` discards both, with nothing counting — and an unfinished form is
|
||||
left alone, so `#_(` does not grey the rest of the file while you are typing it.
|
||||
|
||||
Nothing here needs a running program. Indentation and colouring are the major
|
||||
mode's, so they work in a file you have only opened.
|
||||
|
||||
@ -955,6 +990,23 @@ other three are unaffected.
|
||||
next time that buffer's evaluation is accepted — so it disappears when you fix
|
||||
the thing rather than when you dismiss it.
|
||||
|
||||
**And it is kept.** Every compiler message the editor is handed is also appended
|
||||
to `*flan-diagnostics*`, one entry per message with the time it arrived above
|
||||
it: `M-x flan-show-diagnostics`. The overlay is feedback about one command and
|
||||
is gone at the next keystroke; this is the copy you can still quote an hour
|
||||
later, without going through `*Messages*` for it. Nothing clears it — not
|
||||
connecting, not a successful evaluation, not quitting the program — until you
|
||||
say `M-x flan-clear-diagnostics`. Each entry's message line is left in the shape
|
||||
the compiler wrote it, `file:line:col: message`, so `RET` and `M-g M-n` on one
|
||||
go to the code and a line yanked out of the buffer reads the same as one pasted
|
||||
from a terminal.
|
||||
|
||||
**A build that failed is in `*flan-dev*`**, and it jumps too: the buffer is in
|
||||
`compilation-minor-mode`, so `M-g M-n`, `M-g M-p` and `RET` on a diagnostic take
|
||||
you to the line it names. That is the buffer to read when `M-x flan` reports a
|
||||
daemon that exited before it was ready — a `main` that does not compile means no
|
||||
socket at all, and this is the only account of why.
|
||||
|
||||
**"No .flan-dev.sock found above this buffer"** — nothing is running, or you are
|
||||
outside the project. Start one with `M-x flan`.
|
||||
|
||||
@ -987,7 +1039,8 @@ Use `C-c C-g` if you need frames.
|
||||
| `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-i` | inspect the expression at point |
|
||||
| `C-u C-c C-i` | ...or one you type in the minibuffer |
|
||||
| `C-c C-m` | what the macro call at point expands to |
|
||||
| `C-u C-c C-m` | ...all the way, rather than one step |
|
||||
| `C-c C-a` | disassemble; `C-u` first for LLVM IR |
|
||||
@ -1001,6 +1054,8 @@ Use `C-c C-g` if you need frames.
|
||||
Commands with no key: `M-x flan` (start a program), `M-x flan-quit`
|
||||
(stop it), `M-x flan-watch` (the watch buffer), `M-x flan-watch-stop`,
|
||||
`M-x flan-watch-ghost-mode` (the same values inline),
|
||||
`M-x flan-show-diagnostics` / `M-x flan-clear-diagnostics` (every compiler
|
||||
message this session has been handed, and emptying that),
|
||||
`M-x flan-inspect-address` (what is at an address), `M-x flan-macroexpand-all`
|
||||
(the `C-u` half of `C-c C-m`, by name), `M-x flan-allocations` /
|
||||
`M-x flan-leaks` (where the memory went, and what is still held), and
|
||||
@ -1017,11 +1072,12 @@ in the buffer).
|
||||
| `flan-daemon-args` | `nil` | extra arguments for `flan dev` — `("--llvm")`, `("--debug")` |
|
||||
| `flan-socket-name` | `".flan-dev.sock"` | what `C-c C-z` searches for |
|
||||
| `flan-echo-result` | `t` | report an accepted evaluation in the echo area |
|
||||
| `flan-inline-result` | `t` | show an expression's value beside the form, not in the echo area |
|
||||
| `flan-inline-result` | `t` | also show an expression's value at the end of its line |
|
||||
| `flan-names-shown` | `4` | how many names to list before summarising |
|
||||
| `flan-output-buffer` | `"*flan-output*"` | where the program's output goes |
|
||||
| `flan-poll-interval` | `1.0` | seconds between checks for whether it stopped |
|
||||
| `flan-daemon-buffer` | `"*flan-dev*"` | the daemon's own log |
|
||||
| `flan-diagnostics-buffer` | `"*flan-diagnostics*"` | every compiler message, kept |
|
||||
| `flan-start-timeout` | `60` | seconds to wait for a program to come up |
|
||||
| `flan-lower-buffer` | `"*flan-lowering*"` | where `C-c C-l` writes |
|
||||
| `flan-lower-program` | `"flan"` | the compiler `C-c C-l` shells out to |
|
||||
|
||||
@ -677,19 +677,56 @@ and whether that may be followed is the answer"))
|
||||
(display-buffer buf)
|
||||
buf))
|
||||
|
||||
(defun flan-inspect--expression-at-point ()
|
||||
"The text of the expression point is on, or nil.
|
||||
|
||||
The innermost thing point is on, and not merely the one behind it: point in
|
||||
the middle of a name is where a key pressed without looking lands, and
|
||||
`backward-sexp\=' alone answers with the half of the name already typed past —
|
||||
which reads as the command being broken rather than as point being one
|
||||
character early. So the end of the thing under point is found first, and the
|
||||
form is taken back from there; on an opening delimiter the form that opens
|
||||
there is taken instead, which is the rule `flan-macroexpand\=' follows for the
|
||||
same reason.
|
||||
|
||||
Innermost, so point on `enemies\=' inside `(len enemies)\=' takes `enemies\=' and
|
||||
not the call — which is the right answer for a command that shows you one
|
||||
value, and the enclosing call is a paren away in either direction.
|
||||
|
||||
Nil when there is nothing there to take, which is a buffer position with no
|
||||
form at it at all."
|
||||
(ignore-errors
|
||||
(save-excursion
|
||||
(skip-chars-forward " \t")
|
||||
(if (looking-at-p "[([{]")
|
||||
(buffer-substring-no-properties
|
||||
(point) (progn (forward-sexp) (point)))
|
||||
;; Inside a form: out to its end, so that the text taken is the whole
|
||||
;; of it. Nothing to step over when point is already after one — at
|
||||
;; whitespace, at a closing delimiter, or at the end of the buffer.
|
||||
(unless (or (eobp)
|
||||
(memq (char-syntax (char-after)) '(?\s ?> ?\))))
|
||||
(forward-sexp))
|
||||
(buffer-substring-no-properties
|
||||
(save-excursion (backward-sexp) (point)) (point))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun flan-inspect (expr)
|
||||
"Inspect the value of EXPR in the running program.
|
||||
Interactively, the expression before point, or one you type.
|
||||
|
||||
Interactively, the expression at point — no prompt, because the expression is
|
||||
already written in the buffer and retyping it is the whole cost of the
|
||||
command. With a prefix argument, or with nothing at point to take, the
|
||||
minibuffer opens instead, pre-filled with whatever was there.
|
||||
|
||||
This is the expression root: EXPR is evaluated where the evaluator stands, so
|
||||
it works on a running program but cannot say which frame it means. `i\=' in the
|
||||
break buffer uses `flan-inspect-slot\=' for a local, for exactly that reason."
|
||||
(interactive
|
||||
(list (read-string "Inspect: "
|
||||
(ignore-errors
|
||||
(buffer-substring-no-properties
|
||||
(save-excursion (backward-sexp) (point)) (point))))))
|
||||
(let ((at (flan-inspect--expression-at-point)))
|
||||
(list (if (and at (not current-prefix-arg) (not (string-blank-p at)))
|
||||
at
|
||||
(read-string "Inspect: " at)))))
|
||||
;; A new root, and therefore an empty stack. That is the whole of why `l\='
|
||||
;; cannot walk out of one root into another: there is never an entry from a
|
||||
;; different root left underneath it.
|
||||
|
||||
@ -111,17 +111,37 @@
|
||||
:group 'languages
|
||||
:prefix "flan-")
|
||||
|
||||
;; Both lists are read off `Parse.decl' and `Parse.form' (lib/parse.ml), which
|
||||
;; are where the two questions are actually decided: a head `decl' has an arm
|
||||
;; for introduces a name, and a head `form' has an arm for is never a call.
|
||||
;; Kept whole rather than topped up one name at a time — the list that is
|
||||
;; patched only when somebody notices a gap is the list that is always a
|
||||
;; release behind the parser.
|
||||
(defconst flan--definers
|
||||
'("defn" "defvar" "defconst" "defstruct" "defdata" "defunion" "defenum"
|
||||
"defalias"
|
||||
"declare" "import" "package")
|
||||
'("defn" "defmacro" "defvar" "defconst" "defstruct" "defdata" "defunion"
|
||||
"defenum" "defalias"
|
||||
;; The object and dispatch heads (lib/parse.ml:1277-1334).
|
||||
"defclass" "defgeneric" "defmulti" "defmethod"
|
||||
"declare" "declare-c" "import" "package")
|
||||
"Forms that introduce a top-level name.")
|
||||
|
||||
(defconst flan--special
|
||||
'("let" "if" "do" "while" "until" "dotimes" "loop" "match" "set" "return"
|
||||
"defer" "some" "none" "try" "zeroed" "uninit" "slice" "at" "len" "addr"
|
||||
'("quote" "let" "if" "when" "cond" "and" "or" "do" "while" "until" "dotimes"
|
||||
"loop" "recur" "break" "continue" "match" "set" "return" "fn" "array"
|
||||
"defer" "some" "none" "try" "signal" "error"
|
||||
"handler-bind" "handler-case" "restart-case" "invoke-restart"
|
||||
"zeroed" "uninit" "slice" "at" "len" "addr"
|
||||
"bytes" "cast" "true" "false" "nil" "print" "println")
|
||||
"Forms with meaning to the checker.")
|
||||
"Forms with meaning to the checker.
|
||||
|
||||
The heads `Parse.form' dispatches on, plus the literals and the handful of
|
||||
builtins that are never anything else. Two groups of real heads are
|
||||
deliberately left out: `quasiquote', `unquote' and `unquote-splicing', which
|
||||
nobody writes as words — the reader makes them out of \\=`, ~ and ~@, and the
|
||||
sigils are not symbols for a keyword rule to reach — and `find-restart',
|
||||
`compute-restarts', `errdefer' and `await', which the parser recognises only
|
||||
in order to refuse them. Drawing those four as keywords would advertise four
|
||||
forms that cannot be used.")
|
||||
|
||||
(defvar flan-font-lock-keywords
|
||||
`((,(concat "(" (regexp-opt flan--definers t) "\\_>"
|
||||
@ -194,6 +214,95 @@ line is off screen."
|
||||
table)
|
||||
"Syntax table for `flan-mode'.")
|
||||
|
||||
;;; The discard reader macro
|
||||
|
||||
;; `#_' reads the form after it and throws it away (lib/reader.ml:16-24,
|
||||
;; 202-262), which is how a form is commented out without counting its closing
|
||||
;; parens. The table above knows nothing about it — it comes from
|
||||
;; `lisp-mode-syntax-table', and Common Lisp has no such construct — so a
|
||||
;; discarded form used to render as ordinary code, which is the one thing it
|
||||
;; must not look like.
|
||||
;;
|
||||
;; The fix is `clojure-mode's, which has the same reader macro and the same
|
||||
;; problem: a `syntax-propertize-function' that gives the span the comment
|
||||
;; syntax class. Font lock then draws it as a comment with no rule of its own,
|
||||
;; and every other thing that reads syntax classes — `forward-comment',
|
||||
;; `comment-only-p', a package that skips comments — agrees for free.
|
||||
;;
|
||||
;; Generic comment fences (the `!' class) rather than a line comment, because a
|
||||
;; discarded form is a region and may be one line or twenty. The `#' is the
|
||||
;; opening fence and the last character of the form is the closing one, so
|
||||
;; everything in between is inside a comment and is no longer read as
|
||||
;; structure. That is the reader's own answer, and it leaves the *enclosing*
|
||||
;; list's parens alone: only the discarded form's own last delimiter changes
|
||||
;; class, so `(a #_(b c) d)' still balances.
|
||||
|
||||
(defun flan--discard-end (pos)
|
||||
"Where the discard whose form starts at POS ends, or nil.
|
||||
|
||||
The reader's rule rather than a count: a discard reads *a form*, and reading
|
||||
one skips leading discards of its own, so `#_#_ a b' throws away both a and b
|
||||
with nothing ever counting to two (lib/reader.ml:202-262). Written as the
|
||||
loop that recursion amounts to — every `#_' seen owes one more form — since
|
||||
the walk over the buffer is the same either way.
|
||||
|
||||
Nil when a form is unfinished, which is every buffer halfway through being
|
||||
typed: nothing is propertized then, rather than `#_(' greying out the rest of
|
||||
the file while the parenthesis is still open."
|
||||
(let ((owed 1)
|
||||
;; The scan must not consult the properties it is being run to
|
||||
;; compute: with `parse-sexp-lookup-properties' on, `forward-sexp'
|
||||
;; would ask for the syntax of positions this pass has not reached.
|
||||
;; The table alone is enough — the nesting is counted here, and
|
||||
;; strings and escapes are the table's own business.
|
||||
(parse-sexp-lookup-properties nil))
|
||||
(ignore-errors
|
||||
(save-excursion
|
||||
(goto-char pos)
|
||||
(while (> owed 0)
|
||||
(forward-comment (buffer-size))
|
||||
(if (looking-at-p "#_")
|
||||
(progn (forward-char 2) (setq owed (1+ owed)))
|
||||
(forward-sexp)
|
||||
(setq owed (1- owed))))
|
||||
;; `forward-sexp' at the end of the buffer stays put rather than
|
||||
;; signalling, so a trailing `#_' with nothing after it would otherwise
|
||||
;; come back as a span covering only itself — a two-character comment
|
||||
;; drawn over the reader's one real discard error. It is unfinished
|
||||
;; like any other, and answers nil like any other.
|
||||
(and (> (point) pos) (point))))))
|
||||
|
||||
(defun flan--syntax-propertize (start end)
|
||||
"Mark every `#_' discard between START and END as a comment.
|
||||
For `syntax-propertize-function'."
|
||||
(goto-char start)
|
||||
(while (search-forward "#_" end t)
|
||||
;; Both ends of the match are read off point before anything else runs, and
|
||||
;; the `syntax-ppss' below is wrapped: it moves point and clobbers the
|
||||
;; match data, so asking it first and reading the match afterwards is how
|
||||
;; this would come to scan from the `#' instead of from the form — and how
|
||||
;; a `#_' it declined would be found again, forever, at the same place.
|
||||
(let* ((beg (- (point) 2))
|
||||
(from (point))
|
||||
;; `#_' written inside a string, or inside a `;' comment, is text
|
||||
;; and not a reader macro. One inside a span an earlier discard
|
||||
;; already covers is not reached at all: that discard's own scan
|
||||
;; consumed it, and point jumps past the whole span below.
|
||||
(quoted (save-excursion (nth 8 (syntax-ppss beg))))
|
||||
(fin (and (not quoted) (flan--discard-end from))))
|
||||
(when fin
|
||||
(put-text-property beg (1+ beg)
|
||||
'syntax-table (string-to-syntax "!"))
|
||||
(put-text-property (1- fin) fin
|
||||
'syntax-table (string-to-syntax "!"))
|
||||
;; So that a change *inside* a discarded form re-propertizes from the
|
||||
;; `#_' rather than from the line it was typed on; the other half is
|
||||
;; `syntax-propertize-multiline' in the mode body.
|
||||
(put-text-property beg fin 'syntax-multiline t))
|
||||
;; Past the span when there was one, and past the `#_' when there was
|
||||
;; not. Either way forward, which is the whole of why this terminates.
|
||||
(goto-char (or fin from)))))
|
||||
|
||||
(defvar flan-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
;; Autoloaded from flan.el, so the client loads on first use.
|
||||
@ -270,6 +379,12 @@ line is off screen."
|
||||
(setq-local lisp-indent-function #'flan-indent-function)
|
||||
(setq-local outline-regexp ";;;;+[ \t]*")
|
||||
(setq-local imenu-generic-expression flan-imenu-generic-expression)
|
||||
;; `#_' — see the section above the syntax table.
|
||||
(setq-local syntax-propertize-function #'flan--syntax-propertize)
|
||||
;; A discarded form spans as many lines as it likes, so the region handed to
|
||||
;; the propertizer has to be widened back to the `#_' that owns it.
|
||||
(add-hook 'syntax-propertize-extend-region-functions
|
||||
#'syntax-propertize-multiline nil t)
|
||||
;; Buffer-locally, because this answers for Flan and nothing else.
|
||||
(add-hook 'which-func-functions #'flan-current-defun-name nil t))
|
||||
|
||||
|
||||
259
emacs/flan.el
259
emacs/flan.el
@ -67,6 +67,11 @@
|
||||
(require 'cl-lib)
|
||||
(require 'xref)
|
||||
(require 'eldoc)
|
||||
;; For `compilation-minor-mode', which is what makes a `file:line:col:' line in
|
||||
;; the daemon's buffer and in the diagnostics log somewhere `next-error' can
|
||||
;; go. Flan's messages already have that shape, so nothing here teaches
|
||||
;; `compile' a new one.
|
||||
(require 'compile)
|
||||
;; The macroexpansion buffer is a `flan-mode' buffer — what is in it is Flan
|
||||
;; source and reading it is the point — so the mode has to exist by the time
|
||||
;; this file defines one derived from it. Not a cycle: flan-mode.el autoloads
|
||||
@ -89,16 +94,22 @@ that quietly did nothing, which is why it is on."
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom flan-inline-result t
|
||||
"Whether an expression's value is shown beside the form it came from.
|
||||
"Whether an expression's value is shown at the end of the line it came from.
|
||||
|
||||
The two knobs are not two ways of saying one thing. This one is about a
|
||||
*value*: nothing else an evaluation produces has a place in the buffer to sit
|
||||
beside, so an install still reports through `flan-echo-result' whatever this
|
||||
is set to. Where they do overlap — an expression whose value could go either
|
||||
place — the overlay wins and the echo is not also written, because saying the
|
||||
same number twice is how a reader learns to stop reading both. The echo is
|
||||
what happens when no overlay could be drawn: the REPL, a value that came back
|
||||
with no region behind it, or this turned off."
|
||||
is set to.
|
||||
|
||||
Where they overlap — an expression, whose value can go both places — both are
|
||||
written. The overlay used to suppress the echo, on the argument that saying
|
||||
one number twice teaches a reader to skip both; in practice the echo area is
|
||||
the half that is still there after the next keystroke takes the overlay down,
|
||||
and a value that is gone before it was read is worse than a value said twice.
|
||||
So the two are now independent: this one decides the overlay, and
|
||||
`flan-echo-result' decides the echo, and turning this off leaves the echo as
|
||||
the only place a value appears — which is also what happens where no overlay
|
||||
could be drawn at all, as at the REPL."
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom flan-names-shown 4
|
||||
@ -118,6 +129,18 @@ build that is merely slow can be watched, which is what the reply timeout
|
||||
below points at."
|
||||
:type 'string)
|
||||
|
||||
(defcustom flan-diagnostics-buffer "*flan-diagnostics*"
|
||||
"Buffer every compiler diagnostic the editor is handed is appended to.
|
||||
|
||||
The overlay a refusal draws is about the command that just ran and is gone at
|
||||
the next keystroke, which is right for feedback and wrong for a message you
|
||||
want to keep: to quote one somewhere else you had to find it again in
|
||||
*Messages*, among everything else Emacs had said since. This is the other
|
||||
half — one entry per message, in the order they arrived, never cleared by
|
||||
anything but you. `M-x flan-clear-diagnostics' when it has grown past being
|
||||
useful."
|
||||
:type 'string)
|
||||
|
||||
(defcustom flan-reply-timeout 30
|
||||
"Seconds to wait for one reply from the daemon before giving up.
|
||||
|
||||
@ -755,7 +778,25 @@ It builds the program first, which for a cold project is most of this."
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
(insert (mapconcat #'identity args " ") "\n\n"))
|
||||
(setq default-directory (file-name-directory (expand-file-name file))))
|
||||
;; The buffer's own, not just the `let' above, and now load-bearing
|
||||
;; twice: a relative path in a diagnostic is relative to where the daemon
|
||||
;; ran, and that is what the jump below resolves it against.
|
||||
(setq default-directory (file-name-directory (expand-file-name file)))
|
||||
;; This is where a `main' that does not compile says so, and Flan writes
|
||||
;; a diagnostic as `file:line:col: message' — the GCC shape
|
||||
;; `compilation-error-regexp-alist' was built around, and which its
|
||||
;; `gnu' entry matches with nothing added here. (The two lines a
|
||||
;; diagnostic carries under that one, the source line and its carets,
|
||||
;; match nothing and are left as text, which is what they are.) So the
|
||||
;; buffer only has to be told it is compilation output, and `M-g M-n',
|
||||
;; `next-error' and RET on the line all reach the code.
|
||||
;;
|
||||
;; The minor mode and not `compilation-mode': the buffer is also the
|
||||
;; daemon's own log and the program's stderr, arriving for as long as the
|
||||
;; program runs, and `compilation-mode' would claim it as the output of
|
||||
;; one finished command — killing the process on a `recompile', among
|
||||
;; other things it has no business doing to a live session.
|
||||
(compilation-minor-mode 1))
|
||||
(make-process
|
||||
:name "flan-daemon" :buffer buf
|
||||
:command args
|
||||
@ -1326,6 +1367,70 @@ else, and an overlay that survived a fix would be pointing at code that is
|
||||
no longer wrong."
|
||||
(flan-clear-errors))
|
||||
|
||||
;;; The diagnostics log
|
||||
|
||||
;; The overlay above is the whole of what a refusal used to leave behind, and
|
||||
;; it is deliberately short-lived: it is feedback about one command and the
|
||||
;; next keystroke takes it away. That is right for reading and wrong for
|
||||
;; keeping. A message worth quoting somewhere else — into a bug report, into a
|
||||
;; chat window, into a file — had to be fished back out of *Messages*, where it
|
||||
;; sits among everything else Emacs has said and with no mark saying where one
|
||||
;; message ends and the next begins.
|
||||
;;
|
||||
;; So both: the overlay, unchanged, and a buffer that accumulates. One entry
|
||||
;; per message, the time it arrived on its own line above it, nothing ever
|
||||
;; removed but by hand. The message line itself is left in exactly the shape
|
||||
;; the compiler wrote it — `file:line:col: text' — which is what makes
|
||||
;; `compilation-minor-mode' below able to jump from it, and also what makes a
|
||||
;; line yanked out of here read the same as one pasted from a terminal.
|
||||
|
||||
(defun flan--diagnostics-buffer ()
|
||||
"The diagnostics buffer, made and set up if this is the first message."
|
||||
(or (get-buffer flan-diagnostics-buffer)
|
||||
(with-current-buffer (get-buffer-create flan-diagnostics-buffer)
|
||||
;; The same mode the daemon's buffer gets, for the same reason: every
|
||||
;; entry names a file, a line and a column, so `next-error' and RET
|
||||
;; should go there. A minor mode rather than `compilation-mode',
|
||||
;; because this buffer is not the output of a command that ran once.
|
||||
(compilation-minor-mode 1)
|
||||
(current-buffer))))
|
||||
|
||||
(defun flan--record-diagnostic (loc msg)
|
||||
"Append MSG, which the daemon reported at LOC, to the diagnostics log.
|
||||
LOC may be nil: a refusal the daemon could not place is still a message
|
||||
somebody may want to keep, and it is written without one."
|
||||
(when (and (stringp msg) (> (length msg) 0))
|
||||
(with-current-buffer (flan--diagnostics-buffer)
|
||||
(let ((at-end (= (point) (point-max)))
|
||||
(inhibit-read-only t))
|
||||
(save-excursion
|
||||
(goto-char (point-max))
|
||||
(unless (bolp) (insert "\n"))
|
||||
(insert (format-time-string "── %H:%M:%S ──────────────────────\n"))
|
||||
(insert (if (stringp loc) (format "%s: %s\n" loc msg)
|
||||
(format "%s\n" msg))
|
||||
"\n"))
|
||||
;; `flan--append-output's rule, and the same one: follow the tail only
|
||||
;; for somebody who was already at it, since a reader scrolled back is
|
||||
;; reading something.
|
||||
(when at-end (goto-char (point-max)))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun flan-show-diagnostics ()
|
||||
"Show every compiler message this session has been handed."
|
||||
(interactive)
|
||||
(display-buffer (flan--diagnostics-buffer)))
|
||||
|
||||
;;;###autoload
|
||||
(defun flan-clear-diagnostics ()
|
||||
"Empty the diagnostics log.
|
||||
Nothing else empties it — not connecting, not an accepted evaluation, not
|
||||
quitting the program — which is the point of it."
|
||||
(interactive)
|
||||
(with-current-buffer (flan--diagnostics-buffer)
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer))))
|
||||
|
||||
(defun flan--show-error (loc msg)
|
||||
"Mark MSG at LOC, if LOC names a file some buffer is visiting.
|
||||
Returns non-nil when it put an overlay somewhere."
|
||||
@ -1381,12 +1486,21 @@ Returns non-nil when it put an overlay somewhere."
|
||||
;; The shape is `flan-watch--ghost-text's, deliberately: " => " and a shadow
|
||||
;; face. A watched value at a spy site and an expression's value at the form
|
||||
;; you evaluated are the same thing seen two ways, and two spellings of it
|
||||
;; would read as two features. What keeps them from fighting is that they sit
|
||||
;; at different places — the watch's at end of line, this at end of form — and
|
||||
;; where a one-line top-level form makes those the same position, two
|
||||
;; after-strings at one position stack in priority order rather than one
|
||||
;; hiding the other. They are also on different properties, so neither
|
||||
;; clearing pass reaches into the other's overlays.
|
||||
;; would read as two features. The position is the watch's too — the end of
|
||||
;; the *line* the form ends on, not the end of the form. It used to be the end
|
||||
;; of the form, on the argument that a value at a different place is how you
|
||||
;; tell the two features apart; what that cost is a value wedged into the
|
||||
;; middle of a line, reflowing the code around it, every time the form
|
||||
;; evaluated was not the last thing on its line. Telling them apart is not a
|
||||
;; job worth that, and it was never really the position doing it — the watch
|
||||
;; buffer's values are standing annotations on a running program and this one
|
||||
;; is gone at the next keystroke, which is a difference you can see.
|
||||
;;
|
||||
;; So they do now land at one position where a watched call and an evaluated
|
||||
;; form share a line. Two after-strings at one position stack in priority
|
||||
;; order rather than one hiding the other, which is what that case did before
|
||||
;; on a one-line top-level form. They are also on different properties, so
|
||||
;; neither clearing pass reaches into the other's overlays.
|
||||
|
||||
(defface flan-result-face '((t :inherit shadow))
|
||||
"Face for an expression's value, shown beside the form it came from."
|
||||
@ -1410,18 +1524,24 @@ Returns non-nil when it put an overlay somewhere."
|
||||
(flan-clear-result))
|
||||
|
||||
(defun flan--show-result (value at)
|
||||
"Draw VALUE after position AT. Returns non-nil when it drew one.
|
||||
"Draw VALUE at the end of the line AT is on. Returns non-nil when it drew.
|
||||
|
||||
AT is a position in the form that was evaluated and the value goes at the end
|
||||
of its line, for the reason the section above gives.
|
||||
|
||||
Empty overlays rather than a region: the value is not a property of any text,
|
||||
so nothing should be highlighted and nothing should move when the buffer is
|
||||
edited under it. Returning whether it drew is what lets the caller fall back
|
||||
to the echo area instead of losing the value entirely."
|
||||
edited under it. Whether it drew is no longer what decides the echo area —
|
||||
both are written now — and is still answered, because the REPL and a value
|
||||
with no buffer behind it are cases where nothing was drawn and the caller may
|
||||
want to know."
|
||||
(when (and flan-inline-result at (buffer-live-p (current-buffer)))
|
||||
;; The old one first, so a second evaluation in one command — which is
|
||||
;; what `flan-eval-buffer' and a macro of these amount to — leaves one
|
||||
;; value and not a column of them.
|
||||
(flan-clear-result)
|
||||
(let ((ov (make-overlay at at nil t nil)))
|
||||
(let* ((eol (save-excursion (goto-char at) (line-end-position)))
|
||||
(ov (make-overlay eol eol nil t nil)))
|
||||
(overlay-put ov 'flan-result t)
|
||||
(overlay-put ov 'after-string
|
||||
(propertize (concat " => " value)
|
||||
@ -1821,10 +1941,11 @@ of the tenth name tells you neither how many there were nor which."
|
||||
|
||||
(defun flan--report (reply what &optional at)
|
||||
"Report REPLY, describing WHAT was sent.
|
||||
AT, when given, is where in the current buffer an expression's value may be
|
||||
drawn — the end of the form that was sent. A reply with no value in it never
|
||||
reaches an overlay whatever AT says, because an install is a sentence and not
|
||||
a value and has nothing to sit beside."
|
||||
AT, when given, is the end of the form that was sent: the position that says
|
||||
which *line* an expression's value belongs to, since that is where
|
||||
`flan--show-result' draws it. A reply with no value in it never reaches an
|
||||
overlay whatever AT says, because an install is a sentence and not a value and
|
||||
has nothing to sit beside."
|
||||
(if (equal (plist-get reply :status) "ok")
|
||||
(let ((fns (plist-get reply :fns))
|
||||
(names (plist-get reply :names))
|
||||
@ -1839,13 +1960,20 @@ a value and has nothing to sit beside."
|
||||
;; ...and a name that was just installed should complete, and have a
|
||||
;; signature, from this moment rather than from the next connect.
|
||||
(when (or fns names) (ignore-errors (flan-refresh-defs)))
|
||||
;; The overlay first, because whether it drew is what decides the echo
|
||||
;; area's business: a value shown beside the form does not want saying
|
||||
;; again one line below it. Independent of `flan-echo-result' on
|
||||
;; purpose — turning the echo off is a statement about the echo, and
|
||||
;; it should not silently take the inline value with it.
|
||||
(let ((shown (and value (ignore-errors (flan--show-result value at)))))
|
||||
(when (and flan-echo-result (not shown))
|
||||
;; Both, where both are possible. The overlay is the one that answers
|
||||
;; "which form was that?" and the echo area is the one that is still
|
||||
;; readable after the next keystroke has taken the overlay down —
|
||||
;; between a value said twice and a value gone before it was read, the
|
||||
;; second is the worse failure, and it is the one that kept happening.
|
||||
;; The two settings are independent in both directions: turning the
|
||||
;; echo off is a statement about the echo and does not take the inline
|
||||
;; value with it, and turning the inline value off leaves the echo.
|
||||
;;
|
||||
;; The overlay first, still — not because the echo waits on the answer
|
||||
;; any more, but because the order is what survives: a `message'
|
||||
;; written first is wiped by whatever the drawing does to the buffer.
|
||||
(when value (ignore-errors (flan--show-result value at)))
|
||||
(when flan-echo-result
|
||||
(cond
|
||||
;; An expression's value, rendered inside the running program —
|
||||
;; nothing was marshalled back, because nothing could be.
|
||||
@ -1874,12 +2002,16 @@ a value and has nothing to sit beside."
|
||||
(let ((vars (and fns (seq-difference names fns))))
|
||||
(if vars (format " (also %s)"
|
||||
(flan--names-phrase vars ""))
|
||||
""))))))))
|
||||
"")))))))
|
||||
;; The daemon reports where, so mark it there. This must not itself
|
||||
;; signal: the error the caller is owed is the daemon's, and losing it to a
|
||||
;; bad location would report the wrong thing entirely.
|
||||
(let ((loc (plist-get reply :loc))
|
||||
(msg (plist-get reply :message)))
|
||||
;; Logged before it is drawn, and whether or not it can be drawn: the
|
||||
;; overlay needs a location and a buffer visiting the file, and the
|
||||
;; message is worth keeping in either case.
|
||||
(ignore-errors (flan--record-diagnostic loc (or msg "rejected")))
|
||||
;; A refusal is not a value. `flan--show-error' clears the buffer it
|
||||
;; marks, but a rejection the client cannot place — no `:loc', or a file
|
||||
;; nobody is visiting — marks nothing, and leaving the last value up
|
||||
@ -2004,34 +2136,44 @@ daemon reads that as stopping on entry instead."
|
||||
;; question of it: the top-level form point is *in*, which for a bare
|
||||
;; `(+ 1 1)' written at column 1 is that expression and nothing else.
|
||||
;;
|
||||
;; Hardcoded, which is the thing to be careful about: the authority is the
|
||||
;; declaration arm of `Parse.expr' in lib/parse.ml (the "is a top-level
|
||||
;; declaration, not an expression" refusal, around line 458), plus the
|
||||
;; `declare'/`declare-c' arm a few forms below it. This list is that set and
|
||||
;; has to be changed with it. `defunion' is being renamed `defdata', with
|
||||
;; `defunion' becoming a C-style untagged union; both spellings are top-level
|
||||
;; declarations either way, so this list wants `defdata' adding when that
|
||||
;; lands rather than a swap.
|
||||
;; Hardcoded, which is the thing to be careful about: the authority is
|
||||
;; `Parse.decl' in lib/parse.ml — every head it has an arm for is a
|
||||
;; declaration and belongs here — and this list is that set minus `package',
|
||||
;; for the reason below. Re-derived whole when it is touched, rather than
|
||||
;; topped up a name at a time: patching in the one name somebody noticed is
|
||||
;; exactly how it came to be missing `defdata' and all four of the object and
|
||||
;; dispatch heads for weeks after they landed. (`Parse.decl's top-level
|
||||
;; `(do ...)' arm is not a head — it splices the declarations inside it, and
|
||||
;; the forms this list is matched against are what comes out of that.)
|
||||
;;
|
||||
;; `package' is deliberately not here even though flan-mode.el's
|
||||
;; `flan--definers' has it. This is the set of heads that *fail* when sent to
|
||||
;; the expression evaluator, which is the bug `C-x C-e' was fixed for, and a
|
||||
;; `package' form does not fail — `Parse.expr' has no arm refusing it. It is
|
||||
;; still a declaration, so `C-c C-c' has to keep installing it; see
|
||||
;; `flan--defun-heads' below, which is this list plus that one head.
|
||||
;; It used to be derived from somewhere else: the declaration arm of
|
||||
;; `Parse.expr', the "is a top-level declaration, not an expression" refusal,
|
||||
;; on the reading that this is the set of heads that *fail* when sent to the
|
||||
;; expression evaluator. That reading does not survive `declare' and
|
||||
;; `declare-c', which have been in this list from the start and which
|
||||
;; `Parse.expr' has no arm for at all: sent as expressions they parse as calls
|
||||
;; of an unknown name, and the message that comes back is worse than the one
|
||||
;; the declaration path gives. Which is an argument for installing them, not
|
||||
;; for leaving them out. So the rule is the plain one — is it a declaration —
|
||||
;; and `package' is a single named exception rather than the edge of a subtler
|
||||
;; rule that was never quite true.
|
||||
(defconst flan--declaration-heads
|
||||
'("defmacro" "defn" "defvar" "defconst" "defstruct" "defunion" "defenum"
|
||||
"defalias" "import" "declare" "declare-c")
|
||||
'("defmacro" "defn" "defvar" "defconst"
|
||||
"defstruct" "defdata" "defunion" "defenum" "defalias"
|
||||
;; The object and dispatch heads (lib/parse.ml:1277-1334), which are
|
||||
;; declarations in exactly the way `defn' is: each introduces a top-level
|
||||
;; name and none of them is an expression.
|
||||
"defclass" "defgeneric" "defmulti" "defmethod"
|
||||
"import" "declare" "declare-c")
|
||||
"Heads whose form is a declaration, and never an expression.")
|
||||
|
||||
;; The two keys ask *nearly* the same question, and `package' is where the two
|
||||
;; readings come apart. `C-x C-e' asks "would this fail if I sent it to the
|
||||
;; expression evaluator?", and a `package' form would not: `Parse.expr' has no
|
||||
;; arm refusing it, so it falls through to a call of an unknown name and the
|
||||
;; message would be a worse one than the declaration path gives. `C-c C-c'
|
||||
;; asks the plain question — "is this a declaration?" — and `package' is one:
|
||||
;; `Parse.decl' has an arm for it, `C-c C-c' has always installed it, and
|
||||
;; routing it to the expression evaluator would take that away.
|
||||
;; `package' is the one declaration `C-x C-e' does not claim, and it is kept
|
||||
;; out to keep a behaviour rather than to follow a rule: the two keys have
|
||||
;; always differed here, `C-c C-c' installing a `package' form and `C-x C-e'
|
||||
;; leaving it to the expression evaluator. Nothing wants changing — a
|
||||
;; `package' line is written once at the top of a file and is not a thing
|
||||
;; anybody re-evaluates by hand — so the difference stays, named here where it
|
||||
;; can be found rather than inferred from a list that is otherwise complete.
|
||||
(defconst flan--defun-heads (cons "package" flan--declaration-heads)
|
||||
"Heads `C-c C-c' recompiles and installs rather than evaluating.")
|
||||
|
||||
@ -2181,8 +2323,9 @@ declaration for it to live in."
|
||||
;; start of the *next* line in every file that ends a line after a form
|
||||
;; — which is every file. The declaration path never noticed, because
|
||||
;; a newline more or less in the text sent changes nothing. This one
|
||||
;; draws a value at END, and at the untrimmed one it lands in column 0
|
||||
;; of the line below, over the gap before the next form.
|
||||
;; draws a value on END's line, and the untrimmed END is on the line
|
||||
;; below the form — so the value would sit at the end of the blank line
|
||||
;; after it, or at the end of whatever form comes next.
|
||||
(let ((end (save-excursion (goto-char (cdr b))
|
||||
(skip-chars-backward " \t\n")
|
||||
(point))))
|
||||
@ -2458,8 +2601,10 @@ sends padded text."
|
||||
(msg (plist-get r :message)))
|
||||
;; The two refusals that reach here are a macro that never settles and
|
||||
;; a ring, and both name a macro at a location — so mark it, exactly as
|
||||
;; a refused evaluation is marked. This must not itself signal: the
|
||||
;; error the caller is owed is the daemon's.
|
||||
;; a refused evaluation is marked, and log it for the same reason.
|
||||
;; This must not itself signal: the error the caller is owed is the
|
||||
;; daemon's.
|
||||
(ignore-errors (flan--record-diagnostic loc (or msg "refused")))
|
||||
(ignore-errors (flan--show-error loc (or msg "refused")))
|
||||
(user-error "flan: %s%s" (or msg "refused")
|
||||
(if loc (format " (%s)" loc) ""))))
|
||||
|
||||
@ -496,20 +496,23 @@ already rely on it — so nothing here is a stand-in for the real thing."
|
||||
(test-flan--check "nor does a bare expression at top level"
|
||||
(null (flan--declaration-before-point)))
|
||||
;; ...and it still evaluates as one, which is the half of this key that
|
||||
;; was already working and must not have moved. The value is now drawn
|
||||
;; beside the form and no longer echoed, so both halves are asserted: an
|
||||
;; overlay that says it, and an echo area that does not say it twice.
|
||||
;; was already working and must not have moved. The value goes to both
|
||||
;; places, so both halves are asserted: an overlay that says it, and an
|
||||
;; echo area that says it too. The echo used to be suppressed whenever the
|
||||
;; overlay drew; what that cost is a value gone at the next keystroke with
|
||||
;; nothing left anywhere, which is worse than a number said twice.
|
||||
(let ((said (test-flan--said (flan-eval-last-sexp))))
|
||||
(test-flan--check "which C-x C-e evaluates and shows beside the form"
|
||||
(let ((r (test-flan--result)))
|
||||
(and r (string-match-p "5" r)
|
||||
(string-match-p "=>" r))))
|
||||
(test-flan--check "and does not also say it in the echo area"
|
||||
(not (and said (string-match-p "=>" said))))
|
||||
;; The overlay is anchored to the form that produced it, not to point
|
||||
;; and not to the start of the line: two expressions one under the
|
||||
;; other both returning 2 is the case the echo area cannot answer.
|
||||
(test-flan--check "the value is drawn at the end of the form"
|
||||
(test-flan--check "and says it in the echo area as well"
|
||||
(and said (string-match-p "=> 5" said)))
|
||||
;; The overlay is anchored to the line the form ends on — `flan-watch's
|
||||
;; position, and now this one's too, so that a value never lands in the
|
||||
;; middle of a line and reflows the code after it. Here the form is the
|
||||
;; last thing in the buffer, so that line's end is `point-max'.
|
||||
(test-flan--check "the value is drawn at the end of the form's line"
|
||||
(let ((ovs (flan--result-overlays)))
|
||||
(and (= 1 (length ovs))
|
||||
(= (overlay-start (car ovs)) (point-max)))))
|
||||
@ -531,14 +534,35 @@ already rely on it — so nothing here is a stand-in for the real thing."
|
||||
(current-buffer))))))))
|
||||
(delete-region beg (point-max)))
|
||||
|
||||
;; The position that changed, and the only arrangement that can show it: with
|
||||
;; something written after the form on its line, the end of the form and the
|
||||
;; end of the line are different places. The value belongs at the second,
|
||||
;; where `flan-watch' has always put one — a value wedged into the middle of
|
||||
;; a line reflows every character after it.
|
||||
(goto-char (point-max))
|
||||
(let ((beg (point)))
|
||||
(insert "\n(+ 2 3) ; and a comment after it")
|
||||
(search-backward ") ;")
|
||||
(forward-char 1)
|
||||
(test-flan--said (flan-eval-last-sexp))
|
||||
(test-flan--check "the value goes to the end of the line, not of the form"
|
||||
(let ((ovs (flan--result-overlays)))
|
||||
(and (= 1 (length ovs))
|
||||
(= (overlay-start (car ovs)) (point-max)))))
|
||||
;; What a keystroke would do; there are no commands under --batch.
|
||||
(flan-clear-result)
|
||||
(goto-char (point-max))
|
||||
(delete-region beg (point-max)))
|
||||
|
||||
;; Switched off, the echo area is what is left: the two settings are not two
|
||||
;; spellings of one thing, and a value has to come out somewhere.
|
||||
;; spellings of one thing, and turning the overlay off must not take the
|
||||
;; value with it.
|
||||
(goto-char (point-max))
|
||||
(let ((beg (point)))
|
||||
(insert "\n(+ 7 8)")
|
||||
(let* ((flan-inline-result nil)
|
||||
(said (test-flan--said (flan-eval-last-sexp))))
|
||||
(test-flan--check "with the overlay off the value goes back to the echo area"
|
||||
(test-flan--check "with the overlay off the value is still in the echo area"
|
||||
(and said (string-match-p "15" said)
|
||||
(string-match-p "=>" said)
|
||||
(null (flan--result-overlays)))))
|
||||
@ -599,12 +623,13 @@ already rely on it — so nothing here is a stand-in for the real thing."
|
||||
(test-flan--check "and draws no value beside it"
|
||||
(null (flan--result-overlays))))
|
||||
|
||||
;; `package' is the one head the two keys disagree about, and it is a real
|
||||
;; disagreement rather than an oversight: `Parse.expr' has no arm refusing
|
||||
;; it, so C-x C-e's "would this fail as an expression" says no, while
|
||||
;; `Parse.decl' has one and C-c C-c has always installed it. Routing both
|
||||
;; off one list would have quietly taken that away. Nothing is sent here —
|
||||
;; the claim is only about which path each key picks.
|
||||
;; `package' is the one head the two keys disagree about, and the
|
||||
;; disagreement is a behaviour being kept rather than a rule being followed:
|
||||
;; `C-c C-c' has always installed a `package' form and `C-x C-e' has always
|
||||
;; left it to the expression evaluator, and nothing about a line written once
|
||||
;; at the top of a file asks for that to change. Routing both keys off one
|
||||
;; list would quietly take it away. Nothing is sent here — the claim is only
|
||||
;; about which path each key picks.
|
||||
(with-temp-buffer
|
||||
(flan-mode)
|
||||
(insert "(package demo)\n")
|
||||
@ -618,6 +643,33 @@ already rely on it — so nothing here is a stand-in for the real thing."
|
||||
(test-flan--check "and C-x C-e does not, for the reason it never has"
|
||||
(null (flan--declaration-before-point))))
|
||||
|
||||
;; And every other head does agree, which is the half that had rotted: the
|
||||
;; list was topped up by name and so lost `defdata' and all four object and
|
||||
;; dispatch heads for as long as they had existed. A head missing from it
|
||||
;; is not a quiet fallback — the form goes to the expression evaluator and
|
||||
;; the daemon refuses it with "is a top-level declaration, not an
|
||||
;; expression", which is the complaint this is here to keep from coming
|
||||
;; back. Read off `Parse.decl', so the check is the derivation.
|
||||
(with-temp-buffer
|
||||
(flan-mode)
|
||||
(dolist (head '("defmacro" "defn" "defvar" "defconst" "defstruct"
|
||||
"defdata" "defunion" "defenum" "defalias"
|
||||
"defclass" "defgeneric" "defmulti" "defmethod"
|
||||
"import" "declare" "declare-c"))
|
||||
(erase-buffer)
|
||||
;; A body neither key needs to be valid: both questions are asked of the
|
||||
;; head alone, and a form that parses is a different test.
|
||||
(insert "(" head " thing [x])\n")
|
||||
(goto-char (point-min))
|
||||
(search-forward "thing")
|
||||
(test-flan--check (format "C-c C-c installs a %s form" head)
|
||||
(equal (flan--declaration-head-at
|
||||
(car (flan--defun-bounds)) flan--defun-heads)
|
||||
head))
|
||||
(goto-char (point-max))
|
||||
(test-flan--check (format "and C-x C-e installs one too, for %s" head)
|
||||
(equal (car (flan--declaration-before-point)) head))))
|
||||
|
||||
;; A prefix on the expression path is a flag and not a position — there is
|
||||
;; no inside for one to point at — so both `C-u' and `C-u C-u' mean the one
|
||||
;; thing, which is `C-u C-x C-e's behaviour reached through the other key.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user