haskell / haskell-mode

Emacs mode for Haskell
http://haskell.github.io/haskell-mode/
GNU General Public License v3.0
1.33k stars 342 forks source link

Make indent-according-to-mode continue previous line indentation on empty lines #1265

Open gracjan opened 8 years ago

gracjan commented 8 years ago

indent-according-to-mode is called in:

  1. In newline-and-indent.
  2. In electric-indent-mode after \n has been inserted.
  3. In evil-open-above.
  4. In evil-open-below.

Each of these cases is same: it is to semantically continue previous line. So we need:

  1. If previous line ends with operator, continue expression.
  2. If inside parenthesized expression, continue on the level of the expression.
  3. Lacking other context continue same indentation block.

Note that haskell-indentation already has haskell-indentation-newline-and-indent that touches this problem but hard to say how good it is.

Reference: https://github.com/syl20bnr/spacemacs/issues/5010, https://github.com/syl20bnr/spacemacs/issues/621, https://github.com/syl20bnr/spacemacs/issues/3162, https://github.com/haskell/haskell-mode/issues/896

fice-t commented 8 years ago

I thought this was going to be solved upstream in Evil?

The issue there, from what I can remember, is that in haskell-mode haskell-indentation-newline-and-indent uses haskell-indentation-find-indentations on the end of the line before entering the newline. This gives different results compared to the evil-open- case where it does (newline) then (indent-according-to-mode).

So if this was to be fixed here, then I believe changing the behaviour of haskell-indentation-find-indentations such that haskell-indentation-newline-and-indent is unnecessary would work.

gracjan commented 8 years ago

The point is to change the behaviour of haskell-indentation-find-indentations such that haskell-indentation-newline-and-indent is unnecessary would work.

gracjan commented 8 years ago

So newline-and-indent takes previous line indentation and indents at least that amount or more.

PierreR commented 8 years ago

Hi, do you remember if there is an easy workaround for this issue (while waiting for a full resolution ?)

fice-t commented 8 years ago

@PierreR Does the following patch work for you? I haven't touched it since February, but it worked then:

diff --git a/haskell-indentation.el b/haskell-indentation.el
index 03c5348..b174006 100644
--- a/haskell-indentation.el
+++ b/haskell-indentation.el
@@ -253,14 +253,30 @@ indentation points to the right, we switch going to the left."
                    (or (haskell-indentation-find-indentations)
                        '(0))))
            (valid (memq ci inds))
-           (cursor-in-whitespace (< cc ci)))
-
+           (cursor-in-whitespace (< cc ci))
+           ;; certain evil commands need the behaviour seen in
+           ;; `haskell-indentation-newline-and-indent'
+           (evil-special-command (and (bound-and-true-p evil-mode)
+                                      (memq this-command '(evil-open-above
+                                                           evil-open-below
+                                                           evil-replace))))
+           (on-last-indent (eq ci (car (last inds)))))
       (if (and valid cursor-in-whitespace)
           (move-to-column ci)
         (haskell-indentation-reindent-to
-         (haskell-indentation-next-indentation ci inds 'nofail)
+         (funcall
+          (if on-last-indent
+              #'haskell-indentation-previous-indentation
+            #'haskell-indentation-next-indentation)
+          (if evil-special-command
+              (save-excursion
+                (end-of-line 0)
+                (1- (haskell-indentation-current-indentation)))
+            ci)
+          inds
+          'nofail)
          cursor-in-whitespace))
-      (setq haskell-indentation-dyn-last-direction 'right
+      (setq haskell-indentation-dyn-last-direction (if on-last-indent 'left 'right)
             haskell-indentation-dyn-last-indentations inds))))

 (defun haskell-indentation-indent-line-repeat ()
hatashiro commented 8 years ago

The code below works at least for my use cases. I'm quite new at Emacs and Emacs Lisp, so there could be something ridiculous in the code. Hope it can help somebody though :)

(defun haskell-evil-open-above ()
  (interactive)
  (evil-digit-argument-or-evil-beginning-of-line)
  (haskell-indentation-newline-and-indent)
  (evil-previous-line)
  (haskell-indentation-indent-line)
  (evil-append-line nil))

(defun haskell-evil-open-below ()
  (interactive)
  (evil-append-line nil)
  (haskell-indentation-newline-and-indent))

  (evil-define-key 'normal haskell-mode-map "o" 'haskell-evil-open-below
                                            "O" 'haskell-evil-open-above)
tsoernes commented 7 years ago

@noraesae I tried your code and it's much better, but there is one small problem. When opening line above ('O') at a top-level statements the indent is 2 not 0. Take this example:

partitionM :: (Applicative m) => (a -> m Bool) -> [a] -> m ([a], [a])
partitionM p = foldr f $ pure ([],[]) ...

If you go to line 2 (partitionM p = ...), then press 'O', then the cursor is indented two spaces:

partitionM :: (Applicative m) => (a -> m Bool) -> [a] -> m ([a], [a])
  |<- cursor is here
partitionM p = foldr f $ pure ([],[]) ...

The same applies for opening line above ('O') at line 1 (function signature). Opening line below seem to work correctly.

cdepillabout commented 7 years ago

I created a question on the emacs stack exchange asking about indentation in haskell-mode. It appears that this is the bug in question:

https://emacs.stackexchange.com/questions/35431/how-to-get-correct-indentation-after-pressing-o-in-haskell-mode-using-evil-mod

Is @utatti's suggested solution still the best way to work-around this bug?

(defun haskell-evil-open-above ()
  (interactive)
  (evil-digit-argument-or-evil-beginning-of-line)
  (haskell-indentation-newline-and-indent)
  (evil-previous-line)
  (haskell-indentation-indent-line)
  (evil-append-line nil))

(defun haskell-evil-open-below ()
  (interactive)
  (evil-append-line nil)
  (haskell-indentation-newline-and-indent))

  (evil-define-key 'normal haskell-mode-map "o" 'haskell-evil-open-below
                                            "O" 'haskell-evil-open-above)
andreas-roehler commented 6 years ago

haskell-indentation-newline-and-indent works nice here with the example given. Maybe close this?

c50a326 commented 6 years ago

Why does evil-open-below != A<RET>? As a dumb user, this is annoying and it would be great if it was fixed properly so it just worked :D

joshcho commented 2 years ago

Anyone have any idea on how to fix this for org-babel source blocks? Getting weird behaviors there.

hraban commented 1 year ago

The same problem occurs with typescript-mode.

I got so sick of this I've rebound o to A RET:

(defun hly/evil-open-below (count)
  "Simulate evil’s o using ‘A RET’.

Evil’s native \\[evil-open-below] is too unreliable in the face of various
major
modes messing with indenting. This bruteforces it by relying on (the more
reliable) RET, instead.

See:

- https://github.com/haskell/haskell-mode/issues/1265
- https://emacs.stackexchange.com/a/2471
"
  (interactive "p")
  (setq unread-command-events (listify-key-sequence (kbd "RET")))
  (evil-append-line count))

(defun hly/evil-open-above (count)
  "Simulate evil’s O using ‘UP A RET’.

Evil’s native \\[evil-open-above] is too unreliable in the face of various major
modes messing with indenting. This bruteforces it by relying on (the more
reliable) RET, instead.

Doesn’t work on the first line of a file.
"
  (interactive "p")
  (forward-line -1)
  (hly/evil-open-below count))

(define-key evil-normal-state-map "o" 'hly/evil-open-below)
(define-key evil-normal-state-map "O" 'hly/evil-open-above)
singpolyma commented 2 weeks ago

Besides what @hraban wrote I also needed:

(with-eval-after-load "haskell-mode"
  (evil-define-key 'normal haskell-mode-map "o" 'hly/evil-open-below)
  (evil-define-key 'normal haskell-mode-map "O" 'hly/evil-open-above))