Skip to content

Commit 68f884f

Browse files
committed
Merge pull request #100 from elixir-lang/multiline-match-indent
Fix indentation in multi-line match expressions. Fixes #98
2 parents 23a75c4 + 67751d8 commit 68f884f

File tree

2 files changed

+175
-29
lines changed

2 files changed

+175
-29
lines changed

elixir-smie.el

Lines changed: 101 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
(statements (statement)
4343
(statement ";" statements))
4444
(statement ("def" non-block-expr "do" statements "end")
45-
(non-block-expr "fn" match-statement "end")
45+
(non-block-expr "fn" match-statements "end")
4646
(non-block-expr "do" statements "end")
4747
("if" non-block-expr "do" statements "else" statements "end")
4848
("if" non-block-expr "do" statements "end")
@@ -76,9 +76,11 @@
7676
(left "*" "/"))))))
7777

7878
(defvar elixir-smie--operator-regexp
79-
(regexp-opt '("<<<" ">>>" "^^^" "~~~" "&&&" "|||" "===" "!==" "==" "!=" "<="
80-
">=" "<" ">" "&&" "||" "<>" "++" "--" "//"
81-
"/>" "=~" "|>" "->")))
79+
(rx (or "<<<" ">>>" "^^^" "~~~" "&&&" "|||" "===" "!==" "==" "!=" "<="
80+
">=" "<" ">" "&&" "||" "<>" "++" "--" "//" "/>" "=~" "|>")))
81+
82+
(defvar elixir-smie--block-operator-regexp
83+
(rx "->"))
8284

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

101+
(defun elixir-smie--semi-ends-match ()
102+
"Return non-nil if the current line concludes a match block."
103+
(save-excursion
104+
;; Warning: Recursion.
105+
;; This is easy though.
106+
107+
;; 1. If we're at a blank line, move forward a character. This takes us to
108+
;; the next line.
109+
;; 2. If we're not at the end of the buffer, call this function again.
110+
;; (Otherwise, return nil.)
111+
112+
;; The point here is that we want to treat blank lines as a single semi-
113+
;; colon when it comes to detecting the end of match statements. This could
114+
;; also be handled by a `while' expression or some other looping mechanism.
115+
(flet ((self-call ()
116+
(if (< (point) (point-max))
117+
(elixir-smie--semi-ends-match)
118+
nil)))
119+
(cond
120+
((and (eolp) (bolp))
121+
(forward-char)
122+
(self-call))
123+
((looking-at elixir-smie--spaces-til-eol-regexp)
124+
(move-beginning-of-line 2)
125+
(self-call))
126+
;; And if we're NOT on a blank line, move to the end of the line, and see
127+
;; if we're looking back at a block operator.
128+
(t (move-end-of-line 1)
129+
(looking-back elixir-smie--block-operator-regexp))))))
130+
131+
(defun elixir-smie--same-line-as-parent (parent-pos child-pos)
132+
"Return non-nil if `child-pos' is on same line as `parent-pos'."
133+
(= (line-number-at-pos parent-pos) (line-number-at-pos child-pos)))
134+
99135
(defun elixir-smie-forward-token ()
100136
(cond
101137
;; If there is nothing but whitespace between the last token and eol, emit
@@ -105,7 +141,12 @@
105141
";")
106142
((and (looking-at "[\n#]") (elixir-smie--implicit-semi-p))
107143
(if (eolp) (forward-char 1) (forward-comment 1))
108-
";")
144+
(if (elixir-smie--semi-ends-match)
145+
"MATCH-STATEMENT-DELIMITER"
146+
";"))
147+
((looking-at elixir-smie--block-operator-regexp)
148+
(goto-char (match-end 0))
149+
"->")
109150
((looking-at elixir-smie--operator-regexp)
110151
(goto-char (match-end 0))
111152
"OP")
@@ -117,7 +158,12 @@
117158
(cond
118159
((and (> pos (line-end-position))
119160
(elixir-smie--implicit-semi-p))
120-
";")
161+
(if (elixir-smie--semi-ends-match)
162+
"MATCH-STATEMENT-DELIMITER"
163+
";"))
164+
((looking-back elixir-smie--block-operator-regexp (- (point) 3) t)
165+
(goto-char (match-beginning 0))
166+
"->")
121167
((looking-back elixir-smie--operator-regexp (- (point) 3) t)
122168
(goto-char (match-beginning 0))
123169
"OP")
@@ -140,14 +186,62 @@
140186
((smie-rule-sibling-p) nil)
141187
((smie-rule-hanging-p) (smie-rule-parent elixir-smie-indent-basic))
142188
(t elixir-smie-indent-basic)))
189+
190+
(`(:before . "MATCH-STATEMENT-DELIMITER")
191+
(cond
192+
((and (not (smie-rule-sibling-p))
193+
(smie-rule-hanging-p))
194+
(smie-rule-parent elixir-smie-indent-basic))))
195+
;; ((and (smie-rule-hanging-p)
196+
;; (smie-rule-sibling-p))
197+
;; (smie-rule-parent))))
198+
199+
(`(:after . "MATCH-STATEMENT-DELIMITER")
200+
(cond
201+
;; We don't want to specify any rules for the first `->' after `do' or
202+
;; `fn', since SMIE will look at the BNF to see how to handle indentation
203+
;; in that case.
204+
((smie-rule-hanging-p)
205+
(smie-rule-parent elixir-smie-indent-basic))))
206+
207+
(`(:before . "->")
208+
(cond
209+
((smie-rule-hanging-p)
210+
(smie-rule-parent elixir-smie-indent-basic))))
211+
212+
(`(:after . "->")
213+
(cond
214+
;; This first condition is kind of complicated so I'll try to make this
215+
;; comment as clear as possible.
216+
217+
;; "If `->' is the last thing on the line, and its parent token
218+
;; is `fn' ..."
219+
((and (smie-rule-hanging-p)
220+
(smie-rule-parent-p "fn"))
221+
;; "... and if:
222+
223+
;; 1. `smie--parent' is non-nil
224+
;; 2. the `->' token in question is on the same line as its parent (if
225+
;; the logic has gotten this far, its parent will be `fn')
226+
227+
;; ... then indent the line after the `->' aligned with the
228+
;; parent, offset by `elixir-smie-indent-basic'."
229+
(if (and smie--parent (elixir-smie--same-line-as-parent
230+
(nth 1 smie--parent)
231+
(point)))
232+
(smie-rule-parent elixir-smie-indent-basic)))
233+
;; Otherwise, if just indent by two.
234+
((smie-rule-hanging-p)
235+
elixir-smie-indent-basic)))
236+
143237
(`(:before . ";")
144238
(cond
145239
((smie-rule-parent-p "after" "catch" "def" "defmodule" "defp" "do" "else"
146240
"fn" "if" "rescue" "try" "unless")
147241
(smie-rule-parent elixir-smie-indent-basic))))
148242
(`(:after . ";")
149243
(if (smie-rule-parent-p "if")
150-
(smie-rule-parent 0)))))
244+
(smie-rule-parent)))))
151245

152246
(define-minor-mode elixir-smie-mode
153247
"SMIE-based indentation and syntax for Elixir"

0 commit comments

Comments
 (0)