|
42 | 42 | (statements (statement)
|
43 | 43 | (statement ";" statements))
|
44 | 44 | (statement ("def" non-block-expr "do" statements "end")
|
45 |
| - (non-block-expr "fn" match-statement "end") |
| 45 | + (non-block-expr "fn" match-statements "end") |
46 | 46 | (non-block-expr "do" statements "end")
|
47 | 47 | ("if" non-block-expr "do" statements "else" statements "end")
|
48 | 48 | ("if" non-block-expr "do" statements "end")
|
|
76 | 76 | (left "*" "/"))))))
|
77 | 77 |
|
78 | 78 | (defvar elixir-smie--operator-regexp
|
79 |
| - (regexp-opt '("<<<" ">>>" "^^^" "~~~" "&&&" "|||" "===" "!==" "==" "!=" "<=" |
80 |
| - ">=" "<" ">" "&&" "||" "<>" "++" "--" "//" |
81 |
| - "/>" "=~" "|>" "->"))) |
| 79 | + (rx (or "<<<" ">>>" "^^^" "~~~" "&&&" "|||" "===" "!==" "==" "!=" "<=" |
| 80 | + ">=" "<" ">" "&&" "||" "<>" "++" "--" "//" "/>" "=~" "|>"))) |
| 81 | + |
| 82 | +(defvar elixir-smie--block-operator-regexp |
| 83 | + (rx "->")) |
82 | 84 |
|
83 | 85 | (defvar elixir-smie--spaces-til-eol-regexp
|
84 | 86 | (rx (and (1+ space) eol))
|
|
96 | 98 | (not (or (memq (char-before) '(?\{ ?\[))
|
97 | 99 | (looking-back elixir-smie--operator-regexp (- (point) 3) t))))
|
98 | 100 |
|
| 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 | + |
99 | 135 | (defun elixir-smie-forward-token ()
|
100 | 136 | (cond
|
101 | 137 | ;; If there is nothing but whitespace between the last token and eol, emit
|
|
105 | 141 | ";")
|
106 | 142 | ((and (looking-at "[\n#]") (elixir-smie--implicit-semi-p))
|
107 | 143 | (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 | + "->") |
109 | 150 | ((looking-at elixir-smie--operator-regexp)
|
110 | 151 | (goto-char (match-end 0))
|
111 | 152 | "OP")
|
|
117 | 158 | (cond
|
118 | 159 | ((and (> pos (line-end-position))
|
119 | 160 | (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 | + "->") |
121 | 167 | ((looking-back elixir-smie--operator-regexp (- (point) 3) t)
|
122 | 168 | (goto-char (match-beginning 0))
|
123 | 169 | "OP")
|
|
140 | 186 | ((smie-rule-sibling-p) nil)
|
141 | 187 | ((smie-rule-hanging-p) (smie-rule-parent elixir-smie-indent-basic))
|
142 | 188 | (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 | + |
143 | 237 | (`(:before . ";")
|
144 | 238 | (cond
|
145 | 239 | ((smie-rule-parent-p "after" "catch" "def" "defmodule" "defp" "do" "else"
|
146 | 240 | "fn" "if" "rescue" "try" "unless")
|
147 | 241 | (smie-rule-parent elixir-smie-indent-basic))))
|
148 | 242 | (`(:after . ";")
|
149 | 243 | (if (smie-rule-parent-p "if")
|
150 |
| - (smie-rule-parent 0))))) |
| 244 | + (smie-rule-parent))))) |
151 | 245 |
|
152 | 246 | (define-minor-mode elixir-smie-mode
|
153 | 247 | "SMIE-based indentation and syntax for Elixir"
|
|
0 commit comments