nix-indent-line as default nix-indent-function
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.
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 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...
Better than smie-indent-line or indent-relative? I've had less problems with smie-indent-line than nix-indent-line.
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...
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.
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