Skip to content

Fix indentation in multi-line match expressions. Fixes #98 #100

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

Merged
merged 18 commits into from
Aug 26, 2014
Merged
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
108 changes: 101 additions & 7 deletions elixir-smie.el
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
(statements (statement)
(statement ";" statements))
(statement ("def" non-block-expr "do" statements "end")
(non-block-expr "fn" match-statement "end")
(non-block-expr "fn" match-statements "end")
(non-block-expr "do" statements "end")
("if" non-block-expr "do" statements "else" statements "end")
("if" non-block-expr "do" statements "end")
Expand Down Expand Up @@ -76,9 +76,11 @@
(left "*" "/"))))))

(defvar elixir-smie--operator-regexp
(regexp-opt '("<<<" ">>>" "^^^" "~~~" "&&&" "|||" "===" "!==" "==" "!=" "<="
">=" "<" ">" "&&" "||" "<>" "++" "--" "//"
"/>" "=~" "|>" "->")))
(rx (or "<<<" ">>>" "^^^" "~~~" "&&&" "|||" "===" "!==" "==" "!=" "<="
">=" "<" ">" "&&" "||" "<>" "++" "--" "//" "/>" "=~" "|>")))

(defvar elixir-smie--block-operator-regexp
(rx "->"))

(defvar elixir-smie--spaces-til-eol-regexp
(rx (and (1+ space) eol))
Expand All @@ -96,6 +98,40 @@
(not (or (memq (char-before) '(?\{ ?\[))
(looking-back elixir-smie--operator-regexp (- (point) 3) t))))

(defun elixir-smie--semi-ends-match ()
"Return non-nil if the current line concludes a match block."
(save-excursion
;; Warning: Recursion.
;; This is easy though.

;; 1. If we're at a blank line, move forward a character. This takes us to
;; the next line.
;; 2. If we're not at the end of the buffer, call this function again.
;; (Otherwise, return nil.)

;; The point here is that we want to treat blank lines as a single semi-
;; colon when it comes to detecting the end of match statements. This could
;; also be handled by a `while' expression or some other looping mechanism.
(flet ((self-call ()
(if (< (point) (point-max))
(elixir-smie--semi-ends-match)
nil)))
(cond
((and (eolp) (bolp))
(forward-char)
(self-call))
((looking-at elixir-smie--spaces-til-eol-regexp)
(move-beginning-of-line 2)
(self-call))
;; And if we're NOT on a blank line, move to the end of the line, and see
;; if we're looking back at a block operator.
(t (move-end-of-line 1)
(looking-back elixir-smie--block-operator-regexp))))))

(defun elixir-smie--same-line-as-parent (parent-pos child-pos)
"Return non-nil if `child-pos' is on same line as `parent-pos'."
(= (line-number-at-pos parent-pos) (line-number-at-pos child-pos)))

(defun elixir-smie-forward-token ()
(cond
;; If there is nothing but whitespace between the last token and eol, emit
Expand All @@ -105,7 +141,12 @@
";")
((and (looking-at "[\n#]") (elixir-smie--implicit-semi-p))
(if (eolp) (forward-char 1) (forward-comment 1))
";")
(if (elixir-smie--semi-ends-match)
"MATCH-STATEMENT-DELIMITER"
";"))
((looking-at elixir-smie--block-operator-regexp)
(goto-char (match-end 0))
"->")
((looking-at elixir-smie--operator-regexp)
(goto-char (match-end 0))
"OP")
Expand All @@ -117,7 +158,12 @@
(cond
((and (> pos (line-end-position))
(elixir-smie--implicit-semi-p))
";")
(if (elixir-smie--semi-ends-match)
"MATCH-STATEMENT-DELIMITER"
";"))
((looking-back elixir-smie--block-operator-regexp (- (point) 3) t)
(goto-char (match-beginning 0))
"->")
((looking-back elixir-smie--operator-regexp (- (point) 3) t)
(goto-char (match-beginning 0))
"OP")
Expand All @@ -140,14 +186,62 @@
((smie-rule-sibling-p) nil)
((smie-rule-hanging-p) (smie-rule-parent elixir-smie-indent-basic))
(t elixir-smie-indent-basic)))

(`(:before . "MATCH-STATEMENT-DELIMITER")
(cond
((and (not (smie-rule-sibling-p))
(smie-rule-hanging-p))
(smie-rule-parent elixir-smie-indent-basic))))
;; ((and (smie-rule-hanging-p)
;; (smie-rule-sibling-p))
;; (smie-rule-parent))))

(`(:after . "MATCH-STATEMENT-DELIMITER")
(cond
;; We don't want to specify any rules for the first `->' after `do' or
;; `fn', since SMIE will look at the BNF to see how to handle indentation
;; in that case.
((smie-rule-hanging-p)
(smie-rule-parent elixir-smie-indent-basic))))

(`(:before . "->")
(cond
((smie-rule-hanging-p)
(smie-rule-parent elixir-smie-indent-basic))))

(`(:after . "->")
(cond
;; This first condition is kind of complicated so I'll try to make this
;; comment as clear as possible.

;; "If `->' is the last thing on the line, and its parent token
;; is `fn' ..."
((and (smie-rule-hanging-p)
(smie-rule-parent-p "fn"))
;; "... and if:

;; 1. `smie--parent' is non-nil
;; 2. the `->' token in question is on the same line as its parent (if
;; the logic has gotten this far, its parent will be `fn')

;; ... then indent the line after the `->' aligned with the
;; parent, offset by `elixir-smie-indent-basic'."
(if (and smie--parent (elixir-smie--same-line-as-parent
(nth 1 smie--parent)
(point)))
(smie-rule-parent elixir-smie-indent-basic)))
;; Otherwise, if just indent by two.
((smie-rule-hanging-p)
elixir-smie-indent-basic)))

(`(:before . ";")
(cond
((smie-rule-parent-p "after" "catch" "def" "defmodule" "defp" "do" "else"
"fn" "if" "rescue" "try" "unless")
(smie-rule-parent elixir-smie-indent-basic))))
(`(:after . ";")
(if (smie-rule-parent-p "if")
(smie-rule-parent 0)))))
(smie-rule-parent)))))

(define-minor-mode elixir-smie-mode
"SMIE-based indentation and syntax for Elixir"
Expand Down
Loading