branch: elpa/haskell-tng-mode commit 7a8f7140af71c0e201bf6ee7e02083c8023448c2 Author: Tseen She <ts33n....@gmail.com> Commit: Tseen She <ts33n....@gmail.com>
indent back to the same level of `do' --- haskell-tng-smie.el | 43 +++++++++++------ test/src/indentation.hs | 34 ++++++++------ test/src/indentation.hs.insert.indent | 88 ++++++++++++++++++++--------------- test/src/indentation.hs.layout | 34 ++++++++------ test/src/indentation.hs.lexer | 6 +++ test/src/indentation.hs.reindent | 48 ++++++++++++------- test/src/indentation.hs.sexps | 34 ++++++++------ 7 files changed, 174 insertions(+), 113 deletions(-) diff --git a/haskell-tng-smie.el b/haskell-tng-smie.el index 9fac90d..8caab9f 100644 --- a/haskell-tng-smie.el +++ b/haskell-tng-smie.el @@ -20,8 +20,7 @@ ;; on the side of "staying at the same level" (not escaping or closing a ;; previous line) when we can. ;; -;; Users may consult the SMIE manual to customise their indentation rules: -;; https://www.gnu.org/software/emacs/manual/html_mono/elisp.html#SMIE +;; All the good ideas are from Stefan Monnier, all the bad ones are mine. ;; ;;; Code: @@ -34,6 +33,9 @@ ;; https://www.haskell.org/onlinereport/haskell2010/haskellch3.html ;; https://gitlab.haskell.org/ghc/ghc/blob/master/compiler/parser/Parser.y ;; +;; See also the documentation for `smie-bnf->prec2' which tends to be more up to +;; date. +;; ;; Many of these grammar rules cannot be expressed in SMIE because Haskell uses ;; whitespace separators a lot, whereas the BNF must use non-terminals. ;; @@ -115,12 +117,15 @@ information, to aid in the creation of new rules." ;; https://www.gnu.org/software/emacs/manual/html_mono/elisp.html#SMIE-Indentation ;; +;; See also the documentation for `smie-rules-function', which tends to be more +;; up to date. +;; ;; The concept of "virtual indentation" can be confusing. This function is ;; called multiple times for a single indentation command. `:before' does not -;; always mean that we are indenting the next token, but could be a request for -;; the virtual indentation of the previous token. For example, consider a `do' -;; block, we will get an `:after' and a `:before' on the `do' which may be at -;; column 20 but virtually at column 0. +;; mean that we are indenting the next token, but is a request for the virtual +;; indentation of that token. For example, consider a `do' block, we may get an +;; `:after' and a `:before' for `do' which may be at column 20 but virtually at +;; column 0. (defun haskell-tng-smie:rules (method arg) ;; see docs for `smie-rules-function' (when haskell-tng-smie:debug @@ -133,22 +138,28 @@ information, to aid in the creation of new rules." (:elem (pcase arg - ((or 'empty-line-token 'args) 0) - ('basic 0) + ((or 'args 'basic) 0) + + ;; TODO consult a local table, populated by an external tool, containing + ;; the parameter requirements for function calls. For simple cases, we + ;; should be able to infer if the user wants to terminate ; or continue + ;; "" the current line. + ;; + ;; TODO if there is already an empty lines before here, perhaps best to + ;; close out the indentation with a }. + ('empty-line-token ";") )) - ;; It looks like all patterns of the form + ;; Patterns of the form ;; ;; {TOKEN TOKEN HEAD ; A ; B ; ...} ;; - ;; are showing up as `:list-intro "HEAD"` in positions A and B. + ;; get called with `:list-intro "HEAD"` when indenting positions A and B. (:list-intro - ;; TODO could consult a local table that is populated by an external tool - ;; containing the parameter requirements for function calls to let us know - ;; if it's a single statement or many. (pcase arg - ;; TODO this is a hack to workaround broken list detection - ((or "CONID" "VARID" "}" "<-" "=") t) + ;; TODO work out why we need these list-intro rules. Re-indentation of + ;; continued lines in WLDOs don't behave as expected without. + ((or "<-" "=") t) )) (:after @@ -226,6 +237,8 @@ current line." (looking-at (rx (* space) (| "where" "let" "do") word-end))) (push (current-indentation) relevant)) + ;; TODO when there is a <- add its close +4 (possibly just for the + ;; immediately previous line). (forward-line)) (goto-char start) (while (< (point) bound) diff --git a/test/src/indentation.hs b/test/src/indentation.hs index 8c98baa..7dd8dab 100644 --- a/test/src/indentation.hs +++ b/test/src/indentation.hs @@ -1,6 +1,12 @@ -- | Idealised indentation scenarios. -- -- Bugs and unexpected behaviour in (re-)indentation may be documented here. +-- +-- Lines marked "manual correction" indicate where we expect the user to +-- re-indent because it goes against our prediction. In some of these cases, +-- we could improve the guess with semantic information (e.g. if we know that +-- the RHS of a bind is only partially applied, then we probably mean to +-- continue that line instead of start a new one). module Indentation where import Foo.Bar @@ -10,30 +16,30 @@ import Foo.Baz hiding ( gaz, basic_do = do foo <- blah blah blah - bar <- blah blah -- TODO same level as foo - blah -- TODO manual correction - blah -- continue the blah - sideeffect -- manual correction + bar <- blah blah + blah -- manual correction + blah -- manual correction + sideeffect sideeffect' blah let baz = blah blah - blah -- TODO manual correction - gaz = blah -- TODO same level as baz - haz = -- TODO same level as gaz + blah -- manual correction + gaz = blah + haz = blah pure faz -- manual correction -nested_do = +nested_do = -- manual correction do foo <- blah - do bar <- blah -- TODO same level as foo - baz -- TODO same level as bar + do bar <- blah -- same level as foo + baz -- same level as bar nested_where a b = foo a b where -- TODO 2 - foo = bar baz -- TODO indented - baz = blah blah -- TODO same level as foo + foo = bar baz -- indented + baz = blah blah -- same level as foo where -- manual correction - gaz a = blah -- TODO indented - faz = blah -- TODO same level as gaz + gaz a = blah -- indented + faz = blah -- same level as gaz -- TODO case statements -- TODO let / in diff --git a/test/src/indentation.hs.insert.indent b/test/src/indentation.hs.insert.indent index 6766ce4..442c096 100644 --- a/test/src/indentation.hs.insert.indent +++ b/test/src/indentation.hs.insert.indent @@ -4,6 +4,18 @@ 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 @@ -21,70 +33,70 @@ v 1 2 basic_do = do 1 v foo <- blah blah blah -2 1 v - bar <- blah blah -- TODO same level as foo -2 1 v - blah -- TODO manual correction -1 2 v - blah -- continue the blah -2 1 v - sideeffect -- manual correction +1 v + bar <- blah blah +2 v 1 + blah -- manual correction +2 v 1 + blah -- manual correction +2 v 1 + sideeffect 1 v 2 sideeffect' blah 2 v 1 3 let baz = blah blah -3 2 1 4 v - blah -- TODO manual correction -2 3 1 4 v - gaz = blah -- TODO same level as baz -2 3 1 4 v - haz = -- TODO same level as gaz +3 1 v 4 2 + blah -- manual correction +2 3 v 4 1 + gaz = blah +1 2 v 3 4 + haz = 2 3 1 v4 5 blah -2 1 3 v4 5 +3 2 v 14 5 pure faz -- manual correction 1 v 2 34 5 1 v 2 34 5 -nested_do = +nested_do = -- manual correction 1 v 2 do foo <- blah -3 1 2 v - do bar <- blah -- TODO same level as foo -3 2 1 v - baz -- TODO same level as bar +2 v 1 + do bar <- blah -- same level as foo +2 1 v + baz -- same level as bar 1 2 v 1 2 v nested_where a b = foo a b -1 v +v where -- TODO 2 1 v - foo = bar baz -- TODO indented -2 1 v - baz = blah blah -- TODO same level as foo -2 1 v + foo = bar baz -- indented +1 v + baz = blah blah -- same level as foo +1 v where -- manual correction 1 2 v - gaz a = blah -- TODO indented -2 3 1 v - faz = blah -- TODO same level as gaz -2 3 1 v + gaz a = blah -- indented +1 2 v + faz = blah -- same level as gaz +1 2 v -1 2 3 v +1 2 v -- TODO case statements -1 2 3 v +1 2 v -- TODO let / in -1 2 3 v +1 2 v -1 2 3 v +1 2 v -- TODO coproduct definitions, the | should align with = -1 2 3 v +1 2 v -1 2 3 v +1 2 v -- TODO lists, records, tuples -1 2 3 v +1 2 v -1 2 3 v +1 2 v -- TODO long type signatures vs definitions -1 2 3 v \ No newline at end of file +1 2 v \ No newline at end of file diff --git a/test/src/indentation.hs.layout b/test/src/indentation.hs.layout index 8ae3b00..2c5f03d 100644 --- a/test/src/indentation.hs.layout +++ b/test/src/indentation.hs.layout @@ -1,6 +1,12 @@ -- | Idealised indentation scenarios. -- -- Bugs and unexpected behaviour in (re-)indentation may be documented here. +-- +-- Lines marked "manual correction" indicate where we expect the user to +-- re-indent because it goes against our prediction. In some of these cases, +-- we could improve the guess with semantic information (e.g. if we know that +-- the RHS of a bind is only partially applied, then we probably mean to +-- continue that line instead of start a new one). module Indentation where {import Foo.Bar @@ -10,30 +16,30 @@ module Indentation where ;basic_do = do {foo <- blah blah blah - ;bar <- blah blah -- TODO same level as foo - blah -- TODO manual correction - blah -- continue the blah - ;sideeffect -- manual correction + ;bar <- blah blah + blah -- manual correction + blah -- manual correction + ;sideeffect ;sideeffect' blah ;let {baz = blah blah - blah -- TODO manual correction - ;gaz = blah -- TODO same level as baz - ;haz = -- TODO same level as gaz + blah -- manual correction + ;gaz = blah + ;haz = blah };pure faz -- manual correction -};nested_do = +};nested_do = -- manual correction do {foo <- blah - ;do {bar <- blah -- TODO same level as foo - ;baz -- TODO same level as bar + ;do {bar <- blah -- same level as foo + ;baz -- same level as bar }};nested_where a b = foo a b where -- TODO 2 - {foo = bar baz -- TODO indented - ;baz = blah blah -- TODO same level as foo + {foo = bar baz -- indented + ;baz = blah blah -- same level as foo where -- manual correction - {gaz a = blah -- TODO indented - ;faz = blah -- TODO same level as gaz + {gaz a = blah -- indented + ;faz = blah -- same level as gaz -- TODO case statements -- TODO let / in diff --git a/test/src/indentation.hs.lexer b/test/src/indentation.hs.lexer index 510db03..df70005 100644 --- a/test/src/indentation.hs.lexer +++ b/test/src/indentation.hs.lexer @@ -1,6 +1,12 @@ + + + + + + module CONID where { import CONID diff --git a/test/src/indentation.hs.reindent b/test/src/indentation.hs.reindent index ee5284f..f9fa74f 100644 --- a/test/src/indentation.hs.reindent +++ b/test/src/indentation.hs.reindent @@ -5,6 +5,18 @@ 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 @@ -23,23 +35,23 @@ basic_do = do 1 v foo <- blah blah blah v 1 2 - bar <- blah blah -- TODO same level as foo + bar <- blah blah 2 1 v - blah -- TODO manual correction + blah -- manual correction 2 1 v - blah -- continue the blah + blah -- manual correction v 2 1 - sideeffect -- manual correction + sideeffect v 1 2 3 sideeffect' blah v 1 3 2 let baz = blah blah 3 2 1 4 v - blah -- TODO manual correction + blah -- manual correction v 3 2 4 1 - gaz = blah -- TODO same level as baz + gaz = blah v 3 1 24 5 - haz = -- TODO same level as gaz + haz = 3 2 1 v4 5 blah 2 v 3 14 5 @@ -47,13 +59,13 @@ v 3 1 24 5 1 v 2 34 5 v 2 13 45 6 -nested_do = +nested_do = -- manual correction v 1 do foo <- blah v 1 2 - do bar <- blah -- TODO same level as foo + do bar <- blah -- same level as foo v 2 1 - baz -- TODO same level as bar + baz -- same level as bar 1 2 v v 1 2 @@ -61,30 +73,30 @@ nested_where a b = foo a b 1 v 2 where -- TODO 2 1 v - foo = bar baz -- TODO indented + foo = bar baz -- indented v 1 - baz = blah blah -- TODO same level as foo + baz = blah blah -- same level as foo 2 v 1 where -- manual correction 1 2 v - gaz a = blah -- TODO indented + gaz a = blah -- indented v 2 1 - faz = blah -- TODO same level as gaz -2 3 1 v + faz = blah -- same level as gaz +1 2 v v 1 2 -- TODO case statements v 1 2 -- TODO let / in -1 2 3 v +1 2 v v 1 2 -- TODO coproduct definitions, the | should align with = -1 2 3 v +1 2 v v 1 2 -- TODO lists, records, tuples -1 2 3 v +1 2 v v 1 2 -- TODO long type signatures vs definitions \ No newline at end of file diff --git a/test/src/indentation.hs.sexps b/test/src/indentation.hs.sexps index 8482b82..0611710 100644 --- a/test/src/indentation.hs.sexps +++ b/test/src/indentation.hs.sexps @@ -1,6 +1,12 @@ -- | Idealised indentation scenarios. -- -- Bugs and unexpected behaviour in re-indentation may be documented here. +-- +-- Lines marked "manual correction" indicate where we expect the user to +-- re-indent because it goes against our prediction. In some of these cases, +-- we could improve the guess with semantic information e.g. if we know that +-- the RHS of a bind is only partially applied, then we probably mean to +-- continue that line instead of start a new one. ((module (Indentation) (where) (((import) ((Foo).)(Bar)) @@ -10,30 +16,30 @@ ((basic_do) = (do ((foo) <- (blah) (blah) (blah) - ((bar) <- (blah) (blah) -- TODO same level as foo - (blah) -- TODO manual correction - (blah)) -- continue the blah - (sideeffect) -- manual correction + ((bar) <- (blah) (blah) + (blah) -- manual correction + (blah)) -- manual correction + (sideeffect) ((sideeffect') (blah)) (let ((baz) = (blah) (blah) - (blah) -- TODO manual correction - ((gaz) = (blah)) -- TODO same level as baz - ((haz) = -- TODO same level as gaz + (blah) -- manual correction + ((gaz) = (blah)) + ((haz) = (blah)) )(pure) (faz)) -- manual correction -)(nested_do) = +)(nested_do) = -- manual correction (do (((foo) <- (blah) - (do ((bar) <- (blah) -- TODO same level as foo - (baz) -- TODO same level as bar + (do ((bar) <- (blah) -- same level as foo + (baz) -- same level as bar )))(nested_where) (a) (b) = (foo) (a) (b) (where) -- TODO 2 - (((foo) = (bar) (baz) -- TODO indented - ((baz) = (blah) (blah)) -- TODO same level as foo + (((foo) = (bar) (baz) -- indented + ((baz) = (blah) (blah)) -- same level as foo (where) -- manual correction - ((gaz) (a) = (blah) -- TODO indented - ((faz) = (blah)) -- TODO same level as gaz + ((gaz) (a) = (blah) -- indented + ((faz) = (blah)) -- same level as gaz -- TODO case statements -- TODO let / in