Skip to content
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