Editor code cut out mid-line carries :indent, the column its statement starts at, so the reader accepts its wrapped lines as the file does and reports every location where it is in the buffer
This commit is contained in:
parent
c3ba4c9e08
commit
f1bdcc8840
@ -548,32 +548,31 @@ or the fallback call's name, `defmethod(', must read as one of HEADS,
|
||||
(defun flan-fln--client ()
|
||||
(require 'flan))
|
||||
|
||||
(defun flan-fln--cut-text (beg end)
|
||||
"The text BEG..END, as the reader must see it when BEG is mid-line.
|
||||
(defun flan-fln--cut-indent (beg)
|
||||
"The byte column of the statement BEG was cut out of, when BEG is mid-line.
|
||||
A condition or an arm's value starts after `elif ' or `-> ', and a line that
|
||||
continues it is indented past the statement's start in the file but perhaps
|
||||
not past the value's own column, which the reader, seeded at that column,
|
||||
requires. So every line after the first moves right by the width of what
|
||||
was cut off in front of the first; errors on the first line keep their
|
||||
exact column."
|
||||
(let* ((text (buffer-substring-no-properties beg end))
|
||||
(delta (save-excursion
|
||||
(goto-char beg)
|
||||
(- (current-column) (current-indentation)))))
|
||||
(if (<= delta 0)
|
||||
text
|
||||
(replace-regexp-in-string "\n" (concat "\n" (make-string delta ?\s))
|
||||
text t t))))
|
||||
continues it is indented past the statement's start, perhaps not past the
|
||||
cut. The reader is told where the statement starts (`:indent'), so it
|
||||
reads the text as the file has it, and every location it reports is still
|
||||
the buffer's own."
|
||||
(save-excursion
|
||||
(goto-char beg)
|
||||
(when (> (current-column) (current-indentation))
|
||||
(back-to-indentation)
|
||||
(1+ (- (position-bytes (point))
|
||||
(position-bytes (line-beginning-position)))))))
|
||||
|
||||
(defun flan-fln--eval-expression (beg end arg)
|
||||
"Evaluate BEG..END as an expression, as `flan--eval-expression' does, with
|
||||
the text shaped by `flan-fln--cut-text'."
|
||||
"Evaluate BEG..END as an expression, as `flan--eval-expression' does, and
|
||||
with `:indent' when BEG is mid-line."
|
||||
(flan--report
|
||||
(flan--request
|
||||
(let ((at (flan--text-at beg end)))
|
||||
(append (list :op "eval-expr" :code (flan-fln--cut-text beg end)
|
||||
(let ((at (flan--text-at beg end))
|
||||
(indent (flan-fln--cut-indent beg)))
|
||||
(append (list :op "eval-expr" :code (car at)
|
||||
:file (or buffer-file-name "<buffer>"))
|
||||
(cdr at)
|
||||
(when indent (list :indent indent))
|
||||
(when arg (list :pause t)))))
|
||||
"expression"
|
||||
end))
|
||||
|
||||
@ -56,6 +56,9 @@ comment():
|
||||
if 1 < 2 and
|
||||
3 < 4
|
||||
twice(1)
|
||||
elif 1 > 2 or
|
||||
3 > 4
|
||||
0
|
||||
twice(4)
|
||||
if 2 > 1
|
||||
twice(2)
|
||||
@ -172,6 +175,36 @@ comment():
|
||||
(flan-fln-eval-last)
|
||||
(test-flan--check (funcall name "C-x C-e on a wrapped condition evaluates all of it")
|
||||
(funcall shows "true"))
|
||||
;; An error on the wrapped line of a condition or value cut out
|
||||
;; mid-line is reported where it is in the buffer, column and all.
|
||||
(let ((refused
|
||||
(lambda (needle bad fix)
|
||||
(funcall goto needle t)
|
||||
(let ((line (1+ (line-number-at-pos))) col)
|
||||
(save-excursion
|
||||
(forward-line 1)
|
||||
(search-forward fix (line-end-position))
|
||||
(replace-match bad t t)
|
||||
(setq col (1+ (- (point) (line-beginning-position)
|
||||
(length (car (last (split-string bad " "))))))))
|
||||
(prog1 (list (condition-case err (progn (flan-fln-eval-last) nil)
|
||||
(user-error (error-message-string err)))
|
||||
(format ":%d:%d)" line col))
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(search-forward bad)
|
||||
(replace-match fix t t)))))))
|
||||
(pcase-dolist (`(,what ,needle ,bad ,fix)
|
||||
'(("an if condition" "if 1 < 2 and" "3 < 4 4" "3 < 4")
|
||||
("an elif condition" "elif 1 > 2 or" "3 > 4 4" "3 > 4")
|
||||
("an arm's value" ":east -> 1 +" "2 2" "2")))
|
||||
(let ((r (funcall refused needle bad fix)))
|
||||
(test-flan--check
|
||||
(funcall name (format "an error on the wrapped line of %s is reported at its column" what))
|
||||
(and (car r) (string-suffix-p (cadr r) (car r))))
|
||||
(unless (and (car r) (string-suffix-p (cadr r) (car r)))
|
||||
(message " want ...%s\n got %S" (cadr r) (car r))))))
|
||||
(flan-clear-errors)
|
||||
(funcall goto "if 2 > 1" t)
|
||||
(flan-fln-eval-last)
|
||||
(test-flan--check (funcall name "C-x C-e at the end of an if line evaluates the condition")
|
||||
|
||||
@ -460,12 +460,12 @@ comment:
|
||||
"twice(1)\n twice(2)"))
|
||||
(test-flan-fln--is "C-x C-e at the end of an if line sends its condition"
|
||||
(test-flan-fln--last-at "if n < 0") '("eval-expr" "n < 0"))
|
||||
(test-flan-fln--is "and of an elif, all of its condition, the wrapped line moved right by what was cut off"
|
||||
(test-flan-fln--is "and of an elif, all of its condition"
|
||||
(test-flan-fln--last-at "or n == 1")
|
||||
'("eval-expr" "n == 0\n or n == 1"))
|
||||
'("eval-expr" "n == 0\n or n == 1"))
|
||||
(test-flan-fln--is "and the same from the end of the condition's first line"
|
||||
(test-flan-fln--last-at "elif n == 0")
|
||||
'("eval-expr" "n == 0\n or n == 1"))
|
||||
'("eval-expr" "n == 0\n or n == 1"))
|
||||
|
||||
(defconst test-flan-fln--wrapped
|
||||
"fn f(o: Option(i64)) -> i64
|
||||
@ -489,19 +489,29 @@ comment:
|
||||
|
||||
(test-flan-fln--is "an arm's value wrapped onto a second line goes whole, moved right"
|
||||
(test-flan-fln--wrapped-at "None" #'flan-fln-eval-last)
|
||||
"1 +\n 2")
|
||||
"1 +\n 2")
|
||||
(test-flan-fln--is "from its second line too"
|
||||
(test-flan-fln--wrapped-at " 2\n if" #'flan-fln-eval-last)
|
||||
"1 +\n 2")
|
||||
"1 +\n 2")
|
||||
(test-flan-fln--is "and C-c C-e on it sends the same"
|
||||
(test-flan-fln--wrapped-at "None" #'flan-fln-eval-statement)
|
||||
"1 +\n 2")
|
||||
"1 +\n 2")
|
||||
(test-flan-fln--is "a wrapped if condition, from the end of its first line"
|
||||
(test-flan-fln--wrapped-at "if 1 < 2" #'flan-fln-eval-last)
|
||||
"1 < 2 and\n 3 < 4")
|
||||
"1 < 2 and\n 3 < 4")
|
||||
(test-flan-fln--is "a statement wrapped by an operator, from the end of its first line"
|
||||
(test-flan-fln--wrapped-at "x = 1 +" #'flan-fln-eval-last)
|
||||
"x = 1 +\n 2")
|
||||
(test-flan-fln--in (test-flan-fln--at test-flan-fln--wrapped "None")
|
||||
(end-of-line)
|
||||
(let ((r (test-flan-fln--sending (flan-fln-eval-last))))
|
||||
(test-flan-fln--is "a value cut mid-line says where its statement starts"
|
||||
(list (plist-get r :line) (plist-get r :col) (plist-get r :indent))
|
||||
'(6 13 5))))
|
||||
(test-flan-fln--in (test-flan-fln--at test-flan-fln--wrapped "x = 1")
|
||||
(end-of-line)
|
||||
(let ((r (test-flan-fln--sending (flan-fln-eval-last))))
|
||||
(test-flan--check "a whole statement does not" (null (plist-member r :indent)))))
|
||||
(test-flan-fln--is "an arm whose pattern binds nothing sends its value"
|
||||
(test-flan-fln--wrapped-at "Some(_)" #'flan-fln-eval-last) "5")
|
||||
(test-flan-fln--is "nor one whose value does not use what it binds"
|
||||
|
||||
@ -4371,7 +4371,8 @@ let rec handle t req =
|
||||
| Some l, None -> Some (l, 1)
|
||||
| _ -> None
|
||||
in
|
||||
Source.with_code ~syntax ~at (fun () -> handle_op t req)
|
||||
let indent = Wire.int_field req "indent" in
|
||||
Source.with_code ?indent ~syntax ~at (fun () -> handle_op t req)
|
||||
|
||||
and handle_op t req =
|
||||
match Wire.string_field req "op" with
|
||||
|
||||
@ -220,7 +220,7 @@ let point (l : Loc.t) = { l with Loc.line = l.Loc.eline; col = l.Loc.ecol }
|
||||
(* NEWLINE, INDENT and DEDENT, at bracket depth zero only: inside ( [ { a
|
||||
line break is whitespace. A line continues the one before it when either
|
||||
side of the break is a spaced binary operator (spec §2 "Continuation"). *)
|
||||
let layout ?(snippet = false) ?(base = 1) (toks : token list) : token array =
|
||||
let layout ?(snippet = false) ?(base = 1) ?indent (toks : token list) : token array =
|
||||
let arr = Array.of_list toks in
|
||||
let n = Array.length arr in
|
||||
(* A snippet from the editor starts wherever it was written, and its first
|
||||
@ -229,6 +229,11 @@ let layout ?(snippet = false) ?(base = 1) (toks : token list) : token array =
|
||||
let out = ref [] in
|
||||
let add tok loc = out := { tok; loc; sp = true } :: !out in
|
||||
let stack = ref [ base ] in
|
||||
(* [indent] is the column of the statement a snippet was cut out of, when
|
||||
the snippet starts after that statement's first word (an elif's
|
||||
condition, an arm's value). Its first joined line continues as it does
|
||||
in the file: deeper than the statement, not than the cut. *)
|
||||
let first_line = ref true in
|
||||
let depth = ref 0 in
|
||||
let binop t = match t.tok with NAME s -> is_binop s | _ -> false in
|
||||
for i = 0 to n - 1 do
|
||||
@ -248,11 +253,16 @@ let layout ?(snippet = false) ?(base = 1) (toks : token list) : token array =
|
||||
&& arr.(i + 1).sp
|
||||
in
|
||||
let continues = (binop p && p.sp) || (binop t && spaced_after) in
|
||||
let top =
|
||||
match indent with
|
||||
| Some c when !first_line && List.length !stack = 1 -> min c (List.hd !stack)
|
||||
| _ -> List.hd !stack
|
||||
in
|
||||
(* A continuation line sits deeper than the statement it continues.
|
||||
One at or left of that statement's column is not read as joining
|
||||
it: that would pull a line into a block it was written outside
|
||||
of, silently. *)
|
||||
if continues && t.loc.Loc.col <= List.hd !stack then
|
||||
if continues && t.loc.Loc.col <= top then
|
||||
failk "continuation" t.loc
|
||||
"%s"
|
||||
(if binop t then
|
||||
@ -261,15 +271,16 @@ let layout ?(snippet = false) ?(base = 1) (toks : token list) : token array =
|
||||
line above, but it is not indented past the start of that \
|
||||
line (column %d). Indent it further to continue the line, \
|
||||
or give %s a value on its left"
|
||||
(show t.tok) (List.hd !stack) (show t.tok)
|
||||
(show t.tok) top (show t.tok)
|
||||
else
|
||||
Printf.sprintf
|
||||
"the line above ends with the operator %s, so this line \
|
||||
continues it, but it is not indented past the start of \
|
||||
that line (column %d). Indent it further, or finish the \
|
||||
line above"
|
||||
(show p.tok) (List.hd !stack));
|
||||
(show p.tok) top);
|
||||
if not continues then begin
|
||||
first_line := false;
|
||||
let at = point p.loc in
|
||||
add NEWLINE at;
|
||||
let col = t.loc.Loc.col in
|
||||
@ -1556,7 +1567,7 @@ and lines (s : st) (one : unit -> Form.t list) : Form.t list =
|
||||
|
||||
(** All top-level forms in a [.fln] source string. [col] is the column the
|
||||
text's top level starts at, 1 for a file. *)
|
||||
let read_all ?(line = 1) ?col ~file src =
|
||||
let read_all ?(line = 1) ?col ?indent ~file src =
|
||||
let snippet = col <> None in
|
||||
let col = Option.value col ~default:1 in
|
||||
let saved = !source in
|
||||
@ -1566,7 +1577,7 @@ let read_all ?(line = 1) ?col ~file src =
|
||||
(file, Array.of_list (String.split_on_char '\n'
|
||||
(String.make (line - 1) '\n' ^ String.make (col - 1) ' ' ^ src)));
|
||||
Fun.protect ~finally:(fun () -> source := saved) (fun () ->
|
||||
let toks = layout ~snippet ~base:col (lex ~line ~col ~file src) in
|
||||
let toks = layout ~snippet ~base:col ?indent (lex ~line ~col ~file src) in
|
||||
let s = { p = { toks; i = 0 }; lets = [] } in
|
||||
let fs = stmts s in
|
||||
(match (peek s.p).tok with
|
||||
|
||||
@ -33,15 +33,21 @@ type syntax = Paren | Indented
|
||||
let code_syntax = ref Paren
|
||||
let code_at : (int * int) option ref = ref None
|
||||
|
||||
(* The column of the statement editor code was cut out of, when the code
|
||||
starts after that statement's first word: see [Indent_reader.layout]. *)
|
||||
let code_indent : int option ref = ref None
|
||||
|
||||
let syntax_of_field = function
|
||||
| Some ("indented" | "fln") -> Indented
|
||||
| _ -> Paren
|
||||
|
||||
let with_code ~syntax ~at f =
|
||||
let s = !code_syntax and a = !code_at in
|
||||
let with_code ?indent ~syntax ~at f =
|
||||
let s = !code_syntax and a = !code_at and i = !code_indent in
|
||||
code_syntax := syntax;
|
||||
code_at := at;
|
||||
Fun.protect ~finally:(fun () -> code_syntax := s; code_at := a) f
|
||||
code_indent := indent;
|
||||
Fun.protect
|
||||
~finally:(fun () -> code_syntax := s; code_at := a; code_indent := i) f
|
||||
|
||||
(* The paren reader started at a line and column: [Reader.read_all] always
|
||||
starts at 1:1. *)
|
||||
@ -65,7 +71,7 @@ let read_code ?(expr = false) ~file code =
|
||||
match !code_syntax with
|
||||
| Paren -> read_paren ~line ~col ~file code
|
||||
| Indented ->
|
||||
(match Indent_reader.read_all ~line ~col ~file code with
|
||||
(match Indent_reader.read_all ~line ~col ?indent:!code_indent ~file code with
|
||||
| (first :: _ :: _ as forms) when expr ->
|
||||
let last = List.nth forms (List.length forms - 1) in
|
||||
let loc =
|
||||
|
||||
@ -488,6 +488,22 @@ let () =
|
||||
| [ _; _ ] -> ()
|
||||
| _ -> fail "a snippet with leading spaces"
|
||||
| exception e -> fail "a snippet with leading spaces: %s" (diag_text e));
|
||||
(* A condition cut out from after [elif ] at column 3: its wrapped line at
|
||||
column 8 is deeper than the elif, which is what the file says, though not
|
||||
deeper than the cut. [:indent] says where the statement starts; every
|
||||
location stays the buffer's own. *)
|
||||
Source.with_code ~indent:3 ~syntax:Source.Indented ~at:(Some (10, 8)) (fun () ->
|
||||
(match Source.read_code ~expr:true ~file:"<buf>" "x == 0 or\n x == 1" with
|
||||
| [ f ] -> span_is "a wrapped condition, cut mid-line" f (10, 8, 11, 14)
|
||||
| _ -> fail "a wrapped condition read as more than one form"
|
||||
| exception e -> fail "a wrapped condition: %s" (diag_text e));
|
||||
match Source.read_code ~expr:true ~file:"<buf>" "x == 0 or\n x == 1" with
|
||||
| _ -> fail "a continuation left of its statement was read"
|
||||
| exception Loc.Error _ -> ());
|
||||
Source.with_code ~syntax:Source.Indented ~at:(Some (10, 8)) (fun () ->
|
||||
match Source.read_code ~expr:true ~file:"<buf>" "x == 0 or\n x == 1" with
|
||||
| _ -> fail "without :indent, a wrapped line is measured from the cut"
|
||||
| exception Loc.Error _ -> ());
|
||||
Source.with_code ~syntax:Source.Paren ~at:(Some (7, 3)) (fun () ->
|
||||
match Source.read_code ~file:"<buf>" "(f 1)" with
|
||||
| [ f ] -> span_is "a paren snippet" f (7, 3, 7, 8)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user