From d2b3e6292617ec05217a4d740fa8726972e130c7 Mon Sep 17 00:00:00 2001 From: Tony Zorman Date: Mon, 8 Jan 2024 20:20:17 +0100 Subject: [PATCH 1/4] 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 From 05265b075b785582cd6898fa8e456a6e01a6a633 Mon Sep 17 00:00:00 2001 From: Tony Zorman Date: Mon, 8 Jan 2024 20:37:16 +0100 Subject: [PATCH 2/4] 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." From b9d6ed5e06503191d4a814af2087f2c0c47040f5 Mon Sep 17 00:00:00 2001 From: Tony Zorman Date: Tue, 16 Jan 2024 12:31:12 +0100 Subject: [PATCH 3/4] Make old indentation functions obsolete SMIE is necessary now, and nix-indent-line is the one and only indent function to use. --- nix-mode.el | 28 ++++++++++------------------ nix-mode.org | 20 +------------------- 2 files changed, 11 insertions(+), 37 deletions(-) diff --git a/nix-mode.el b/nix-mode.el index 864e376..99177b3 100644 --- a/nix-mode.el +++ b/nix-mode.el @@ -28,16 +28,12 @@ "Nix mode customizations." :group 'nix) -(defcustom nix-indent-function 'smie-indent-line - "The function to use to indent. - -Valid functions for this are: - -- ‘indent-relative’ -- ‘nix-indent-line' (buggy) -- `smie-indent-line' (‘nix-mode-use-smie’ must be enabled)" +(defcustom nix-indent-function 'nix-indent-line + "The function to use to indent." :group 'nix-mode :type 'function) +(make-obsolete 'nix-indent-function "This value is a no-op now, \ +and is only here for backwards compatibility." "1.6.0") (defcustom nix-mode-use-smie t "Whether to use SMIE when editing Nix files. @@ -45,6 +41,7 @@ This is enabled by default, but can take a while to load with very large Nix files (all-packages.nix)." :group 'nix-mode :type 'boolean) +(make-obsolete 'nix-mode-use-smie "SMIE is required for nix-mode to work correctly." "1.6.0") (defgroup nix-faces nil "Nix faces." @@ -908,7 +905,7 @@ END where to end the region." (nix-is-comment-p))))) ;; Don't mess with strings. (nix-is-string-p)) - (funcall nix-indent-function))) + (nix-indent-line))) (forward-line 1)))) ;;;###autoload @@ -994,19 +991,14 @@ The hook `nix-mode-hook' is run when Nix mode is started. (let ((nix-smie-indent-functions ;; Replace the smie-indent-* equivalents with nix-mode's. (mapcar (lambda (fun) (pcase fun - ('smie-indent-exps 'nix-smie--indent-exps) - ('smie-indent-close 'nix-smie--indent-close) - (_ fun))) + ('smie-indent-exps 'nix-smie--indent-exps) + ('smie-indent-close 'nix-smie--indent-close) + (_ fun))) smie-indent-functions))) (setq-local smie-indent-functions nix-smie-indent-functions))) ;; Automatic indentation [C-j] - (setq-local indent-line-function - (lambda () - (if (and (not nix-mode-use-smie) - (eq nix-indent-function 'smie-indent-line)) - (indent-relative) - (funcall nix-indent-function)))) + (setq-local indent-line-function #'nix-indent-line) ;; Indenting of comments (setq-local comment-start "# ") diff --git a/nix-mode.org b/nix-mode.org index d5cae77..837ccd0 100644 --- a/nix-mode.org +++ b/nix-mode.org @@ -113,24 +113,6 @@ You can set it up to handle .nix files with, :mode ("\\.nix\\'" "\\.nix.in\\'")) #+END_SRC -To turn on the experimental “electric” style Nix indent, you have to -set the custom variable, nix-indent-function. This can be set by -typing, - -#+BEGIN_SRC text -M-x customize-variable RET nix-indent-function RET -#+END_SRC - -There are three possible values for nix-mode. They are: - -- indent-relative -- smie-indent-line (default) -- nix-indent-line - -Starting in version 1.4.0, SMIE is the default and it works very well -(kudos to @j-piecuch on GitHub). You can restore the old behavior by -setting =nix-indent-function= to =indent-relative=. - *** nix-drv-mode nix-drv-mode is a simple major mode for viewing Nix’s .drv files. If @@ -209,7 +191,7 @@ scripts. This is as simple as, M-x nix-shell-configure RET RET emacs RET #+END_SRC -This, again, will take a few minutes. After that, though, we can build +This, again, will take a few minutes. After that, though, we can build #+BEGIN_SRC text M-x nix-shell-build RET RET emacs RET From 0f480d2d5bb4ec270329d7cb9f7151b5a091c619 Mon Sep 17 00:00:00 2001 From: Tony Zorman Date: Tue, 16 Jan 2024 12:31:48 +0100 Subject: [PATCH 4/4] [EXPERIMENTAL] Remove old nix-indent-line code --- nix-mode.el | 110 ---------------------------------------------------- 1 file changed, 110 deletions(-) diff --git a/nix-mode.el b/nix-mode.el index 99177b3..9af8cf4 100644 --- a/nix-mode.el +++ b/nix-mode.el @@ -713,116 +713,6 @@ not to any other arguments." ;; begins. I'm not sure which one is better. (+ tab-width (current-indentation)))))))) -;;; Indentation not using SMIE - -(defun nix-find-backward-matching-token () - "Find the previous Nix token." - (cond - ((looking-at "in\\b") - (let ((counter 1)) - (while (and (> counter 0) - (re-search-backward "\\b\\(let\\|in\\)\\b" nil t)) - (unless (or (nix--get-string-type (nix--get-parse-state (point))) - (nix-is-comment-p)) - (setq counter (cond ((looking-at "let") (- counter 1)) - ((looking-at "in") (+ counter 1)))))) - counter )) - ((looking-at "}") - (backward-up-list) t) - ((looking-at "]") - (backward-up-list) t) - ((looking-at ")") - (backward-up-list) t))) - -(defun nix-indent-to-backward-match () - "Match the previous line’s indentation." - (let ((matching-indentation (save-excursion - (beginning-of-line) - (skip-chars-forward "[:space:]") - (if (nix-find-backward-matching-token) - (current-indentation))))) - (when matching-indentation (indent-line-to matching-indentation) t))) - -(defun nix-indent-first-line-in-block () - "Indent the first line in a block." - - (let ((matching-indentation (save-excursion - ;; Go back to previous line that contain anything useful to check the - ;; contents of that line. - (beginning-of-line) - (skip-chars-backward "\n[:space:]") - - ;; Grab the full string of the line before the one we're indenting - (let ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) - ;; Then regex-match strings at the end of the line to detect if we need to indent the line after. - ;; We could probably add more things to look for here in the future. - (if (or (string-match "\\blet$" line) - (string-match "\\bimport$" line) - (string-match "\\[$" line) - (string-match "=$" line) - (string-match "\($" line) - (string-match "\{$" line)) - - ;; If it matches any of the regexes above, grab the indent level - ;; of the line and add 2 to ident the line below this one. - (+ 2 (current-indentation))))))) - (when matching-indentation (indent-line-to matching-indentation) t))) - -(defun nix-mode-search-backward () - "Search backward for items of interest regarding indentation." - (re-search-backward nix-re-ends nil t) - (re-search-backward nix-re-quotes nil t) - (re-search-backward nix-re-caps nil t)) - -(defun nix-indent-expression-start () - "Indent the start of a nix expression." - (let* ((ends 0) - (once nil) - (done nil) - (indent (current-indentation))) - (save-excursion - ;; we want to indent this line, so we don't care what it - ;; contains skip to the beginning so reverse searching doesn't - ;; find any matches within - (beginning-of-line) - ;; search backward until an unbalanced cap is found or no cap or - ;; end is found - (while (and (not done) (nix-mode-search-backward)) - (cond - ((looking-at nix-re-quotes) - ;; skip over strings entirely - (re-search-backward nix-re-quotes nil t)) - ((looking-at nix-re-comments) - ;; skip over comments entirely - (re-search-backward nix-re-comments nil t)) - ((looking-at nix-re-ends) - ;; count the matched end - ;; this means we expect to find at least one more cap - (setq ends (+ ends 1))) - ((looking-at nix-re-caps) - ;; we found at least one cap - ;; this means our function will return true - ;; this signals to the caller we handled the indentation - (setq once t) - (if (> ends 0) - ;; this cap corresponds to a previously matched end - ;; reduce the number of unbalanced ends - (setq ends (- ends 1)) - ;; no unbalanced ends correspond to this cap - ;; this means we have found the expression that contains our line - ;; we want to indent relative to this line - (setq indent (current-indentation)) - ;; signal that the search loop should exit - (setq done t)))))) - ;; done is t when we found an unbalanced expression cap - (when done - ;; indent relative to the indentation of the expression - ;; containing our line - (indent-line-to (+ tab-width indent))) - ;; return t to the caller if we found at least one cap - ;; this signals that we handled the indentation - once)) - (defun nix-indent-prev-level () "Get the indent level of the previous line." (save-excursion