Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use nix-indent-line as the default indentation function #198

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 38 additions & 180 deletions nix-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,20 @@
"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.
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."
Expand Down Expand Up @@ -499,12 +496,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
Expand Down Expand Up @@ -720,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
Expand All @@ -854,52 +737,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."
Expand Down Expand Up @@ -932,7 +795,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
Expand Down Expand Up @@ -1018,19 +881,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 "# ")
Expand Down
20 changes: 1 addition & 19 deletions nix-mode.org
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -209,7 +191,7 @@ scripts. This is as simple as,
M-x nix-shell-configure RET <nixpkgs> 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 <nixpkgs> RET emacs RET
Expand Down