branch: elpa/haskell-tng-mode commit 10090824cd2998ad90625a4983e741cdb900b198 Author: Tseen She <ts33n....@gmail.com> Commit: Tseen She <ts33n....@gmail.com>
append indentation test --- haskell-tng-layout.el | 14 ++++ haskell-tng-mode.el | 9 ++- haskell-tng-smie.el | 2 +- test/haskell-tng-indent-test.el | 127 +++++++++++++++++++++++----------- test/src/indentation.hs.append.indent | 102 +++++++++++++++++++++++++++ 5 files changed, 210 insertions(+), 44 deletions(-) diff --git a/haskell-tng-layout.el b/haskell-tng-layout.el index 0a4f311..f4af977 100644 --- a/haskell-tng-layout.el +++ b/haskell-tng-layout.el @@ -32,8 +32,14 @@ ;; ;; Anything more complicated that small brain needs improved testing. +(require 'dash) + (require 'haskell-tng-util) +;; FIXME only search up to one line for the WLDO opener, otherwise close it out +;; with {} This is not valid compiling Haskell code, but it allows SMIE to close +;; off the s-expression. + ;; Easiest cache... full buffer parse with full invalidation on any insertion. (defvar-local haskell-tng-layout:cache nil) @@ -74,6 +80,14 @@ Designed to be called repeatedly, managing its own caching." (push ";" breaks)))))) (append (reverse closes) (reverse breaks)))))) +(defun haskell-tng-layout:has-virtual-at-point () + "t if there is a virtual at POINT" + ;; avoids a measured performance hit (append indentation) + (unless haskell-tng-layout:cache + (haskell-tng-layout:rebuild-cache-full)) + (--any (member (point) it) + haskell-tng-layout:cache)) + (defun haskell-tng-layout:rebuild-cache-full () (let (case-fold-search cache) diff --git a/haskell-tng-mode.el b/haskell-tng-mode.el index fbc9e4f..86594bc 100644 --- a/haskell-tng-mode.el +++ b/haskell-tng-mode.el @@ -73,10 +73,13 @@ Load `prettify-symbols-mode' in `haskell-tng-mode-hook'." font-lock-multiline t font-lock-extend-region-functions haskell-tng:extend-region-functions - ;; whitespace is meaningful, no electric indentation - electric-indent-inhibit t - prettify-symbols-alist haskell-tng-mode:prettify-symbols) + + ;; whitespace is meaningful, disable electric indentation. Note that + ;; `electric-indent-inhibit' causes a performance regression in SMIE + ;; indentation, so it's best to just make sure it is disabled. + (electric-indent-mode 0) + (setq-local projectile-tags-command "fast-tags -Re --exclude=dist-newstyle .") (setq-local smie-blink-matching-inners nil) ;; c.f. `smie-closer-alist' diff --git a/haskell-tng-smie.el b/haskell-tng-smie.el index 8caab9f..db485c9 100644 --- a/haskell-tng-smie.el +++ b/haskell-tng-smie.el @@ -242,7 +242,7 @@ current line." (forward-line)) (goto-char start) (while (< (point) bound) - (when (haskell-tng-layout:virtuals-at-point) + (when (haskell-tng-layout:has-virtual-at-point) (push (current-column) relevant)) (forward-char)) relevant)) diff --git a/test/haskell-tng-indent-test.el b/test/haskell-tng-indent-test.el index 07c3128..213de04 100644 --- a/test/haskell-tng-indent-test.el +++ b/test/haskell-tng-indent-test.el @@ -12,31 +12,48 @@ (require 'haskell-tng-testutils "test/haskell-tng-testutils.el") - ;; Three indentation regression tests are possible: - ;; - ;; 1. newline-and-indent with the rest of the file deleted (append) - ;; 2. newline-and-indent with the rest of the file intact (insert) - ;; 3. indent-line-function at the beginning of each line (re-indent) - ;; - ;; each maybe with alternative indentation suggestions. - ;; - ;; Expectations could use lines of symbols such as | and . or digits to - ;; indicate where the indentation(s) go. - ;; - ;; Test 1 involves a lot of buffer refreshing and will be very slow. - -(ert-deftest haskell-tng-newline-indent-file-tests () - (should (have-expected-newline-indent-insert (testdata "src/indentation.hs"))) - - ;; (should (have-expected-newline-indent-insert (testdata "src/layout.hs"))) - ;; (should (have-expected-newline-indent-insert (testdata "src/medley.hs"))) +;; Three indentation regression tests are possible: +;; +;; 1. newline-and-indent with the rest of the file deleted (append) +;; 2. newline-and-indent with the rest of the file intact (insert) +;; 3. indent-line-function at the beginning of each line (re-indent) +;; +;; each maybe with alternative indentation suggestions. +;; +;; Expectations could use lines of symbols such as | and . or digits to +;; indicate where the indentation(s) go. +;; +;; Test 1 involves a lot of buffer refreshing and will be very slow. + +(ert-deftest haskell-tng-append-indent-file-tests () + ;; this is a very slow test + + ;; (require 'profiler) + ;; (profiler-start 'cpu) + + (should (have-expected-append-indent (testdata "src/indentation.hs"))) + + ;; (profiler-report) + ;; (profiler-report-write-profile "indentation.profile") + ;; (profiler-stop) + + ;; To interactively inspect + ;; (profiler-find-profile "../indentation.profile") + ) + + +(ert-deftest haskell-tng-indent-file-tests () + (should (have-expected-insert-indent (testdata "src/indentation.hs"))) + + ;; (should (have-expected-insert-indent (testdata "src/layout.hs"))) + ;; (should (have-expected-insert-indent (testdata "src/medley.hs"))) ) (ert-deftest haskell-tng-reindent-file-tests () - (should (have-expected-reindent-insert (testdata "src/indentation.hs"))) + (should (have-expected-reindent (testdata "src/indentation.hs"))) - ;; (should (have-expected-reindent-insert (testdata "src/layout.hs"))) - ;; (should (have-expected-reindent-insert (testdata "src/medley.hs"))) + ;; (should (have-expected-reindent (testdata "src/layout.hs"))) + ;; (should (have-expected-reindent (testdata "src/medley.hs"))) ) (defun current-line-string () @@ -44,13 +61,22 @@ (line-beginning-position) (- (line-beginning-position 2) 1))) -(defun haskell-tng-indent-test:indent-insert (return-mode) - ;; FIXME the slow append test - (let (indents) +(defun haskell-tng-indent-test:work (mode) + "MODE can be 'insert, 'reindent, or 'append. + +'append is VERY slow." + ;; each line could be done in parallel, if emacs allowed such a thing... + (let (indents kill) (while (not (eobp)) ;; the command loop is necessary for this/last-command (cl-flet ((RET () (end-of-line) + (pcase mode + ('append + (setq kill ;; kill-region/yank is noisy + (buffer-substring-no-properties + (point) (point-max))) + (delete-region (point) (point-max)))) (ert-simulate-command '(newline-and-indent)) (current-column)) (TAB () @@ -59,28 +85,40 @@ (let ((orig (current-indentation)) (line (current-line-string)) - (prime (if return-mode (RET) (TAB))) + (prime (pcase mode + ((or 'insert 'append) (RET)) + ('reindent (TAB)))) alts) (while (and (TAB) (not (eq (current-column) prime)) (not (member (current-column) alts))) (push (current-column) alts)) - (push `(, return-mode ,line . (,prime . ,(reverse alts))) indents) + (push `(,(pcase mode ((or 'insert 'append) t)) + ,line . (,prime . ,(reverse alts))) + indents) ;; unfortunately killing resets this-command so we can't test double ;; newline insertions, which could accidentally trigger alts only. - (if return-mode - (kill-whole-line) - (indent-line-to orig) - (ert-simulate-command '(forward-line)))))) + (pcase mode + ('insert (kill-whole-line)) + ('reindent + (indent-line-to orig) + (ert-simulate-command '(forward-line))) + ('append + (forward-line -1) + (end-of-line) + (save-excursion + (insert kill) + (delete-region (point) (point-max))) + (ert-simulate-command '(forward-line))))))) (reverse indents))) (defun haskell-tng-indent-test:indents-to-string (indents) "INDENTS is a list of INDENT. -INDENT is a non-empty list of (RETURN-MODE . (LINE . (INDENT . -ALTS))) where RETURN-MODE is t for newline insertions (i.e. LINE -is a string of the previous line) and nil for reindent (i.e. LINE -is a string of the current line). +INDENT is a non-empty list of (RET . (LINE . (INDENT . ALTS))) +where RET is t for newline insertions (i.e. LINE is a string of +the previous line) and nil for reindent (i.e. LINE is a string of +the current line). INDENT is the integer suggested next line indentation column and ALTS is a list of integer alternative indentations." @@ -88,7 +126,7 @@ ALTS is a list of integer alternative indentations." (-map #'haskell-tng-indent-test:indent-to-string indents)))) (defun haskell-tng-indent-test:indent-to-string (indent) - (let* ((return-mode (car indent)) + (let* ((ret (car indent)) (line (cadr indent)) (prime (caddr indent)) (alts (cdddr indent)) @@ -106,26 +144,35 @@ ALTS is a list of integer alternative indentations." (t " ")) repr)) (let ((indents (s-join "" (reverse repr)))) - (if return-mode + (if ret (list line indents) (list indents line))))) -(defun have-expected-newline-indent-insert (file) +(defun have-expected-insert-indent (file) (haskell-tng-testutils:assert-file-contents file #'haskell-tng-mode (lambda () (haskell-tng-indent-test:indents-to-string - (haskell-tng-indent-test:indent-insert t))) + (haskell-tng-indent-test:work 'insert))) "insert.indent")) -(defun have-expected-reindent-insert (file) +(defun have-expected-reindent (file) (haskell-tng-testutils:assert-file-contents file #'haskell-tng-mode (lambda () (haskell-tng-indent-test:indents-to-string - (haskell-tng-indent-test:indent-insert nil))) + (haskell-tng-indent-test:work 'reindent))) "reindent")) +(defun have-expected-append-indent (file) + (haskell-tng-testutils:assert-file-contents + file + #'haskell-tng-mode + (lambda () + (haskell-tng-indent-test:indents-to-string + (haskell-tng-indent-test:work 'append))) + "append.indent")) + ;;; haskell-tng-indent-test.el ends here diff --git a/test/src/indentation.hs.append.indent b/test/src/indentation.hs.append.indent new file mode 100644 index 0000000..b630885 --- /dev/null +++ b/test/src/indentation.hs.append.indent @@ -0,0 +1,102 @@ +-- | Idealised indentation scenarios. +v +-- +v +-- Bugs and unexpected behaviour in (re-)indentation may be documented here. +v +-- +v +-- Lines marked "manual correction" indicate where we expect the user to +v +-- re-indent because it goes against our prediction. In some of these cases, +v +-- we could improve the guess with semantic information (e.g. if we know that +v +-- the RHS of a bind is only partially applied, then we probably mean to +v +-- continue that line instead of start a new one). +v +module Indentation where +v + +v +import Foo.Bar +v +import Foo.Baz hiding ( gaz, +1 v + baz +1 v + ) +v 1 2 + +v 1 2 +basic_do = do +v + foo <- blah blah blah +v 1 + bar <- blah blah +v 1 + blah -- manual correction +v 2 1 + blah -- manual correction +v 2 1 + sideeffect +v 1 2 + sideeffect' blah +v 1 2 + let baz = blah blah +v 2 1 3 + blah -- manual correction +v 2 3 4 1 + gaz = blah +v 2 1 3 4 + haz = +v 2 1 3 4 + blah +v 2 3 14 5 + pure faz -- manual correction +v 1 2 34 5 + +v 1 2 34 5 +nested_do = -- manual correction +v + do foo <- blah +v 1 + do bar <- blah -- same level as foo +v 2 1 + baz -- same level as bar +v 2 1 + +v 1 2 +nested_where a b = foo a b +v + where -- TODO 2 +v + foo = bar baz -- indented +v 1 + baz = blah blah -- same level as foo +v 1 + where -- manual correction +v 1 + gaz a = blah -- indented +v 2 1 + faz = blah -- same level as gaz +v 2 1 + +v 1 2 +-- TODO case statements +v 1 2 +-- TODO let / in +v 1 2 + +v 1 2 +-- TODO coproduct definitions, the | should align with = +v 1 2 + +v 1 2 +-- TODO lists, records, tuples +v 1 2 + +v 1 2 +-- TODO long type signatures vs definitions +v 1 2 \ No newline at end of file