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