nix-mode icon indicating copy to clipboard operation
nix-mode copied to clipboard

nix-indent-line as default nix-indent-function

Open shlevy opened this issue 7 years ago • 6 comments

nix-indent-line is marked as buggy, and I'm happy to believe it is, but indent-relative is by contrast completely unusable. I constantly found myself fighting indentation before I found out about nix-indent-line. See @grahamc's screenshare for an example of the kind of thing I mean.

shlevy avatar Nov 23 '18 20:11 shlevy

There are still enough bugs in nix-indent-line that I am not sure if it is quite ready. One I noticed recently is that with _; { ... } is incorrect:

  meta = with stdenv.lib; {
    homepage = http://distrho.sourceforge.net;
    description = "A collection of cross-platform audio effects and plugins";
    longDescription = ''
      Includes:
      Dexed drowaudio-distortion drowaudio-distortionshaper drowaudio-flanger
      drowaudio-reverb drowaudio-tremolo drumsynth EasySSP eqinox HiReSam
      JuceDemoPlugin KlangFalter LUFSMeter LUFSMeterMulti Luftikus Obxd
      PitchedDelay ReFine StereoSourceSeparation TAL-Dub-3 TAL-Filter
      TAL-Filter-2 TAL-NoiseMaker TAL-Reverb TAL-Reverb-2 TAL-Reverb-3
      TAL-Vocoder-2 TheFunction ThePilgrim Vex Wolpertinger
    '';
    license = with licenses; [ gpl2 gpl3 gpl2Plus lgpl3 mit ];
    maintainers = [ maintainers.goibhniu ];
    platforms = [ "x86_64-linux" ];
  };

becomes

  meta = with stdenv.lib; {
  homepage = http://distrho.sourceforge.net;
    description = "A collection of cross-platform audio effects and plugins";
    longDescription = ''
      Includes:
      Dexed drowaudio-distortion drowaudio-distortionshaper drowaudio-flanger
      drowaudio-reverb drowaudio-tremolo drumsynth EasySSP eqinox HiReSam
      JuceDemoPlugin KlangFalter LUFSMeter LUFSMeterMulti Luftikus Obxd
      PitchedDelay ReFine StereoSourceSeparation TAL-Dub-3 TAL-Filter
      TAL-Filter-2 TAL-NoiseMaker TAL-Reverb TAL-Reverb-2 TAL-Reverb-3
      TAL-Vocoder-2 TheFunction ThePilgrim Vex Wolpertinger
    '';
    license = with licenses; [ gpl2 gpl3 gpl2Plus lgpl3 mit ];
    maintainers = [ maintainers.goibhniu ];
    platforms = [ "x86_64-linux" ];
  };

indent-relative is the default for Emacs so it feels like a good choice for now. On the other side of this, there were also complaints when nix-indent-line was the default:

https://github.com/NixOS/nix-mode/commit/cc23fd6a0e394aeeed603e2bfeb4a5ebc63db660

matthewbauer avatar Nov 26 '18 23:11 matthewbauer

@matthewbauer Maybe we should enable nix-indent-line as default indentation? It's been a lot better than the default one for a very long time now...

etu avatar Sep 04 '19 19:09 etu

Better than smie-indent-line or indent-relative? I've had less problems with smie-indent-line than nix-indent-line.

matthewbauer avatar Sep 04 '19 19:09 matthewbauer

I tend to find smie-indent-line to behave worse in multi-line strings, but I've not managed to set my finger on exactly how I think it's worse yet...

etu avatar Sep 04 '19 19:09 etu

smie-indent-line jumps to column 0 every time I hit enter inside a multi-line string, which tbh is a lot worse than just using vanilla emacs #'indent-line.

Profpatsch avatar Jan 10 '21 16:01 Profpatsch

I honestly like SMIE for indentation; the code needed is not super difficult to understand, and there's definitely less of it than if we wrote our own indentation logic.

Anyways, I hit a bumper today, where an expression of the form

f = {
  a,| b
  }:
  a;

(where | signifies the cursor) would turn into the following upon hitting Return:

f = {
  a,
    b
  }:
  a;

Not quite what we want! When I fixed this, I realised that nix-indent-line could just be a thin wrapper around smie-indent-line that takes care of some things that SMIE does not know about, like multiline strings (as @Profpatsch noted). This would enable us to delete quite a bit of the indentation logic, and clean up the file a bit.

@matthewbauer @nagy If you want I can prepare a PR

EDIT: For reference, here are the patches that I have for now:

Patch 1
From 2d99cacd4d6e2960bad19db3780ef3bda62c7826 Mon Sep 17 00:00:00 2001
From: Tony Zorman <[email protected]>
Date: Mon, 8 Jan 2024 20:20:17 +0100
Subject: [PATCH 1/2] nix-smie-rules: Fix indentation of ","

This seems deceptively simple, but there are actually quite a few
situations that can occur; e.g.,

    f = { a,    f = {    f = { a      f =       f =
          b       a,         , b        { a       {
        }         b          }          , b         a
                }                       }           , b
                                                  }

Thankfully, smie already sanely handles all of these for us, so rely on
that instead.
---
 nix-mode.el | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/nix-mode.el b/nix-mode.el
index 2e42e23..644843f 100644
--- a/nix-mode.el
+++ b/nix-mode.el
@@ -499,12 +499,8 @@ STRING-TYPE type of string based off of Emacs syntax table types"
     (`(:after . ":")
      (or (nix-smie--indent-args-line)
          (nix-smie--indent-anchor)))
-    (`(:after . ",")
-     (smie-rule-parent tab-width))
-    (`(:before . ",")
-     ;; The parent is either the enclosing "{" or some previous ",".
-     ;; In both cases this is what we want to align to.
-     (smie-rule-parent))
+    (`(,_ . ",")
+     (smie-rule-separator kind))
     (`(:before . "if")
      (let ((bol (line-beginning-position)))
        (save-excursion
--
2.42.0

Patch 2
From 5f132fd5ab72e23d981124ef449c0adb86b0556d Mon Sep 17 00:00:00 2001
From: Tony Zorman <[email protected]>
Date: Mon, 8 Jan 2024 20:37:16 +0100
Subject: [PATCH 2/2] nix-indent-line: Use smie-indent-line in most cases
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

SMIE does not know how to handle multiline strings, but other than that
it's quite a solid foundation—use it.
---
 nix-mode.el | 72 +++++++++++++++++++----------------------------------
 1 file changed, 26 insertions(+), 46 deletions(-)

diff --git a/nix-mode.el b/nix-mode.el
index 644843f..864e376 100644
--- a/nix-mode.el
+++ b/nix-mode.el
@@ -850,52 +850,32 @@ not to any other arguments."
 (defun nix-indent-line ()
   "Indent current line in a Nix expression."
   (interactive)
-  (let ((end-of-indentation
-         (save-excursion
-           (cond
-            ;; Indent first line of file to 0
-            ((= (line-number-at-pos) 1)
-             (indent-line-to 0))
-
-            ;; comment
-            ((save-excursion
-               (beginning-of-line)
-               (nix-is-comment-p))
-             (indent-line-to (nix-indent-prev-level)))
-
-            ;; string
-            ((save-excursion
-               (beginning-of-line)
-               (nth 3 (syntax-ppss)))
-             (indent-line-to (+ (nix-indent-prev-level)
-                                (* tab-width
-                                   (+ (if (save-excursion
-                                            (forward-line -1)
-                                            (end-of-line)
-                                            (skip-chars-backward "[:space:]")
-                                            (looking-back "''" 0)) 1 0)
-                                      (if (save-excursion
-                                            (beginning-of-line)
-                                            (skip-chars-forward
-                                             "[:space:]")
-                                            (looking-at "''")
-                                            ) -1 0)
-                                      )))))
-
-            ;; dedent '}', ']', ')' 'in'
-            ((nix-indent-to-backward-match))
-
-            ;; indent line after 'let', 'import', '[', '=', '(', '{'
-            ((nix-indent-first-line-in-block))
-
-            ;; indent between = and ; + 2, or to 2
-            ((nix-indent-expression-start))
-
-            ;; else
-            (t
-             (indent-line-to (nix-indent-prev-level))))
-           (point))))
-    (when (> end-of-indentation (point)) (goto-char end-of-indentation))))
+  (when-let ((*point*
+              (save-excursion
+                (cond
+                 ((save-excursion       ; Multiline string?
+                    (beginning-of-line)
+                    (nth 3 (syntax-ppss)))
+                  (indent-line-to (+ (nix-indent-prev-level)
+                                     (* tab-width
+                                        (+ (if (save-excursion
+                                                 (forward-line -1)
+                                                 (end-of-line)
+                                                 (skip-chars-backward "[:space:]")
+                                                 (looking-back "''" 0))
+                                               1
+                                             0)
+                                           (if (save-excursion
+                                                 (beginning-of-line)
+                                                 (skip-chars-forward "[:space:]")
+                                                 (looking-at "''"))
+                                               -1
+                                             0))))))
+                 (t                     ; Default to smie
+                  (smie-indent-line)))
+                (point)))
+             ((> *point* (point))))
+    (goto-char *point*)))

 (defun nix-is-comment-p ()
   "Whether we are in a comment."
--
2.42.0

slotThe avatar Jan 08 '24 19:01 slotThe