summaryrefslogtreecommitdiff
path: root/.emacs.d/lisp/rust-prog-mode.el
diff options
context:
space:
mode:
Diffstat (limited to '.emacs.d/lisp/rust-prog-mode.el')
-rw-r--r--.emacs.d/lisp/rust-prog-mode.el1509
1 files changed, 1509 insertions, 0 deletions
diff --git a/.emacs.d/lisp/rust-prog-mode.el b/.emacs.d/lisp/rust-prog-mode.el
new file mode 100644
index 0000000..51e802d
--- /dev/null
+++ b/.emacs.d/lisp/rust-prog-mode.el
@@ -0,0 +1,1509 @@
1;;; rust-prog-mode.el --- old rust-mode without treesitter -*-lexical-binding: t-*-
2;;; Commentary:
3
4;; rust-mode code deriving from prog-mode instead of rust-ts-mode
5
6;;; Code:
7(require 'rust-common)
8
9(defvar electric-pair-inhibit-predicate)
10(defvar electric-pair-skip-self)
11(defvar electric-indent-chars)
12
13(defvar rust-prettify-symbols-alist
14 '(("&&" . ?∧) ("||" . ?∨)
15 ("<=" . ?≤) (">=" . ?≥) ("!=" . ?≠)
16 ("INFINITY" . ?∞) ("->" . ?→) ("=>" . ?⇒))
17 "Alist of symbol prettifications used for `prettify-symbols-alist'.")
18
19(defcustom rust-indent-offset 4
20 "Indent Rust code by this number of spaces."
21 :type 'integer
22 :group 'rust-mode
23 :safe #'integerp)
24
25(defcustom rust-indent-method-chain nil
26 "Indent Rust method chains, aligned by the `.' operators."
27 :type 'boolean
28 :group 'rust-mode
29 :safe #'booleanp)
30
31(defcustom rust-indent-where-clause nil
32 "Indent lines starting with the `where' keyword following a function or trait.
33When nil, `where' will be aligned with `fn' or `trait'."
34 :type 'boolean
35 :group 'rust-mode
36 :safe #'booleanp)
37
38(defcustom rust-match-angle-brackets t
39 "Whether to enable angle bracket (`<' and `>') matching where appropriate."
40 :type 'boolean
41 :safe #'booleanp
42 :group 'rust-mode)
43
44(defcustom rust-indent-return-type-to-arguments t
45 "Indent a line starting with the `->' (RArrow) following a function, aligning
46to the function arguments. When nil, `->' will be indented one level."
47 :type 'boolean
48 :group 'rust-mode
49 :safe #'booleanp)
50
51(defface rust-unsafe
52 '((t :inherit font-lock-warning-face))
53 "Face for the `unsafe' keyword."
54 :group 'rust-mode)
55
56(defface rust-question-mark
57 '((t :weight bold :inherit font-lock-builtin-face))
58 "Face for the question mark operator."
59 :group 'rust-mode)
60
61(defface rust-ampersand-face
62 '((t :inherit default))
63 "Face for the ampersand reference mark."
64 :group 'rust-mode)
65
66(defface rust-builtin-formatting-macro
67 '((t :inherit font-lock-builtin-face))
68 "Face for builtin formatting macros (print! &c.)."
69 :group 'rust-mode)
70
71(defface rust-string-interpolation
72 '((t :slant italic :inherit font-lock-string-face))
73 "Face for interpolating braces in builtin formatting macro strings."
74 :group 'rust-mode)
75
76;;; Syntax
77
78(defun rust-re-word (inner) (concat "\\<" inner "\\>"))
79(defun rust-re-grab (inner) (concat "\\(" inner "\\)"))
80(defun rust-re-shy (inner) (concat "\\(?:" inner "\\)"))
81
82(defconst rust-re-ident "[[:word:][:multibyte:]_][[:word:][:multibyte:]_[:digit:]]*")
83(defconst rust-re-lc-ident "[[:lower:][:multibyte:]_][[:word:][:multibyte:]_[:digit:]]*")
84(defconst rust-re-uc-ident "[[:upper:]][[:word:][:multibyte:]_[:digit:]]*")
85(defvar rust-re-vis
86 ;; pub | pub ( crate ) | pub ( self ) | pub ( super ) | pub ( in SimplePath )
87 (concat
88 "pub"
89 (rust-re-shy
90 (concat
91 "[[:space:]]*([[:space:]]*"
92 (rust-re-shy
93 (concat "crate" "\\|"
94 "\\(?:s\\(?:elf\\|uper\\)\\)" "\\|"
95 ;; in SimplePath
96 (rust-re-shy
97 (concat
98 "in[[:space:]]+"
99 rust-re-ident
100 (rust-re-shy (concat "::" rust-re-ident)) "*"))))
101 "[[:space:]]*)"))
102 "?"))
103(defconst rust-re-unsafe "unsafe")
104(defconst rust-re-extern "extern")
105(defconst rust-re-async-or-const "async\\|const")
106(defconst rust-re-generic
107 (concat "<[[:space:]]*'" rust-re-ident "[[:space:]]*>"))
108(defconst rust-re-union
109 (rx-to-string
110 `(seq
111 (or space line-start)
112 (group symbol-start "union" symbol-end)
113 (+ space) (regexp ,rust-re-ident))))
114
115(defun rust-re-item-def (itype)
116 (concat (rust-re-word itype)
117 (rust-re-shy rust-re-generic) "?"
118 "[[:space:]]+" (rust-re-grab rust-re-ident)))
119
120;; TODO some of this does only make sense for `fn' (unsafe, extern...)
121;; and not other items
122(defun rust-re-item-def-imenu (itype)
123 (concat "^[[:space:]]*"
124 (rust-re-shy (concat rust-re-vis "[[:space:]]+")) "?"
125 (rust-re-shy (concat (rust-re-word "default") "[[:space:]]+")) "?"
126 (rust-re-shy (concat (rust-re-shy rust-re-async-or-const) "[[:space:]]+")) "?"
127 (rust-re-shy (concat (rust-re-word rust-re-unsafe) "[[:space:]]+")) "?"
128 (rust-re-shy (concat (rust-re-word rust-re-extern) "[[:space:]]+"
129 (rust-re-shy "\"[^\"]+\"[[:space:]]+") "?")) "?"
130 (rust-re-item-def itype)))
131
132(defvar rust-imenu-generic-expression
133 (append (mapcar #'(lambda (x)
134 (list (capitalize x) (rust-re-item-def-imenu x) 1))
135 '("enum" "struct" "union" "type" "mod" "fn" "trait" "impl"))
136 `(("Macro" ,(rust-re-item-def-imenu "macro_rules!") 1)))
137 "Value for `imenu-generic-expression' in Rust mode.
138
139Create a hierarchical index of the item definitions in a Rust file.
140
141Imenu will show all the enums, structs, etc. in their own subheading.
142Use idomenu (imenu with `ido-mode') for best mileage.")
143
144;;; Prettify
145
146(defun rust--prettify-symbols-compose-p (start end match)
147 "Return true iff the symbol MATCH should be composed.
148See `prettify-symbols-compose-predicate'."
149 (and (fboundp 'prettify-symbols-default-compose-p)
150 (prettify-symbols-default-compose-p start end match)
151 ;; Make sure || is not a closure with 0 arguments and && is not
152 ;; a double reference.
153 (pcase match
154 ("||" (not (save-excursion
155 (goto-char start)
156 (looking-back "\\(?:\\<move\\|[[({:=,;]\\) *"
157 (line-beginning-position)))))
158 ("&&" (char-equal (char-after end) ?\s))
159 (_ t))))
160
161(defvar rust-top-item-beg-re
162 (concat "\\s-*\\(?:priv\\|pub\\)?\\s-*"
163 ;; TODO some of this does only make sense for `fn' (unsafe, extern...)
164 ;; and not other items
165 (rust-re-shy (concat (rust-re-shy rust-re-vis) "[[:space:]]+")) "?"
166 (rust-re-shy (concat (rust-re-shy rust-re-async-or-const) "[[:space:]]+")) "?"
167 (rust-re-shy (concat (rust-re-shy rust-re-unsafe) "[[:space:]]+")) "?"
168 (regexp-opt
169 '("enum" "struct" "union" "type" "mod" "fn" "static" "impl"
170 "extern" "trait" "async"))
171 "\\_>")
172 "Start of a Rust item.")
173
174(defconst rust-re-type-or-constructor
175 (rx symbol-start
176 (group upper (0+ (any word nonascii digit "_")))
177 symbol-end))
178
179(defconst rust-keywords
180 '("as" "async" "await"
181 "box" "break"
182 "const" "continue" "crate"
183 "do" "dyn"
184 "else" "enum" "extern" "existential"
185 "false" "fn" "for"
186 "if" "impl" "in"
187 "let" "loop"
188 "match" "mod" "move" "mut"
189 "priv" "pub"
190 "ref" "return"
191 "self" "static" "struct" "super"
192 "true" "trait" "type" "try"
193 "use"
194 "virtual"
195 "where" "while"
196 "yield")
197 "Font-locking definitions and helpers.")
198
199(defconst rust-special-types
200 '("u8" "i8"
201 "u16" "i16"
202 "u32" "i32"
203 "u64" "i64"
204 "u128" "i128"
205
206 "f32" "f64"
207 "isize" "usize"
208 "bool"
209 "str" "char"))
210
211(defconst rust-expression-introducers
212 '("if" "while" "match" "return" "box" "in")
213 "List of Rust keywords that are always followed by expressions.")
214
215(defconst rust-number-with-type
216 (eval-when-compile
217 (concat
218 "\\_<\\(?:0[box]?\\|[1-9]\\)[[:digit:]a-fA-F_.]*\\(?:[eE][+-]?[[:digit:]_]\\)?"
219 (regexp-opt '("u8" "i8" "u16" "i16" "u32" "i32" "u64" "i64"
220 "u128" "i128" "usize" "isize" "f32" "f64")
221 t)
222 "\\_>"))
223 "Regular expression matching a number with a type suffix.")
224
225(defvar rust-builtin-formatting-macros
226 '("eprint"
227 "eprintln"
228 "format"
229 "print"
230 "println")
231 "List of builtin Rust macros for string formatting.
232This is used by `rust-font-lock-keywords'.
233\(`write!' is handled separately).")
234
235(defvar rust-formatting-macro-opening-re
236 "[[:space:]\n]*[({[][[:space:]\n]*"
237 "Regular expression to match the opening delimiter of a Rust formatting macro.")
238
239(defvar rust-start-of-string-re
240 "\\(?:r#*\\)?\""
241 "Regular expression to match the start of a Rust raw string.")
242
243(defun rust-path-font-lock-matcher (re-ident)
244 "Match occurrences of RE-IDENT followed by a double-colon.
245Examples include to match names like \"foo::\" or \"Foo::\".
246Does not match type annotations of the form \"foo::<\"."
247 `(lambda (limit)
248 (catch 'rust-path-font-lock-matcher
249 (while t
250 (let* ((symbol-then-colons (rx-to-string '(seq (group (regexp ,re-ident)) "::")))
251 (match (re-search-forward symbol-then-colons limit t)))
252 (cond
253 ;; If we didn't find a match, there are no more occurrences
254 ;; of foo::, so return.
255 ((null match) (throw 'rust-path-font-lock-matcher nil))
256 ;; If this isn't a type annotation foo::<, we've found a
257 ;; match, so a return it!
258 ((not (looking-at (rx (0+ space) "<")))
259 (throw 'rust-path-font-lock-matcher match))))))))
260
261(defvar rust-font-lock-keywords
262 (append
263 `(
264 ;; Keywords proper
265 (,(regexp-opt rust-keywords 'symbols) . font-lock-keyword-face)
266
267 ;; Contextual keywords
268 ("\\_<\\(default\\)[[:space:]]+fn\\_>" 1 font-lock-keyword-face)
269 (,rust-re-union 1 font-lock-keyword-face)
270
271 ;; Special types
272 (,(regexp-opt rust-special-types 'symbols) . font-lock-type-face)
273
274 ;; The unsafe keyword
275 ("\\_<unsafe\\_>" . 'rust-unsafe)
276
277 ;; Attributes like `#[bar(baz)]` or `#![bar(baz)]` or `#[bar = "baz"]`
278 (,(rust-re-grab (concat "#\\!?\\[" rust-re-ident "[^]]*\\]"))
279 1 font-lock-preprocessor-face keep)
280
281 ;; Builtin formatting macros
282 (,(concat (rust-re-grab
283 (concat (rust-re-word (regexp-opt rust-builtin-formatting-macros))
284 "!"))
285 rust-formatting-macro-opening-re
286 "\\(?:" rust-start-of-string-re "\\)?")
287 (1 'rust-builtin-formatting-macro)
288 (rust-string-interpolation-matcher
289 (rust-end-of-string)
290 nil
291 (0 'rust-string-interpolation t nil)))
292
293 ;; write! macro
294 (,(concat (rust-re-grab (concat (rust-re-word "write\\(ln\\)?") "!"))
295 rust-formatting-macro-opening-re
296 "[[:space:]]*[^\"]+,[[:space:]]*"
297 rust-start-of-string-re)
298 (1 'rust-builtin-formatting-macro)
299 (rust-string-interpolation-matcher
300 (rust-end-of-string)
301 nil
302 (0 'rust-string-interpolation t nil)))
303
304 ;; Syntax extension invocations like `foo!`, highlight including the !
305 (,(concat (rust-re-grab (concat rust-re-ident "!")) "[({[:space:][]")
306 1 font-lock-preprocessor-face)
307
308 ;; Field names like `foo:`, highlight excluding the :
309 (,(concat (rust-re-grab rust-re-ident) "[[:space:]]*:[^:]")
310 1 font-lock-variable-name-face)
311
312 ;; CamelCase Means Type Or Constructor
313 (,rust-re-type-or-constructor 1 font-lock-type-face)
314
315 ;; Type-inferred binding
316 (,(concat "\\_<\\(?:let\\s-+ref\\|let\\|ref\\|for\\)\\s-+\\(?:mut\\s-+\\)?"
317 (rust-re-grab rust-re-ident)
318 "\\_>")
319 1 font-lock-variable-name-face)
320
321 ;; Type names like `Foo::`, highlight excluding the ::
322 (,(rust-path-font-lock-matcher rust-re-uc-ident) 1 font-lock-type-face)
323
324 ;; Module names like `foo::`, highlight excluding the ::
325 (,(rust-path-font-lock-matcher rust-re-lc-ident) 1 font-lock-constant-face)
326
327 ;; Lifetimes like `'foo`
328 (,(concat "'" (rust-re-grab rust-re-ident) "[^']") 1 font-lock-variable-name-face)
329
330 ;; Question mark operator
331 ("\\?" . 'rust-question-mark)
332 ("\\(&+\\)\\(?:'\\(?:\\<\\|_\\)\\|\\<\\|[[({:*_|]\\)"
333 1 'rust-ampersand-face)
334 ;; Numbers with type suffix
335 (,rust-number-with-type 1 font-lock-type-face)
336 )
337
338 ;; Ensure we highlight `Foo` in `struct Foo` as a type.
339 (mapcar #'(lambda (x)
340 (list (rust-re-item-def (car x))
341 1 (cdr x)))
342 '(("enum" . font-lock-type-face)
343 ("struct" . font-lock-type-face)
344 ("union" . font-lock-type-face)
345 ("type" . font-lock-type-face)
346 ("mod" . font-lock-constant-face)
347 ("use" . font-lock-constant-face)
348 ("fn" . font-lock-function-name-face)))))
349
350(defun rust-end-of-string ()
351 "Skip to the end of the current string."
352 (save-excursion
353 (skip-syntax-forward "^\"|")
354 (skip-syntax-forward "\"|")
355 (point)))
356
357(defun rust-looking-back-str (str)
358 "Return non-nil if there's a match on the text before point and STR.
359Like `looking-back' but for fixed strings rather than regexps (so
360that it's not so slow)."
361 (let ((len (length str)))
362 (and (> (point) len)
363 (equal str (buffer-substring-no-properties (- (point) len) (point))))))
364
365(defun rust-looking-back-symbols (symbols)
366 "Return non-nil if the point is after a member of SYMBOLS.
367SYMBOLS is a list of strings that represent the respective
368symbols."
369 (save-excursion
370 (let* ((pt-orig (point))
371 (beg-of-symbol (progn (forward-thing 'symbol -1) (point)))
372 (end-of-symbol (progn (forward-thing 'symbol 1) (point))))
373 (and
374 (= end-of-symbol pt-orig)
375 (member (buffer-substring-no-properties beg-of-symbol pt-orig)
376 symbols)))))
377
378(defun rust-looking-back-ident ()
379 "Non-nil if we are looking backwards at a valid rust identifier.
380If we are, regexp match 0 is the identifier."
381 (let ((outer-point (point)))
382 (save-excursion
383 (forward-thing 'symbol -1)
384 (and (looking-at rust-re-ident)
385 (eq (match-end 0) outer-point)))))
386
387(defun rust-looking-back-macro ()
388 "Non-nil if looking back at a potential macro name followed by a \"!\".
389If we are, regexp match 0 is the macro name."
390 (save-excursion
391 ;; Look past whitespace and line breaks.
392 ;; > is okay because we only use it for \n and \r, not "*/"
393 (skip-syntax-backward "->")
394 (when (eq (char-before) ?!)
395 (forward-char -1)
396 (skip-syntax-backward "->")
397 (when (rust-looking-back-ident)
398 (let ((ident (match-string 0)))
399 (not (member ident rust-expression-introducers)))))))
400
401(defun rust-looking-back-macro-rules ()
402 "Non-nil if looking back at \"macro_rules IDENT !\"."
403 (save-excursion
404 (skip-syntax-backward "->")
405 (let ((outer-point (point)))
406 (forward-thing 'symbol -2)
407 (and (looking-at (concat "macro_rules\\s-*!\\s-*" rust-re-ident))
408 (eq (match-end 0) outer-point)))))
409
410;;; Syntax definitions and helpers
411
412(defun rust-paren-level () (nth 0 (syntax-ppss)))
413(defun rust-in-str () (nth 3 (syntax-ppss)))
414(defun rust-in-str-or-cmnt () (nth 8 (syntax-ppss)))
415(defun rust-rewind-past-str-cmnt () (goto-char (nth 8 (syntax-ppss))))
416
417(defun rust-rewind-irrelevant ()
418 (let ((continue t))
419 (while continue
420 (let ((starting (point)))
421 (skip-chars-backward "[:space:]\n")
422 (when (rust-looking-back-str "*/")
423 (backward-char))
424 (when (rust-in-str-or-cmnt)
425 (rust-rewind-past-str-cmnt))
426 ;; Rewind until the point no longer moves
427 (setq continue (/= starting (point)))))))
428
429(defun rust-in-macro ()
430 "Return non-nil when point is within the scope of a macro.
431If we are, return the position of the opening bracket of the macro's arguments."
432 (let ((ppss (syntax-ppss)))
433 ;; If we're in a string or comment, we're definitely not on a token a macro
434 ;; will see.
435 (when (not (or (nth 3 ppss) (nth 4 ppss)))
436 ;; Walk outward to enclosing parens, looking for one preceded by "ident !"
437 ;; or "macro_rules! ident".
438 (let (result
439 (enclosing (reverse (nth 9 ppss))))
440 (save-excursion
441 (while enclosing
442 (goto-char (car enclosing))
443 (if (or (rust-looking-back-macro)
444 (rust-looking-back-macro-rules))
445 (setq result (point) enclosing nil)
446 (setq enclosing (cdr enclosing)))))
447 result))))
448
449(defun rust-looking-at-where ()
450 "Return T when looking at the \"where\" keyword."
451 (and (looking-at-p "\\bwhere\\b")
452 (not (rust-in-str-or-cmnt))))
453
454(defun rust-rewind-to-where (&optional limit)
455 "Rewind the point to the closest occurrence of the \"where\" keyword.
456Return T iff a where-clause was found. Does not rewind past
457LIMIT when passed, otherwise only stops at the beginning of the
458buffer."
459 (when (re-search-backward "\\bwhere\\b" limit t)
460 (if (rust-in-str-or-cmnt)
461 (rust-rewind-to-where limit)
462 t)))
463
464(defconst rust-re-pre-expression-operators "[-=!%&*/:<>[{(|.^;}]")
465
466(defconst rust-re-special-types (regexp-opt rust-special-types 'symbols))
467
468(defun rust-align-to-expr-after-brace ()
469 (save-excursion
470 (forward-char)
471 ;; We don't want to indent out to the open bracket if the
472 ;; open bracket ends the line
473 (when (not (looking-at "[[:blank:]]*\\(?://.*\\)?$"))
474 (when (looking-at "[[:space:]]")
475 (forward-word 1)
476 (backward-word 1))
477 (current-column))))
478
479(defun rust-rewind-to-beginning-of-current-level-expr ()
480 (let ((current-level (rust-paren-level)))
481 (back-to-indentation)
482 (when (looking-at "->")
483 (rust-rewind-irrelevant)
484 (back-to-indentation))
485 (while (> (rust-paren-level) current-level)
486 (backward-up-list)
487 (back-to-indentation))
488 ;; When we're in the where clause, skip over it. First find out the start
489 ;; of the function and its paren level.
490 (let ((function-start nil) (function-level nil))
491 (save-excursion
492 (rust-beginning-of-defun)
493 (back-to-indentation)
494 ;; Avoid using multiple-value-bind
495 (setq function-start (point)
496 function-level (rust-paren-level)))
497 ;; On a where clause
498 (when (or (rust-looking-at-where)
499 ;; or in one of the following lines, e.g.
500 ;; where A: Eq
501 ;; B: Hash <- on this line
502 (and (save-excursion
503 (rust-rewind-to-where function-start))
504 (= current-level function-level)))
505 (goto-char function-start)))))
506
507(defun rust-align-to-method-chain ()
508 (save-excursion
509 ;; for method-chain alignment to apply, we must be looking at
510 ;; another method call or field access or something like
511 ;; that. This avoids rather "eager" jumps in situations like:
512 ;;
513 ;; {
514 ;; something.foo()
515 ;; <indent>
516 ;;
517 ;; Without this check, we would wind up with the cursor under the
518 ;; `.`. In an older version, I had the inverse of the current
519 ;; check, where we checked for situations that should NOT indent,
520 ;; vs checking for the one situation where we SHOULD. It should be
521 ;; clear that this is more robust, but also I find it mildly less
522 ;; annoying to have to press tab again to align to a method chain
523 ;; than to have an over-eager indent in all other cases which must
524 ;; be undone via tab.
525
526 (when (looking-at (concat "\s*\." rust-re-ident))
527 (forward-line -1)
528 (end-of-line)
529 ;; Keep going up (looking for a line that could contain a method chain)
530 ;; while we're in a comment or on a blank line. Stop when the paren
531 ;; level changes.
532 (let ((level (rust-paren-level)))
533 (while (and (or (rust-in-str-or-cmnt)
534 ;; Only whitespace (or nothing) from the beginning to
535 ;; the end of the line.
536 (looking-back "^\s*" (line-beginning-position)))
537 (= (rust-paren-level) level))
538 (forward-line -1)
539 (end-of-line)))
540
541 (let
542 ;; skip-dot-identifier is used to position the point at the
543 ;; `.` when looking at something like
544 ;;
545 ;; foo.bar
546 ;; ^ ^
547 ;; | |
548 ;; | position of point
549 ;; returned offset
550 ;;
551 ((skip-dot-identifier
552 (lambda ()
553 (when (and (rust-looking-back-ident)
554 (save-excursion
555 (forward-thing 'symbol -1)
556 (= ?. (char-before))))
557 (forward-thing 'symbol -1)
558 (backward-char)
559 (- (current-column) rust-indent-offset)))))
560 (cond
561 ;; foo.bar(...)
562 ((looking-back "[)?]" (1- (point)))
563 (backward-list 1)
564 (funcall skip-dot-identifier))
565
566 ;; foo.bar
567 (t (funcall skip-dot-identifier)))))))
568
569(defun rust-mode-indent-line ()
570 (interactive)
571 (let ((indent
572 (save-excursion
573 (back-to-indentation)
574 ;; Point is now at beginning of current line
575 (let* ((level (rust-paren-level))
576 (baseline
577 ;; Our "baseline" is one level out from the
578 ;; indentation of the expression containing the
579 ;; innermost enclosing opening bracket. That way
580 ;; if we are within a block that has a different
581 ;; indentation than this mode would give it, we
582 ;; still indent the inside of it correctly relative
583 ;; to the outside.
584 (if (= 0 level)
585 0
586 (or
587 (when rust-indent-method-chain
588 (rust-align-to-method-chain))
589 (save-excursion
590 (rust-rewind-irrelevant)
591 (backward-up-list)
592 (rust-rewind-to-beginning-of-current-level-expr)
593 (+ (current-column) rust-indent-offset))))))
594 (cond
595 ;; Indent inside a non-raw string only if the previous line
596 ;; ends with a backslash that is inside the same string
597 ((nth 3 (syntax-ppss))
598 (let*
599 ((string-begin-pos (nth 8 (syntax-ppss)))
600 (end-of-prev-line-pos
601 (and (not (rust--same-line-p (point) (point-min)))
602 (line-end-position 0))))
603 (when
604 (and
605 ;; If the string begins with an "r" it's a raw string and
606 ;; we should not change the indentation
607 (/= ?r (char-after string-begin-pos))
608
609 ;; If we're on the first line this will be nil and the
610 ;; rest does not apply
611 end-of-prev-line-pos
612
613 ;; The end of the previous line needs to be inside the
614 ;; current string...
615 (> end-of-prev-line-pos string-begin-pos)
616
617 ;; ...and end with a backslash
618 (= ?\\ (char-before end-of-prev-line-pos)))
619
620 ;; Indent to the same level as the previous line, or the
621 ;; start of the string if the previous line starts the string
622 (if (rust--same-line-p end-of-prev-line-pos string-begin-pos)
623 ;; The previous line is the start of the string.
624 ;; If the backslash is the only character after the
625 ;; string beginning, indent to the next indent
626 ;; level. Otherwise align with the start of the string.
627 (if (> (- end-of-prev-line-pos string-begin-pos) 2)
628 (save-excursion
629 (goto-char (+ 1 string-begin-pos))
630 (current-column))
631 baseline)
632
633 ;; The previous line is not the start of the string, so
634 ;; match its indentation.
635 (save-excursion
636 (goto-char end-of-prev-line-pos)
637 (back-to-indentation)
638 (current-column))))))
639
640 ;; A function return type is indented to the corresponding
641 ;; function arguments, if -to-arguments is selected.
642 ((and rust-indent-return-type-to-arguments
643 (looking-at "->"))
644 (save-excursion
645 (backward-list)
646 (or (rust-align-to-expr-after-brace)
647 (+ baseline rust-indent-offset))))
648
649 ;; A closing brace is 1 level unindented
650 ((looking-at "[]})]") (- baseline rust-indent-offset))
651
652 ;; Doc comments in /** style with leading * indent to line up the *s
653 ((and (nth 4 (syntax-ppss)) (looking-at "*"))
654 (+ 1 baseline))
655
656 ;; When the user chose not to indent the start of the where
657 ;; clause, put it on the baseline.
658 ((and (not rust-indent-where-clause)
659 (rust-looking-at-where))
660 baseline)
661
662 ;; If we're in any other token-tree / sexp, then:
663 (t
664 (or
665 ;; If we are inside a pair of braces, with something after the
666 ;; open brace on the same line and ending with a comma, treat
667 ;; it as fields and align them.
668 (when (> level 0)
669 (save-excursion
670 (rust-rewind-irrelevant)
671 (backward-up-list)
672 ;; Point is now at the beginning of the containing set of braces
673 (rust-align-to-expr-after-brace)))
674
675 ;; When where-clauses are spread over multiple lines, clauses
676 ;; should be aligned on the type parameters. In this case we
677 ;; take care of the second and following clauses (the ones
678 ;; that don't start with "where ")
679 (save-excursion
680 ;; Find the start of the function, we'll use this to limit
681 ;; our search for "where ".
682 (let ((function-start nil) (function-level nil))
683 (save-excursion
684 ;; If we're already at the start of a function,
685 ;; don't go back any farther. We can easily do
686 ;; this by moving to the end of the line first.
687 (end-of-line)
688 (rust-beginning-of-defun)
689 (back-to-indentation)
690 ;; Avoid using multiple-value-bind
691 (setq function-start (point)
692 function-level (rust-paren-level)))
693 ;; When we're not on a line starting with "where ", but
694 ;; still on a where-clause line, go to "where "
695 (when (and
696 (not (rust-looking-at-where))
697 ;; We're looking at something like "F: ..."
698 (looking-at (concat rust-re-ident ":"))
699 ;; There is a "where " somewhere after the
700 ;; start of the function.
701 (rust-rewind-to-where function-start)
702 ;; Make sure we're not inside the function
703 ;; already (e.g. initializing a struct) by
704 ;; checking we are the same level.
705 (= function-level level))
706 ;; skip over "where"
707 (forward-char 5)
708 ;; Unless "where" is at the end of the line
709 (if (eolp)
710 ;; in this case the type parameters bounds are just
711 ;; indented once
712 (+ baseline rust-indent-offset)
713 ;; otherwise, skip over whitespace,
714 (skip-chars-forward "[:space:]")
715 ;; get the column of the type parameter and use that
716 ;; as indentation offset
717 (current-column)))))
718
719 (progn
720 (back-to-indentation)
721 ;; Point is now at the beginning of the current line
722 (if (or
723 ;; If this line begins with "else" or "{", stay on the
724 ;; baseline as well (we are continuing an expression,
725 ;; but the "else" or "{" should align with the beginning
726 ;; of the expression it's in.)
727 ;; Or, if this line starts a comment, stay on the
728 ;; baseline as well.
729 (looking-at "\\<else\\>\\|{\\|/[/*]")
730
731 ;; If this is the start of a top-level item,
732 ;; stay on the baseline.
733 (looking-at rust-top-item-beg-re)
734
735 (save-excursion
736 (rust-rewind-irrelevant)
737 ;; Point is now at the end of the previous line
738 (or
739 ;; If we are at the start of the buffer, no
740 ;; indentation is needed, so stay at baseline...
741 (= (point) 1)
742 ;; ..or if the previous line ends with any of these:
743 ;; { ? : ( , ; [ }
744 ;; then we are at the beginning of an
745 ;; expression, so stay on the baseline...
746 (looking-back "[(,:;[{}]\\|[^|]|" (- (point) 2))
747 ;; or if the previous line is the end of an
748 ;; attribute, stay at the baseline...
749 (progn (rust-rewind-to-beginning-of-current-level-expr)
750 (looking-at "#")))))
751 baseline
752
753 ;; Otherwise, we are continuing the same expression from
754 ;; the previous line, so add one additional indent level
755 (+ baseline rust-indent-offset))))))))))
756
757 (when indent
758 ;; If we're at the beginning of the line (before or at the current
759 ;; indentation), jump with the indentation change. Otherwise, save the
760 ;; excursion so that adding the indentations will leave us at the
761 ;; equivalent position within the line to where we were before.
762 (if (<= (current-column) (current-indentation))
763 (indent-line-to indent)
764 (save-excursion (indent-line-to indent))))))
765
766(defun rust--same-line-p (pos1 pos2)
767 "Return non-nil if POS1 and POS2 are on the same line."
768 (save-excursion (= (progn (goto-char pos1) (line-end-position))
769 (progn (goto-char pos2) (line-end-position)))))
770
771;;; Font-locking definitions and helpers
772
773(defun rust-next-string-interpolation (limit)
774 "Search forward from point for next Rust interpolation marker before LIMIT.
775Set point to the end of the occurrence found, and return match beginning
776and end."
777 (catch 'match
778 (save-match-data
779 (save-excursion
780 (while (search-forward "{" limit t)
781 (if (eql (char-after (point)) ?{)
782 (forward-char)
783 (let ((start (match-beginning 0)))
784 ;; According to fmt_macros::Parser::next, an opening brace
785 ;; must be followed by an optional argument and/or format
786 ;; specifier, then a closing brace. A single closing brace
787 ;; without a corresponding unescaped opening brace is an
788 ;; error. We don't need to do anything special with
789 ;; arguments, specifiers, or errors, so we only search for
790 ;; the single closing brace.
791 (when (search-forward "}" limit t)
792 (throw 'match (list start (point)))))))))))
793
794(defun rust-string-interpolation-matcher (limit)
795 "Match next Rust interpolation marker before LIMIT and set match data if found.
796Returns nil if not within a Rust string."
797 (when-let (((rust-in-str))
798 (match (rust-next-string-interpolation limit)))
799 (set-match-data match)
800 (goto-char (cadr match))
801 match))
802
803(defun rust-syntax-class-before-point ()
804 (when (> (point) 1)
805 (syntax-class (syntax-after (1- (point))))))
806
807(defun rust-rewind-qualified-ident ()
808 (while (rust-looking-back-ident)
809 (backward-sexp)
810 (when (save-excursion (rust-rewind-irrelevant) (rust-looking-back-str "::"))
811 (rust-rewind-irrelevant)
812 (backward-char 2)
813 (rust-rewind-irrelevant))))
814
815(defun rust-rewind-type-param-list ()
816 (cond
817 ((and (rust-looking-back-str ">") (equal 5 (rust-syntax-class-before-point)))
818 (backward-sexp)
819 (rust-rewind-irrelevant))
820
821 ;; We need to be able to back up past the Fn(args) -> RT form as well. If
822 ;; we're looking back at this, we want to end up just after "Fn".
823 ((member (char-before) '(?\] ?\) ))
824 (let ((is-paren (rust-looking-back-str ")")))
825 (when-let ((dest (save-excursion
826 (backward-sexp)
827 (rust-rewind-irrelevant)
828 (or
829 (when (rust-looking-back-str "->")
830 (backward-char 2)
831 (rust-rewind-irrelevant)
832 (when (rust-looking-back-str ")")
833 (backward-sexp)
834 (point)))
835 (and is-paren (point))))))
836 (goto-char dest))))))
837
838(defun rust-rewind-to-decl-name ()
839 "Return the point at the beginning of the name in a declaration.
840I.e. if we are before an ident that is part of a declaration that
841can have a where clause, rewind back to just before the name of
842the subject of that where clause and return the new point.
843Otherwise return nil."
844 (let* ((ident-pos (point))
845 (newpos (save-excursion
846 (rust-rewind-irrelevant)
847 (rust-rewind-type-param-list)
848 (cond
849 ((rust-looking-back-symbols
850 '("fn" "trait" "enum" "struct" "union" "impl" "type"))
851 ident-pos)
852
853 ((equal 5 (rust-syntax-class-before-point))
854 (backward-sexp)
855 (rust-rewind-to-decl-name))
856
857 ((looking-back "[:,'+=]" (1- (point)))
858 (backward-char)
859 (rust-rewind-to-decl-name))
860
861 ((rust-looking-back-str "->")
862 (backward-char 2)
863 (rust-rewind-to-decl-name))
864
865 ((rust-looking-back-ident)
866 (rust-rewind-qualified-ident)
867 (rust-rewind-to-decl-name))))))
868 (when newpos (goto-char newpos))
869 newpos))
870
871(defun rust-is-in-expression-context (token)
872 "Return t if what comes right after the point is part of an
873expression (as opposed to starting a type) by looking at what
874comes before. Takes a symbol that roughly indicates what is
875after the point.
876
877This function is used as part of `rust-is-lt-char-operator' as
878part of angle bracket matching, and is not intended to be used
879outside of this context."
880 (save-excursion
881 (let ((postchar (char-after)))
882 (rust-rewind-irrelevant)
883 ;; A type alias or ascription could have a type param list. Skip backwards past it.
884 (when (member token '(ambiguous-operator open-brace))
885 (rust-rewind-type-param-list))
886 (cond
887
888 ;; Certain keywords always introduce expressions
889 ((rust-looking-back-symbols rust-expression-introducers) t)
890
891 ;; "as" introduces a type
892 ((rust-looking-back-symbols '("as")) nil)
893
894 ;; An open angle bracket never introduces expression context WITHIN the angle brackets
895 ((and (equal token 'open-brace) (equal postchar ?<)) nil)
896
897 ;; An ident! followed by an open brace is a macro invocation. Consider
898 ;; it to be an expression.
899 ((and (equal token 'open-brace) (rust-looking-back-macro)) t)
900
901 ;; In a brace context a "]" introduces an expression.
902 ((and (eq token 'open-brace) (rust-looking-back-str "]")))
903
904 ;; An identifier is right after an ending paren, bracket, angle bracket
905 ;; or curly brace. It's a type if the last sexp was a type.
906 ((and (equal token 'ident) (equal 5 (rust-syntax-class-before-point)))
907 (backward-sexp)
908 (rust-is-in-expression-context 'open-brace))
909
910 ;; If a "for" appears without a ; or { before it, it's part of an
911 ;; "impl X for y", so the y is a type. Otherwise it's
912 ;; introducing a loop, so the y is an expression
913 ((and (equal token 'ident) (rust-looking-back-symbols '("for")))
914 (backward-sexp)
915 (rust-rewind-irrelevant)
916 (looking-back "[{;]" (1- (point))))
917
918 ((rust-looking-back-ident)
919 (rust-rewind-qualified-ident)
920 (rust-rewind-irrelevant)
921 (cond
922 ((equal token 'open-brace)
923 ;; We now know we have:
924 ;; ident <maybe type params> [{([]
925 ;; where [{([] denotes either a {, ( or [.
926 ;; This character is bound as postchar.
927 (cond
928 ;; If postchar is a paren or square bracket, then if the
929 ;; brace is a type if the identifier is one
930 ((member postchar '(?\( ?\[ )) (rust-is-in-expression-context 'ident))
931
932 ;; If postchar is a curly brace, the brace can only be a type if
933 ;; ident2 is the name of an enum, struct or trait being declared.
934 ;; Note that if there is a -> before the ident then the ident would
935 ;; be a type but the { is not.
936 ((equal ?{ postchar)
937 (not (and (rust-rewind-to-decl-name)
938 (progn
939 (rust-rewind-irrelevant)
940 (rust-looking-back-symbols
941 '("enum" "struct" "union" "trait" "type"))))))))
942
943 ((equal token 'ambiguous-operator)
944 (cond
945 ;; An ampersand after an ident has to be an operator rather
946 ;; than a & at the beginning of a ref type
947 ((equal postchar ?&) t)
948
949 ;; A : followed by a type then an = introduces an
950 ;; expression (unless it is part of a where clause of a
951 ;; "type" declaration)
952 ((and (equal postchar ?=)
953 (looking-back "[^:]:" (- (point) 2))
954 (not (save-excursion
955 (and (rust-rewind-to-decl-name)
956 (progn (rust-rewind-irrelevant)
957 (rust-looking-back-symbols '("type"))))))))
958
959 ;; "let ident =" introduces an expression--and so does "const" and "mut"
960 ((and (equal postchar ?=) (rust-looking-back-symbols '("let" "const" "mut"))) t)
961
962 ;; As a specific special case, see if this is the = in this situation:
963 ;; enum EnumName<type params> { Ident =
964 ;; In this case, this is a c-like enum and despite Ident
965 ;; representing a type, what comes after the = is an expression
966 ((and
967 (> (rust-paren-level) 0)
968 (save-excursion
969 (backward-up-list)
970 (rust-rewind-irrelevant)
971 (rust-rewind-type-param-list)
972 (and
973 (rust-looking-back-ident)
974 (progn
975 (rust-rewind-qualified-ident)
976 (rust-rewind-irrelevant)
977 (rust-looking-back-str "enum")))))
978 t)
979
980 ;; Otherwise the ambiguous operator is a type if the identifier is a type
981 ((rust-is-in-expression-context 'ident) t)))
982
983 ((equal token 'colon)
984 (cond
985 ;; If we see a ident: not inside any braces/parens, we're at top level.
986 ;; There are no allowed expressions after colons there, just types.
987 ((<= (rust-paren-level) 0) nil)
988
989 ;; We see ident: inside a list
990 ((looking-back "[{,]" (1- (point)))
991 (backward-up-list)
992
993 ;; If a : appears whose surrounding paren/brackets/braces are
994 ;; anything other than curly braces, it can't be a field
995 ;; initializer and must be denoting a type.
996 (when (looking-at "{")
997 (rust-rewind-irrelevant)
998 (rust-rewind-type-param-list)
999 (when (rust-looking-back-ident)
1000 ;; We have a context that looks like this:
1001 ;; ident2 <maybe type params> { [maybe paren-balanced code ending in comma] ident1:
1002 ;; the point is sitting just after ident2, and we trying to
1003 ;; figure out if the colon introduces an expression or a type.
1004 ;; The answer is that ident1 is a field name, and what comes
1005 ;; after the colon is an expression, if ident2 is an
1006 ;; expression.
1007 (rust-rewind-qualified-ident)
1008 (rust-is-in-expression-context 'ident))))
1009
1010 ;; Otherwise, if the ident: appeared with anything other than , or {
1011 ;; before it, it can't be part of a struct initializer and therefore
1012 ;; must be denoting a type.
1013 (t nil)))))
1014
1015 ;; An operator-like character after a string is indeed an operator
1016 ((and (equal token 'ambiguous-operator)
1017 (member (rust-syntax-class-before-point) '(5 7 15))) t)
1018
1019 ;; A colon that has something other than an identifier before it is a
1020 ;; type ascription
1021 ((equal token 'colon) nil)
1022
1023 ;; A :: introduces a type (or module, but not an expression in any case)
1024 ((rust-looking-back-str "::") nil)
1025
1026 ((rust-looking-back-str ":")
1027 (backward-char)
1028 (rust-is-in-expression-context 'colon))
1029
1030 ;; A -> introduces a type
1031 ((rust-looking-back-str "->") nil)
1032
1033 ;; If we are up against the beginning of a list, or after a comma inside
1034 ;; of one, back up out of it and check what the list itself is
1035 ((or
1036 (equal 4 (rust-syntax-class-before-point))
1037 (rust-looking-back-str ","))
1038 (condition-case nil
1039 (progn
1040 (backward-up-list)
1041 (rust-is-in-expression-context 'open-brace))
1042 (scan-error nil)))
1043
1044 ;; A => introduces an expression
1045 ((rust-looking-back-str "=>") t)
1046
1047 ;; A == introduces an expression
1048 ((rust-looking-back-str "==") t)
1049
1050 ;; These operators can introduce expressions or types
1051 ((looking-back "[-+=!?&*]" (1- (point)))
1052 (backward-char)
1053 (rust-is-in-expression-context 'ambiguous-operator))
1054
1055 ;; These operators always introduce expressions. (Note that if this
1056 ;; regexp finds a < it must not be an angle bracket, or it'd
1057 ;; have been caught in the syntax-class check above instead of this.)
1058 ((looking-back rust-re-pre-expression-operators (1- (point))) t)))))
1059
1060(defun rust-is-lt-char-operator ()
1061 "Return non-nil if the `<' sign just after point is an operator.
1062Otherwise, if it is an opening angle bracket, then return nil."
1063 (let ((case-fold-search nil))
1064 (save-excursion
1065 (rust-rewind-irrelevant)
1066 ;; We are now just after the character syntactically before the <.
1067 (cond
1068
1069 ;; If we are looking back at a < that is not an angle bracket (but not
1070 ;; two of them) then this is the second < in a bit shift operator
1071 ((and (rust-looking-back-str "<")
1072 (not (equal 4 (rust-syntax-class-before-point)))
1073 (not (rust-looking-back-str "<<"))))
1074
1075 ;; On the other hand, if we are after a closing paren/brace/bracket it
1076 ;; can only be an operator, not an angle bracket. Likewise, if we are
1077 ;; after a string it's an operator. (The string case could actually be
1078 ;; valid in rust for character literals.)
1079 ((member (rust-syntax-class-before-point) '(5 7 15)) t)
1080
1081 ;; If we are looking back at an operator, we know that we are at
1082 ;; the beginning of an expression, and thus it has to be an angle
1083 ;; bracket (starting a "<Type as Trait>::" construct.)
1084 ((looking-back rust-re-pre-expression-operators (1- (point))) nil)
1085
1086 ;; If we are looking back at a keyword, it's an angle bracket
1087 ;; unless that keyword is "self", "true" or "false"
1088 ((rust-looking-back-symbols rust-keywords)
1089 (rust-looking-back-symbols '("self" "true" "false")))
1090
1091 ((rust-looking-back-str "?")
1092 (rust-is-in-expression-context 'ambiguous-operator))
1093
1094 ;; If we're looking back at an identifier, this depends on whether
1095 ;; the identifier is part of an expression or a type
1096 ((rust-looking-back-ident)
1097 (backward-sexp)
1098 (or
1099 ;; The special types can't take type param lists, so a < after one is
1100 ;; always an operator
1101 (looking-at rust-re-special-types)
1102
1103 (rust-is-in-expression-context 'ident)))
1104
1105 ;; Otherwise, assume it's an angle bracket
1106 ))))
1107
1108(defun rust-electric-pair-inhibit-predicate-wrap (char)
1109 "Prevent \"matching\" with a `>' when CHAR is the less-than operator.
1110This wraps the default defined by `electric-pair-inhibit-predicate'."
1111 (or
1112 (when (= ?< char)
1113 (save-excursion
1114 (backward-char)
1115 (rust-is-lt-char-operator)))
1116 (funcall (default-value 'electric-pair-inhibit-predicate) char)))
1117
1118(defun rust-electric-pair-skip-self (char)
1119 "Skip CHAR instead of inserting a second closing character.
1120This is added to the default skips defined by `electric-pair-skip-self'."
1121 (= ?> char))
1122
1123(defun rust-ordinary-lt-gt-p ()
1124 "Test whether the `<' or `>' at point is an ordinary operator of some kind.
1125
1126This returns t if the `<' or `>' is an ordinary operator (like
1127less-than) or part of one (like `->'); and nil if the character
1128should be considered a paired angle bracket."
1129 (cond
1130 ;; If matching is turned off suppress all of them
1131 ((not rust-match-angle-brackets) t)
1132
1133 ;; This is a cheap check so we do it early.
1134 ;; Don't treat the > in -> or => as an angle bracket
1135 ((and (= (following-char) ?>) (memq (preceding-char) '(?- ?=))) t)
1136
1137 ;; We don't take < or > in strings or comments to be angle brackets
1138 ((rust-in-str-or-cmnt) t)
1139
1140 ;; Inside a macro we don't really know the syntax. Any < or > may be an
1141 ;; angle bracket or it may not. But we know that the other braces have
1142 ;; to balance regardless of the < and >, so if we don't treat any < or >
1143 ;; as angle brackets it won't mess up any paren balancing.
1144 ((rust-in-macro) t)
1145
1146 ((= (following-char) ?<)
1147 (rust-is-lt-char-operator))
1148
1149 ;; Since rust-ordinary-lt-gt-p is called only when either < or > are at the point,
1150 ;; we know that the following char must be > in the clauses below.
1151
1152 ;; If we are at top level and not in any list, it can't be a closing
1153 ;; angle bracket
1154 ((>= 0 (rust-paren-level)) t)
1155
1156 ;; Otherwise, treat the > as a closing angle bracket if it would
1157 ;; match an opening one
1158 ((save-excursion
1159 (backward-up-list)
1160 (/= (following-char) ?<)))))
1161
1162(defun rust-mode-syntactic-face-function (state)
1163 "Return face that distinguishes doc and normal comments in given syntax STATE."
1164 (if (nth 3 state)
1165 'font-lock-string-face
1166 (save-excursion
1167 (goto-char (nth 8 state))
1168 (if (looking-at "/\\([*][*!][^*!]\\|/[/!][^/!]\\)")
1169 'font-lock-doc-face
1170 'font-lock-comment-face))))
1171
1172(eval-and-compile
1173 (defconst rust--char-literal-rx
1174 (rx (seq
1175 (group "'")
1176 (or
1177 (seq
1178 "\\"
1179 (or
1180 (: "u{" (** 1 6 xdigit) "}")
1181 (: "x" (= 2 xdigit))
1182 (any "'nrt0\"\\")))
1183 (not (any "'\\")))
1184 (group "'")))
1185 "A regular expression matching a character literal."))
1186
1187(defun rust-fill-prefix-for-comment-start (line-start)
1188 "Determine what to use for `fill-prefix' based on the text at LINE-START."
1189 (let ((result
1190 ;; Replace /* with same number of spaces
1191 (replace-regexp-in-string
1192 "\\(?:/\\*+?\\)[!*]?"
1193 (lambda (s)
1194 ;; We want the * to line up with the first * of the
1195 ;; comment start
1196 (let ((offset (if (eq t
1197 (compare-strings "/*" nil nil
1198 s
1199 (- (length s) 2)
1200 (length s)))
1201 1 2)))
1202 (concat (make-string (- (length s) offset)
1203 ?\x20) "*")))
1204 line-start)))
1205 ;; Make sure we've got at least one space at the end
1206 (if (not (= (aref result (- (length result) 1)) ?\x20))
1207 (setq result (concat result " ")))
1208 result))
1209
1210(defun rust-in-comment-paragraph (body)
1211 ;; We might move the point to fill the next comment, but we don't want it
1212 ;; seeming to jump around on the user
1213 (save-excursion
1214 ;; If we're outside of a comment, with only whitespace and then a comment
1215 ;; in front, jump to the comment and prepare to fill it.
1216 (when (not (nth 4 (syntax-ppss)))
1217 (beginning-of-line)
1218 (when (looking-at (concat "[[:space:]\n]*" comment-start-skip))
1219 (goto-char (match-end 0))))
1220
1221 ;; We need this when we're moving the point around and then checking syntax
1222 ;; while doing paragraph fills, because the cache it uses isn't always
1223 ;; invalidated during this.
1224 (syntax-ppss-flush-cache 1)
1225 ;; If we're at the beginning of a comment paragraph with nothing but
1226 ;; whitespace til the next line, jump to the next line so that we use the
1227 ;; existing prefix to figure out what the new prefix should be, rather than
1228 ;; inferring it from the comment start.
1229 (let ((next-bol (line-beginning-position 2)))
1230 (while (save-excursion
1231 (end-of-line)
1232 (syntax-ppss-flush-cache 1)
1233 (and (nth 4 (syntax-ppss))
1234 (save-excursion
1235 (beginning-of-line)
1236 (looking-at paragraph-start))
1237 (looking-at "[[:space:]]*$")
1238 (nth 4 (syntax-ppss next-bol))))
1239 (goto-char next-bol)))
1240
1241 (syntax-ppss-flush-cache 1)
1242 ;; If we're on the last line of a multiline-style comment that started
1243 ;; above, back up one line so we don't mistake the * of the */ that ends
1244 ;; the comment for a prefix.
1245 (when (save-excursion
1246 (and (nth 4 (syntax-ppss (line-beginning-position 1)))
1247 (looking-at "[[:space:]]*\\*/")))
1248 (goto-char (line-end-position 0)))
1249 (funcall body)))
1250
1251(defun rust-with-comment-fill-prefix (body)
1252 (let*
1253 ((line-string (buffer-substring-no-properties
1254 (line-beginning-position) (line-end-position)))
1255 (line-comment-start
1256 (when (nth 4 (syntax-ppss))
1257 (cond
1258 ;; If we're inside the comment and see a * prefix, use it
1259 ((string-match "^\\([[:space:]]*\\*+[[:space:]]*\\)"
1260 line-string)
1261 (match-string 1 line-string))
1262 ;; If we're at the start of a comment, figure out what prefix
1263 ;; to use for the subsequent lines after it
1264 ((string-match (concat "[[:space:]]*" comment-start-skip) line-string)
1265 (rust-fill-prefix-for-comment-start
1266 (match-string 0 line-string))))))
1267 (fill-prefix
1268 (or line-comment-start
1269 fill-prefix)))
1270 (funcall body)))
1271
1272(defun rust-find-fill-prefix ()
1273 (rust-in-comment-paragraph
1274 (lambda ()
1275 (rust-with-comment-fill-prefix
1276 (lambda ()
1277 fill-prefix)))))
1278
1279(defun rust-fill-paragraph (&rest args)
1280 "Special wrapping for `fill-paragraph'.
1281This handles multi-line comments with a * prefix on each line."
1282 (rust-in-comment-paragraph
1283 (lambda ()
1284 (rust-with-comment-fill-prefix
1285 (lambda ()
1286 (let
1287 ((fill-paragraph-function
1288 (if (not (eq fill-paragraph-function #'rust-fill-paragraph))
1289 fill-paragraph-function))
1290 (fill-paragraph-handle-comment t))
1291 (apply #'fill-paragraph args)
1292 t))))))
1293
1294(defun rust-do-auto-fill (&rest args)
1295 "Special wrapping for `do-auto-fill'.
1296This handles multi-line comments with a * prefix on each line."
1297 (rust-with-comment-fill-prefix
1298 (lambda ()
1299 (apply #'do-auto-fill args)
1300 t)))
1301
1302(defun rust-fill-forward-paragraph (arg)
1303 ;; This is to work around some funny behavior when a paragraph separator is
1304 ;; at the very top of the file and there is a fill prefix.
1305 (let ((fill-prefix nil)) (forward-paragraph arg)))
1306
1307(defun rust-comment-indent-new-line (&optional arg)
1308 (rust-with-comment-fill-prefix
1309 (lambda () (comment-indent-new-line arg))))
1310
1311;;; Defun Motions
1312
1313(defun rust-beginning-of-defun (&optional arg)
1314 "Move backward to the beginning of the current defun.
1315
1316With ARG, move backward multiple defuns. Negative ARG means
1317move forward.
1318
1319This is written mainly to be used as `beginning-of-defun-function' for Rust.
1320Don't move to the beginning of the line. `beginning-of-defun',
1321which calls this, does that afterwards."
1322 (interactive "p")
1323 (let* ((arg (or arg 1))
1324 (magnitude (abs arg))
1325 (sign (if (< arg 0) -1 1)))
1326 ;; If moving forward, don't find the defun we might currently be
1327 ;; on.
1328 (when (< sign 0)
1329 (end-of-line))
1330 (catch 'done
1331 (dotimes (_ magnitude)
1332 ;; Search until we find a match that is not in a string or comment.
1333 (while (if (re-search-backward (concat "^\\(" rust-top-item-beg-re "\\)")
1334 nil 'move sign)
1335 (rust-in-str-or-cmnt)
1336 ;; Did not find it.
1337 (throw 'done nil)))))
1338 t))
1339
1340(defun rust-end-of-defun ()
1341 "Move forward to the next end of defun.
1342
1343With argument, do it that many times.
1344Negative argument -N means move back to Nth preceding end of defun.
1345
1346Assume that this is called after `beginning-of-defun'. So point is
1347at the beginning of the defun body.
1348
1349This is written mainly to be used as `end-of-defun-function' for Rust."
1350 (interactive)
1351 ;; Find the opening brace
1352 (if (re-search-forward "[{]" nil t)
1353 (progn
1354 (goto-char (match-beginning 0))
1355 ;; Go to the closing brace
1356 (condition-case nil
1357 (forward-sexp)
1358 (scan-error
1359 ;; The parentheses are unbalanced; instead of being unable
1360 ;; to fontify, just jump to the end of the buffer
1361 (goto-char (point-max)))))
1362 ;; There is no opening brace, so consider the whole buffer to be one "defun"
1363 (goto-char (point-max))))
1364
1365;;; _
1366
1367(defun rust-mode-reload ()
1368 (interactive)
1369 (unload-feature 'rust-mode)
1370 (require 'rust-mode)
1371 (rust-mode))
1372
1373(defvar rust-mode-syntax-table
1374 (let ((table (make-syntax-table)))
1375
1376 ;; Operators
1377 (dolist (i '(?+ ?- ?* ?/ ?% ?& ?| ?^ ?! ?< ?> ?~ ?@))
1378 (modify-syntax-entry i "." table))
1379
1380 ;; Strings
1381 (modify-syntax-entry ?\" "\"" table)
1382 (modify-syntax-entry ?\\ "\\" table)
1383
1384 ;; Angle brackets. We suppress this with syntactic propertization
1385 ;; when needed
1386 (modify-syntax-entry ?< "(>" table)
1387 (modify-syntax-entry ?> ")<" table)
1388
1389 ;; Comments
1390 (modify-syntax-entry ?/ ". 124b" table)
1391 (modify-syntax-entry ?* ". 23n" table)
1392 (modify-syntax-entry ?\n "> b" table)
1393 (modify-syntax-entry ?\^m "> b" table)
1394
1395 table)
1396 "Syntax definitions and helpers.")
1397
1398(defun rust--syntax-propertize-raw-string (str-start end)
1399 "A helper for rust-syntax-propertize.
1400
1401This will apply the appropriate string syntax to the character
1402from the STR-START up to the end of the raw string, or to END,
1403whichever comes first."
1404 (when (save-excursion
1405 (goto-char str-start)
1406 (looking-at "r\\(#*\\)\\(\"\\)"))
1407 ;; In a raw string, so try to find the end.
1408 (let ((hashes (match-string 1)))
1409 ;; Match \ characters at the end of the string to suppress
1410 ;; their normal character-quote syntax.
1411 (when (re-search-forward (concat "\\(\\\\*\\)\\(\"" hashes "\\)") end t)
1412 (put-text-property (match-beginning 1) (match-end 1)
1413 'syntax-table (string-to-syntax "_"))
1414 (put-text-property (1- (match-end 2)) (match-end 2)
1415 'syntax-table (string-to-syntax "|"))
1416 (goto-char (match-end 0))))))
1417
1418;;; Syntax Propertize
1419
1420(defun rust-syntax-propertize (start end)
1421 "A `syntax-propertize-function' to apply properties from START to END."
1422 (goto-char start)
1423 (when-let ((str-start (rust-in-str-or-cmnt)))
1424 (rust--syntax-propertize-raw-string str-start end))
1425 (funcall
1426 (syntax-propertize-rules
1427 ;; Character literals.
1428 (rust--char-literal-rx (1 "\"") (2 "\""))
1429 ;; Raw strings.
1430 ("\\(r\\)#*\""
1431 (0 (ignore
1432 (goto-char (match-end 0))
1433 (unless (save-excursion (nth 8 (syntax-ppss (match-beginning 0))))
1434 (put-text-property (match-beginning 1) (match-end 1)
1435 'syntax-table (string-to-syntax "|"))
1436 (rust--syntax-propertize-raw-string (match-beginning 0) end)))))
1437 ("[<>]"
1438 (0 (ignore
1439 (when (save-match-data
1440 (save-excursion
1441 (goto-char (match-beginning 0))
1442 (rust-ordinary-lt-gt-p)))
1443 (put-text-property (match-beginning 0) (match-end 0)
1444 'syntax-table (string-to-syntax "."))
1445 (goto-char (match-end 0)))))))
1446 (point) end))
1447
1448(define-derived-mode rust-mode prog-mode "Rust"
1449 "Major mode for Rust code.
1450
1451\\{rust-mode-map}"
1452 :group 'rust-mode
1453 :syntax-table rust-mode-syntax-table
1454
1455 ;; Syntax
1456 (setq-local syntax-propertize-function #'rust-syntax-propertize)
1457
1458 ;; Indentation
1459 (setq-local indent-line-function 'rust-mode-indent-line)
1460
1461 ;; Fonts
1462 (setq-local font-lock-defaults
1463 '(rust-font-lock-keywords
1464 nil nil nil nil
1465 (font-lock-syntactic-face-function
1466 . rust-mode-syntactic-face-function)))
1467
1468 ;; Misc
1469 (setq-local comment-start "// ")
1470 (setq-local comment-end "")
1471 (setq-local open-paren-in-column-0-is-defun-start nil)
1472
1473 ;; Auto indent on }
1474 (setq-local electric-indent-chars
1475 (cons ?} (and (boundp 'electric-indent-chars)
1476 electric-indent-chars)))
1477
1478 ;; Allow paragraph fills for comments
1479 (setq-local comment-start-skip "\\(?://[/!]*\\|/\\*[*!]?\\)[[:space:]]*")
1480 (setq-local paragraph-start
1481 (concat "[[:space:]]*\\(?:"
1482 comment-start-skip
1483 "\\|\\*/?[[:space:]]*\\|\\)$"))
1484 (setq-local paragraph-separate paragraph-start)
1485 (setq-local normal-auto-fill-function #'rust-do-auto-fill)
1486 (setq-local fill-paragraph-function #'rust-fill-paragraph)
1487 (setq-local fill-forward-paragraph-function #'rust-fill-forward-paragraph)
1488 (setq-local adaptive-fill-function #'rust-find-fill-prefix)
1489 (setq-local adaptive-fill-first-line-regexp "")
1490 (setq-local comment-multi-line t)
1491 (setq-local comment-line-break-function #'rust-comment-indent-new-line)
1492 (setq-local imenu-generic-expression rust-imenu-generic-expression)
1493 (setq-local imenu-syntax-alist '((?! . "w"))) ; For macro_rules!
1494 (setq-local beginning-of-defun-function #'rust-beginning-of-defun)
1495 (setq-local end-of-defun-function #'rust-end-of-defun)
1496 (setq-local parse-sexp-lookup-properties t)
1497 (setq-local electric-pair-inhibit-predicate
1498 #'rust-electric-pair-inhibit-predicate-wrap)
1499 (add-function :before-until (local 'electric-pair-skip-self)
1500 #'rust-electric-pair-skip-self)
1501 ;; Configure prettify
1502 (setq prettify-symbols-alist rust-prettify-symbols-alist)
1503 (setq prettify-symbols-compose-predicate #'rust--prettify-symbols-compose-p)
1504
1505 (add-hook 'before-save-hook rust-before-save-hook nil t)
1506 (add-hook 'after-save-hook rust-after-save-hook nil t))
1507
1508(provide 'rust-prog-mode)
1509;;; rust-prog-mode.el ends here