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 ()
|
(defun flan-fln--client ()
|
||||||
(require 'flan))
|
(require 'flan))
|
||||||
|
|
||||||
(defun flan-fln--cut-text (beg end)
|
(defun flan-fln--cut-indent (beg)
|
||||||
"The text BEG..END, as the reader must see it when BEG is mid-line.
|
"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
|
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
|
continues it is indented past the statement's start, perhaps not past the
|
||||||
not past the value's own column, which the reader, seeded at that column,
|
cut. The reader is told where the statement starts (`:indent'), so it
|
||||||
requires. So every line after the first moves right by the width of what
|
reads the text as the file has it, and every location it reports is still
|
||||||
was cut off in front of the first; errors on the first line keep their
|
the buffer's own."
|
||||||
exact column."
|
(save-excursion
|
||||||
(let* ((text (buffer-substring-no-properties beg end))
|
(goto-char beg)
|
||||||
(delta (save-excursion
|
(when (> (current-column) (current-indentation))
|
||||||
(goto-char beg)
|
(back-to-indentation)
|
||||||
(- (current-column) (current-indentation)))))
|
(1+ (- (position-bytes (point))
|
||||||
(if (<= delta 0)
|
(position-bytes (line-beginning-position)))))))
|
||||||
text
|
|
||||||
(replace-regexp-in-string "\n" (concat "\n" (make-string delta ?\s))
|
|
||||||
text t t))))
|
|
||||||
|
|
||||||
(defun flan-fln--eval-expression (beg end arg)
|
(defun flan-fln--eval-expression (beg end arg)
|
||||||
"Evaluate BEG..END as an expression, as `flan--eval-expression' does, with
|
"Evaluate BEG..END as an expression, as `flan--eval-expression' does, and
|
||||||
the text shaped by `flan-fln--cut-text'."
|
with `:indent' when BEG is mid-line."
|
||||||
(flan--report
|
(flan--report
|
||||||
(flan--request
|
(flan--request
|
||||||
(let ((at (flan--text-at beg end)))
|
(let ((at (flan--text-at beg end))
|
||||||
(append (list :op "eval-expr" :code (flan-fln--cut-text beg end)
|
(indent (flan-fln--cut-indent beg)))
|
||||||
|
(append (list :op "eval-expr" :code (car at)
|
||||||
:file (or buffer-file-name "<buffer>"))
|
:file (or buffer-file-name "<buffer>"))
|
||||||
(cdr at)
|
(cdr at)
|
||||||
|
(when indent (list :indent indent))
|
||||||
(when arg (list :pause t)))))
|
(when arg (list :pause t)))))
|
||||||
"expression"
|
"expression"
|
||||||
end))
|
end))
|
||||||
|
|||||||
@ -56,6 +56,9 @@ comment():
|
|||||||
if 1 < 2 and
|
if 1 < 2 and
|
||||||
3 < 4
|
3 < 4
|
||||||
twice(1)
|
twice(1)
|
||||||
|
elif 1 > 2 or
|
||||||
|
3 > 4
|
||||||
|
0
|
||||||
twice(4)
|
twice(4)
|
||||||
if 2 > 1
|
if 2 > 1
|
||||||
twice(2)
|
twice(2)
|
||||||
@ -172,6 +175,36 @@ comment():
|
|||||||
(flan-fln-eval-last)
|
(flan-fln-eval-last)
|
||||||
(test-flan--check (funcall name "C-x C-e on a wrapped condition evaluates all of it")
|
(test-flan--check (funcall name "C-x C-e on a wrapped condition evaluates all of it")
|
||||||
(funcall shows "true"))
|
(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)
|
(funcall goto "if 2 > 1" t)
|
||||||
(flan-fln-eval-last)
|
(flan-fln-eval-last)
|
||||||
(test-flan--check (funcall name "C-x C-e at the end of an if line evaluates the condition")
|
(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)"))
|
"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--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--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")
|
(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--is "and the same from the end of the condition's first line"
|
||||||
(test-flan-fln--last-at "elif n == 0")
|
(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
|
(defconst test-flan-fln--wrapped
|
||||||
"fn f(o: Option(i64)) -> i64
|
"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--is "an arm's value wrapped onto a second line goes whole, moved right"
|
||||||
(test-flan-fln--wrapped-at "None" #'flan-fln-eval-last)
|
(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--is "from its second line too"
|
||||||
(test-flan-fln--wrapped-at " 2\n if" #'flan-fln-eval-last)
|
(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--is "and C-c C-e on it sends the same"
|
||||||
(test-flan-fln--wrapped-at "None" #'flan-fln-eval-statement)
|
(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--is "a wrapped if condition, from the end of its first line"
|
||||||
(test-flan-fln--wrapped-at "if 1 < 2" #'flan-fln-eval-last)
|
(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--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)
|
(test-flan-fln--wrapped-at "x = 1 +" #'flan-fln-eval-last)
|
||||||
"x = 1 +\n 2")
|
"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--is "an arm whose pattern binds nothing sends its value"
|
||||||
(test-flan-fln--wrapped-at "Some(_)" #'flan-fln-eval-last) "5")
|
(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"
|
(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)
|
| Some l, None -> Some (l, 1)
|
||||||
| _ -> None
|
| _ -> None
|
||||||
in
|
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 =
|
and handle_op t req =
|
||||||
match Wire.string_field req "op" with
|
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
|
(* NEWLINE, INDENT and DEDENT, at bracket depth zero only: inside ( [ { a
|
||||||
line break is whitespace. A line continues the one before it when either
|
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"). *)
|
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 arr = Array.of_list toks in
|
||||||
let n = Array.length arr in
|
let n = Array.length arr in
|
||||||
(* A snippet from the editor starts wherever it was written, and its first
|
(* 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 out = ref [] in
|
||||||
let add tok loc = out := { tok; loc; sp = true } :: !out in
|
let add tok loc = out := { tok; loc; sp = true } :: !out in
|
||||||
let stack = ref [ base ] 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 depth = ref 0 in
|
||||||
let binop t = match t.tok with NAME s -> is_binop s | _ -> false in
|
let binop t = match t.tok with NAME s -> is_binop s | _ -> false in
|
||||||
for i = 0 to n - 1 do
|
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
|
&& arr.(i + 1).sp
|
||||||
in
|
in
|
||||||
let continues = (binop p && p.sp) || (binop t && spaced_after) 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.
|
(* A continuation line sits deeper than the statement it continues.
|
||||||
One at or left of that statement's column is not read as joining
|
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
|
it: that would pull a line into a block it was written outside
|
||||||
of, silently. *)
|
of, silently. *)
|
||||||
if continues && t.loc.Loc.col <= List.hd !stack then
|
if continues && t.loc.Loc.col <= top then
|
||||||
failk "continuation" t.loc
|
failk "continuation" t.loc
|
||||||
"%s"
|
"%s"
|
||||||
(if binop t then
|
(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 above, but it is not indented past the start of that \
|
||||||
line (column %d). Indent it further to continue the line, \
|
line (column %d). Indent it further to continue the line, \
|
||||||
or give %s a value on its left"
|
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
|
else
|
||||||
Printf.sprintf
|
Printf.sprintf
|
||||||
"the line above ends with the operator %s, so this line \
|
"the line above ends with the operator %s, so this line \
|
||||||
continues it, but it is not indented past the start of \
|
continues it, but it is not indented past the start of \
|
||||||
that line (column %d). Indent it further, or finish the \
|
that line (column %d). Indent it further, or finish the \
|
||||||
line above"
|
line above"
|
||||||
(show p.tok) (List.hd !stack));
|
(show p.tok) top);
|
||||||
if not continues then begin
|
if not continues then begin
|
||||||
|
first_line := false;
|
||||||
let at = point p.loc in
|
let at = point p.loc in
|
||||||
add NEWLINE at;
|
add NEWLINE at;
|
||||||
let col = t.loc.Loc.col in
|
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
|
(** All top-level forms in a [.fln] source string. [col] is the column the
|
||||||
text's top level starts at, 1 for a file. *)
|
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 snippet = col <> None in
|
||||||
let col = Option.value col ~default:1 in
|
let col = Option.value col ~default:1 in
|
||||||
let saved = !source 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'
|
(file, Array.of_list (String.split_on_char '\n'
|
||||||
(String.make (line - 1) '\n' ^ String.make (col - 1) ' ' ^ src)));
|
(String.make (line - 1) '\n' ^ String.make (col - 1) ' ' ^ src)));
|
||||||
Fun.protect ~finally:(fun () -> source := saved) (fun () ->
|
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 s = { p = { toks; i = 0 }; lets = [] } in
|
||||||
let fs = stmts s in
|
let fs = stmts s in
|
||||||
(match (peek s.p).tok with
|
(match (peek s.p).tok with
|
||||||
|
|||||||
@ -33,15 +33,21 @@ type syntax = Paren | Indented
|
|||||||
let code_syntax = ref Paren
|
let code_syntax = ref Paren
|
||||||
let code_at : (int * int) option ref = ref None
|
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
|
let syntax_of_field = function
|
||||||
| Some ("indented" | "fln") -> Indented
|
| Some ("indented" | "fln") -> Indented
|
||||||
| _ -> Paren
|
| _ -> Paren
|
||||||
|
|
||||||
let with_code ~syntax ~at f =
|
let with_code ?indent ~syntax ~at f =
|
||||||
let s = !code_syntax and a = !code_at in
|
let s = !code_syntax and a = !code_at and i = !code_indent in
|
||||||
code_syntax := syntax;
|
code_syntax := syntax;
|
||||||
code_at := at;
|
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
|
(* The paren reader started at a line and column: [Reader.read_all] always
|
||||||
starts at 1:1. *)
|
starts at 1:1. *)
|
||||||
@ -65,7 +71,7 @@ let read_code ?(expr = false) ~file code =
|
|||||||
match !code_syntax with
|
match !code_syntax with
|
||||||
| Paren -> read_paren ~line ~col ~file code
|
| Paren -> read_paren ~line ~col ~file code
|
||||||
| Indented ->
|
| 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 ->
|
| (first :: _ :: _ as forms) when expr ->
|
||||||
let last = List.nth forms (List.length forms - 1) in
|
let last = List.nth forms (List.length forms - 1) in
|
||||||
let loc =
|
let loc =
|
||||||
|
|||||||
@ -488,6 +488,22 @@ let () =
|
|||||||
| [ _; _ ] -> ()
|
| [ _; _ ] -> ()
|
||||||
| _ -> fail "a snippet with leading spaces"
|
| _ -> fail "a snippet with leading spaces"
|
||||||
| exception e -> fail "a snippet with leading spaces: %s" (diag_text e));
|
| 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 () ->
|
Source.with_code ~syntax:Source.Paren ~at:(Some (7, 3)) (fun () ->
|
||||||
match Source.read_code ~file:"<buf>" "(f 1)" with
|
match Source.read_code ~file:"<buf>" "(f 1)" with
|
||||||
| [ f ] -> span_is "a paren snippet" f (7, 3, 7, 8)
|
| [ f ] -> span_is "a paren snippet" f (7, 3, 7, 8)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user