diff --git a/haskell-indentation.el b/haskell-indentation.el index d596149ba..2aeca81f9 100644 --- a/haskell-indentation.el +++ b/haskell-indentation.el @@ -355,7 +355,6 @@ fixes up only indentation." (defvar starter-indent) ;; column at a keyword (defvar current-indent) ;; the most right indentation (defvar layout-indent) ;; the column of the layout list -(defvar parse-line-number) ;; the number of lines parsed (defvar possible-indentations) ;; the return value of the indentations (defvar indentation-point) ;; where to stop parsing (defvar implicit-layout-active) ;; is "off-side" rule active? @@ -409,7 +408,6 @@ fixes up only indentation." (skip-syntax-forward "-") (let ((indentation-point (point)) (layout-indent 0) - (parse-line-number 0) (current-indent haskell-indentation-layout-offset) (starter-indent haskell-indentation-layout-offset) (left-indent haskell-indentation-layout-offset) @@ -616,8 +614,9 @@ After a lambda (backslash) there are two possible cases: ((eq current-token 'end-tokens) (when (member following-token '(value operator no-following-token - "->" "(" "[" "{" "::")) - (haskell-indentation-add-indentation current-indent)) + "(" "[" "{" "::")) + (haskell-indentation-add-indentation current-indent) + (haskell-indentation-add-indentation left-indent)) (throw 'return nil)) (t (let ((parser (assoc current-token haskell-indentation-type-list))) (if (not parser) @@ -884,33 +883,38 @@ parser. If parsing ends here, set indentation to left-indent." (defun haskell-indentation-expression () "Parse an expression until an unknown token is encountered." (catch 'return - (while t - (cond - ((memq current-token '(value operator)) - (haskell-indentation-read-next-token)) - ((eq current-token 'end-tokens) - (cond ((string= following-token "where") - (haskell-indentation-add-where-pre-indent)) ; before a where - ((haskell-indentation-expression-token-p following-token) - (haskell-indentation-add-indentation - current-indent))) ; a normal expression - (throw 'return nil)) - (t (let ((parser (assoc current-token - haskell-indentation-expression-list))) - (when (null parser) - (throw 'return nil)) ; not expression token, so exit - (funcall (cdr parser)) ; run parser - (when (and (eq current-token 'end-tokens) - (string= (car parser) "let") - (= haskell-indentation-layout-offset current-indent) - (haskell-indentation-expression-token-p following-token)) - ;; inside a layout, after a let construct - ;; for example: "do let a = 20" - (haskell-indentation-add-layout-indent) - (throw 'parse-end nil)) - ;; after an 'open' expression such as 'if', exit - (unless (member (car parser) '("(" "[" "{" "case")) - (throw 'return nil)))))))) + (let ((current-indent (current-column))) + (while t + (cond + ((memq current-token '(value operator)) + (haskell-indentation-read-next-token)) + ((eq current-token 'end-tokens) + (cond ((string= following-token "where") + (haskell-indentation-add-where-pre-indent)) ; before a where + ((haskell-indentation-expression-token-p following-token) + ;; a normal expression can be either continued or have + ;; left indent + (haskell-indentation-add-indentation + current-indent) + (haskell-indentation-add-indentation + left-indent))) + (throw 'return nil)) + (t (let ((parser (assoc current-token + haskell-indentation-expression-list))) + (when (null parser) + (throw 'return nil)) ; not expression token, so exit + (funcall (cdr parser)) ; run parser + (when (and (eq current-token 'end-tokens) + (string= (car parser) "let") + (= haskell-indentation-layout-offset current-indent) + (haskell-indentation-expression-token-p following-token)) + ;; inside a layout, after a let construct + ;; for example: "do let a = 20" + (haskell-indentation-add-layout-indent) + (throw 'parse-end nil)) + ;; after an 'open' expression such as 'if', exit + (unless (member (car parser) '("(" "[" "{" "case")) + (throw 'return nil))))))))) (defun haskell-indentation-separated (parser separator &optional stmt-separator) "Evaluate PARSER separated by SEPARATOR and STMT-SEPARATOR. @@ -918,7 +922,7 @@ If STMT-SEPARATOR is not NIL, it will be used to set a new starter-indent. For example: -[ i | i <- [1..10] + [ i | i <- [1..10] ," (catch 'return (unless (listp separator) @@ -935,13 +939,21 @@ For example: (haskell-indentation-at-separator)) ((eq current-token 'end-tokens) - (cond ((or (member following-token separator) - (member following-token stmt-separator)) - ;; Set an indentation before a separator, for example: - ;; [ 1 or [ 1 | a - ;; , 2 , 20 - (haskell-indentation-add-indentation starter-indent) - (throw 'parse-end nil))) + (when (or (member following-token separator) + (member following-token stmt-separator)) + ;; Set an indentation before a separator, for example: + ;; [ 1 or [ 1 | a + ;; , 2 , 20 + (haskell-indentation-add-indentation starter-indent) + (when (< left-indent starter-indent) + (haskell-indentation-add-indentation left-indent)) + (throw 'parse-end nil)) + (when (equal following-token 'no-following-token) + ;; Set an indentation before a separator, for example: + ;; [ 1 or [ 1 | a + ;; , 2 , 20 + (haskell-indentation-add-indentation starter-indent) + (haskell-indentation-add-indentation left-indent)) (throw 'return nil)) (t (throw 'return nil)))))) @@ -960,6 +972,7 @@ l = [ 1 (haskell-indentation-read-next-token) (cond ((eq current-token 'end-tokens) (haskell-indentation-add-indentation current-indent) + (haskell-indentation-add-indentation left-indent) (throw 'return nil)) (separator-column ; on the beginning of the line (setq current-indent (current-column)) @@ -1024,16 +1037,20 @@ layout starts." (defun haskell-indentation-phrase-rest (phrase1) "" ; FIXME (while phrase1 - (let ((starter-line parse-line-number) - (phrase phrase1)) + (let ((phrase phrase1)) (setq phrase1 nil) - (let ((current-indent (current-column))) + (let ((current-indent (current-column)) + (left-indent left-indent) + (layout-indent layout-indent)) (funcall (car phrase))) (cond ((eq current-token 'end-tokens) (cond ((null (cdr phrase))) ;; fallthrough ((equal following-token (cadr phrase)) (haskell-indentation-add-indentation starter-indent) + (unless (member following-token '("," ";")) + ;; we want to keep comma and semicolon aligned always + (haskell-indentation-add-indentation left-indent)) (throw 'parse-end nil)) ((string= (cadr phrase) "in") (when (= left-indent layout-indent) @@ -1082,11 +1099,13 @@ layout starts." (+ left-indent haskell-indentation-left-offset))) (defun haskell-indentation-push-indentation (indent) - "" ; FIXME - (when (or (null possible-indentations) - (< indent (car possible-indentations))) + "Add INDENT to list of possible indentations. + +Add INDENT to `possible-indentations' if it is not there +yet. Keep the list in ascending order." + (unless (member indent possible-indentations) (setq possible-indentations - (cons indent possible-indentations)))) + (sort (cons indent possible-indentations) #'<)))) (defun haskell-indentation-read-next-token () "Go to the next token and set current-token to the next token. @@ -1131,14 +1150,14 @@ line." (if (>= (point) indentation-point) (progn (setq following-token - (if (= (point) indentation-point) + (if (and (not (eobp)) + (= (point) indentation-point)) (haskell-indentation-peek-token) 'no-following-token)) (setq current-token 'end-tokens)) (when (= (current-column) (haskell-indentation-current-indentation)) ;; on a new line - (setq current-indent (current-column)) - (setq parse-line-number (+ parse-line-number 1))) + (setq current-indent (current-column))) (cond ((and implicit-layout-active (> layout-indent (current-column))) (setq current-token 'layout-end)) diff --git a/tests/haskell-indentation-tests.el b/tests/haskell-indentation-tests.el index c830d88ea..21382f7b5 100644 --- a/tests/haskell-indentation-tests.el +++ b/tests/haskell-indentation-tests.el @@ -137,7 +137,7 @@ macro quotes them for you." function = Record { field = 123 }" (1 0) - (2 2)) + (2 2 11)) (hindent-test "2 Handle underscore in identifiers"" function = do @@ -145,7 +145,7 @@ function = do z" (1 0) (2 2) - (3 0 2 4)) + (3 0 2 4 10)) (hindent-test "2u Handle underscore in identifiers"" function = do @@ -153,7 +153,7 @@ function = do z" (1 0) (2 2) - (3 0 2 4)) + (3 0 2 4 9)) (hindent-test "2a Handle apostrophe in identifiers"" function = do @@ -161,7 +161,7 @@ function = do z" (1 0) (2 2) - (3 0 2 4)) + (3 0 2 4 12)) (hindent-test "2au Handle apostrophe in identifiers"" function = do @@ -169,32 +169,32 @@ function = do z" (1 0) (2 2) - (3 0 2 4)) + (3 0 2 4 11)) (hindent-test "3 Import statememnt symbol list 1"" import Control.Concurrent ( forkIO, killThread )" (1 0) - (2 0 7) + (2 0 2 7) (3 9) - (4 0 7)) + (4 0 2 7)) (hindent-test "4 Import statememnt symbol list 2"" import Control.Concurrent ( forkIO , killThread )" (1 0) - (2 0 7) + (2 0 2 7) (3 7) - (4 0 7)) + (4 0 2 7)) (hindent-test "5 List comprehension"" fun = [ x | y , z ]" (1 0) - (2 10) - (3 0 2)) + (2 8 10) + (3 0 2 6)) (hindent-test "5a* List comprehension"" fun = [ x | y, @@ -244,16 +244,16 @@ fun = [ f | x ← xs (4 10) (5 0)) -(hindent-test "6b \"let\" in do"" +(hindent-test "6bx \"let\" in do"" fact n = do let g = 7 z <- let x = 5 in return (x + 4)" (1 0) (2 2) - (3 2 6 8) - (4 7) - (5 2 10)) + (3 2 6 8 10) + (4 4 7) + (5 0 2 4 10)) (hindent-test "7a \"data\" after \"data\""" @@ -267,33 +267,33 @@ import ABC import DEF" (1 0) (2 0) - (3 0 7)) + (3 0 2 7)) -(hindent-test "7b* declaration after declaration" " +(hindent-test "7b declaration after declaration" " fun1 = undefined fun2 = undefined" (1 0) - (2 0)) + (2 0 2 7)) -(hindent-test "8* Guards in function definition"" +(hindent-test "8 Guards in function definition"" resolve (amount, max) number | number > max = (1, number) | number == max = (amount + 1, number)" (1 0) (2 2) - (3 0 2) - (4 0 2)) + (3 2) + (4 0 2 4 20)) (hindent-test "9* Operator last on line"" fun = x ++" (1 0) (2 6)) -(hindent-test "10 Operator first on line"" +(hindent-test "10* Operator first on line"" fun = x ++ z" (1 0) - (2 0 2)) + (2 2 6)) (hindent-test "11 Guards with commas"" clunky env var1 var2 @@ -302,7 +302,7 @@ clunky env var1 var2 (1 0) (2 2) (3 2) - (4 0 4)) + (4 0 2 4 17)) (hindent-test "11u Guards with commas"" clunky env var1 var2 @@ -311,14 +311,14 @@ clunky env var1 var2 (1 0) (2 2) (3 2) - (4 0 4)) + (4 0 2 4 16)) (hindent-test "12 \"do\" as expression"" fun = do { putStrLn \"X\"; }" (1 0) (2 9 11) - (3 0)) + (3 0 2)) (hindent-test "13a Deriving on new line"" data X = X | Y @@ -387,19 +387,19 @@ fun = \\x →" (1 0) (2 2 8)) -(hindent-test "17a* A type for a function"" +(hindent-test "17a A type for a function"" fun :: Int -> Int" (1 0) - (2 4) - (3 0 4)) + (2 2 4) + (3 0 2 4)) -(hindent-test "17au* A type for a function"" +(hindent-test "17au A type for a function"" fun :: Int → Int" (1 0) - (2 4) - (3 0 4)) + (2 2 4) + (3 0 2 4)) (hindent-test "17b* A type for a function with context"" fun :: Monad m @@ -459,9 +459,9 @@ x = if flag then 1 else 0" (1 0) - (2 4) - (3 4) - (4 0 9)) + (2 2 4) + (3 2 4) + (4 0 2 9)) (hindent-test "18c* \"do\" and \"if-then-else\" indentation: \"then\""" x = do @@ -508,51 +508,51 @@ x = do (6 4) (7 0 2 4)) -(hindent-test "19a* \"let\" and \"in\""" +(hindent-test "19a \"let\" and \"in\""" x = let y" (1 0) (2 2) - (3 2)) + (3 0 2 4)) (hindent-test "19b \"let\" and \"in\"" " x = let y in z" (1 0) - (2 4) + (2 2 4) (3 2 6)) -(hindent-test "19c* \"let\" in a \"do\""" +(hindent-test "19c \"let\" in a \"do\""" x = do thing let z = 5" (1 0) (2 2) - (3 2) + (3 0 2 4) (4 4)) -(hindent-test "20a* \"instance\" declaration"" +(hindent-test "20a \"instance\" declaration"" instance C a where c = undefined" (1 0) (2 2) - (3 0 2)) + (3 0 2 4 6)) -(hindent-test "20b* \"instance\" declaration"" +(hindent-test "20b \"instance\" declaration"" instance (Monad m) => C m a where c = undefined" (1 0) (2 2) - (3 0 2)) + (3 0 2 4 6)) -(hindent-test "20bu* \"instance\" declaration"" +(hindent-test "20bu \"instance\" declaration"" instance (Monad m) ⇒ C m a where c = undefined" (1 0) (2 2) - (3 0 2)) + (3 0 2 4 6)) (hindent-test "21a fix \"let\" statement in \"do\" block"" main :: IO () @@ -561,11 +561,11 @@ let foo = Foo { bar = 0 , baz = 0" (1 0) - (2 0 8) + (2 0 2 8) (3 2) (4 6) (5 6) - (6 8)) + (6 6 8 14)) (hindent-test "21b fix named fields in \"data\" declaration"" data Foo = Foo { @@ -574,16 +574,16 @@ data Foo = Foo { (1 0) (2 2) (3 2) - (4 11)) + (4 2 4 8 11)) -(hindent-test "21c* \"data\" declaration open on next line" " +(hindent-test "21c \"data\" declaration open on next line" " data Foo = Foo { bar :: Int , baz :: Int" (1 0) - (2 2) + (2 2 11) (3 2) - (4 4 11)) + (4 2 4 8 11)) (hindent-test "22 should obey layout only outside parentheses" " func = 1234 @@ -594,14 +594,14 @@ func = 1234 (1 0) (2 2) (3 4) - (4 0 4 11) + (4 0 4 6 11) (5 6)) -(hindent-test "23* should not fail when seeing comments" " +(hindent-test "23 should not fail when seeing comments" " -- important non-empty line {- -}" - ((3 2) 0)) + (3 0)) (hindent-test "24 should parse inline type signatures properly" " foo = do @@ -610,8 +610,8 @@ foo = do return ()" (1 0) (2 2) - (3 0 2 4) - (4 0 2 4)) + (3 0 2 4 17) + (4 0 2 4 17)) (hindent-test "25a* support scoped type declarations" " foo = do @@ -620,7 +620,7 @@ foo = do <- undefined" (1 0) (2 2) - (3 6 9) + (3 4 6 9) ;; here it brakes, it would like to put '<-' on same line with 'bar' ;; the culprit is the 'do' keyword (4 4)) @@ -632,7 +632,7 @@ foo = let = undefined" (1 0) (2 2) - (3 6 9) + (3 4 6) (4 4)) (hindent-test "26 should parse unindented where-clause properly" " @@ -653,13 +653,23 @@ foo = do f = a (a 'A) (a 'A) " - (2 0 2)) + (2 0 2 4)) + +(hindent-test "28g continue expression after value" " +f = a + a" + (3 0 2 7)) + +(hindent-test "28h continue expression after parentheses" " +f = a + (a)" + (3 0 2 7)) (hindent-test "28b character literal (escape sequence)" " f = '\\\\' " - (2 0 2)) + (2 0 2 4)) (hindent-test "28c name starting with a quote" " @@ -672,7 +682,7 @@ function (Operation 'Init) = do test = [randomQQ| This is a quasiquote with the word in |] " - (2 0 2)) + (2 0 2 7)) (hindent-test "29b quasiquote multiple lines" " test = [randomQQ| This is @@ -680,7 +690,7 @@ test = [randomQQ| This is with the word in |] " - (4 0 2)) + (4 0 2 7)) (hindent-test "29c quasiquote with quotes in it and a string outside" " foo = do @@ -699,10 +709,10 @@ foo = [|forever $ do " (2 10)) -(hindent-test "30* parse '[] identifier correctly" " -instance Callable '[] +(hindent-test "30 parse '[] identifier correctly" " +instance Callable '[] where " - (1 2)) + (2 2)) (hindent-test "31* allow type class declaration without methods" " class Foo a where @@ -711,9 +721,16 @@ instance Bar Int (2 0)) (hindent-test "32 allow type operators" " -data (:.) a b = a :. b -" - (2 0 16)) +data (:.) a b = a :. b" + (2 0 2 14 16)) + +(hindent-test "32b next line after data" " +data X = X | Y" + (2 0 2 7 13)) + +(hindent-test "32c* next line after unfinished data" " +data X = X | Y |" + (2 2 9)) (hindent-test "33* parse #else in CPP" " #ifdef FLAG @@ -730,7 +747,7 @@ data T = T { } " - (5 0 9)) + (5 0 2 7 9)) (hindent-test "35 baroque construct which causes parse error" " az = Projection @@ -759,17 +776,17 @@ tokOpenTag = ] " (4 7)) -(hindent-test "37* Indent continuation lines in multiline string literal" " +(hindent-test "37 Indent continuation lines in multiline string literal" " a = \"multiline\\ " - (2 4)) + (2 0 4)) (hindent-test "38 Indent in do block after multiline string literal" " s = do a <- \"multiline\\ \\ line 2\" " - (4 0 2 4)) + (4 0 2 4 7)) (hindent-test "39 do not crash after two multiline literals in do block" " servePost = do @@ -778,13 +795,13 @@ servePost = do b <- queryT \"comma is important: , \\ \\ line 2 \" " - (6 0 2 4)) + (6 0 2 4 7)) (hindent-test "40 parse error in multiline tuple" " a = ( 1 , " (2 4) - (3 2)) + (3 6)) (hindent-test "41 open do inside a list" " x = asum [ withX $ do @@ -813,7 +830,7 @@ x = asum [ mzero function = abc def xyz" - (3 0 7)) + (3 0 2 7)) (hindent-test "46 case expression with paths on their own lines" " fact n = @@ -824,8 +841,8 @@ fact n = (1 0) (2 2) (3 4) - (4 0 2 4 6) - (5 0 2 4 6)) + (4 0 2 4 6 9) + (5 0 2 4 6 9)) (hindent-test "46b case expression with guards" " fact n = case n of @@ -834,10 +851,9 @@ fact n = case n of , True == True -> n * fact (n - 1)" (1 0) (2 2 11) - ;; returns (0 2 2 6), to investigate - (3 0 2 6) + (3 0 2 6 9 16) (4 4) - (5 0 2 6)) + (5 0 2 4 6 9 22)) (hindent-test "47a multiline strings" " fact n = \"\\ @@ -846,7 +862,7 @@ fact n = \"\\ ;; we want to offer both a continuation style and the ;; align to left column style (like in lisp) (2 0 9) - (3 0 2)) + (3 0 2 9)) (hindent-test "47b multiline strings" " fact n = \"\\ @@ -863,9 +879,9 @@ class X a b | a -> b , b -> a where fun :: a -> b" (1 0) - (2 12) + (2 2 12) (3 2) - (4 0 2 9)) + (4 0 2 4 6 9)) (hindent-test "49 data with GADT syntax" " data Term a where @@ -873,8 +889,8 @@ data Term a where Pair :: Term a -> Term b -> Term (a,b)" (1 0) (2 2) - (3 0 2 9) - (4 0 2 10)) + (3 0 2 4 9) + (4 0 2 4 7 10)) (hindent-test "49b data with GADT syntax and a deriving clause" " data G [a] b where @@ -903,7 +919,7 @@ newtype instance T Char = TC Bool" (1 0) (2 0) (3 0) - (4 0 26)) + (4 0 2 24 26)) (hindent-test "52a module simplest case two lines" " module A.B @@ -935,7 +951,7 @@ fun = if | guard1 -> expr1 | guardN -> exprN" (1 0) (2 9) - (3 0 11)) + (3 0 2 9 11 21)) (hindent-test "54 equal after guards on separate line" " foo x @@ -949,14 +965,14 @@ foo x data Foo = Bar | Baz" (1 0) - (2 9)) + (2 2 9)) (hindent-test "55a deriving below aligned data constructors" " data Foo = Bar | Baz deriving (Show)" (1 0) - (2 9) + (2 2 9) (3 0 2 9)) (hindent-test "56 module name on next line" " @@ -982,6 +998,14 @@ module X (2 0) (3 0)) +(hindent-test "60* must continue indentation after a vertical bar" " +data X = X | + Y" + (1 0) + (2 2 9) + (3 0 7 9)) + + (ert-deftest haskell-indentation-ret-indents () (with-temp-switch-to-buffer (haskell-mode)