1 ;;; rust-mode.el --- A major emacs mode for editing Rust source code
5 ;; Url: https://github.com/rust-lang/rust
13 (eval-when-compile (require 'misc))
15 ;; for GNU Emacs < 24.3
17 (unless (fboundp 'setq-local)
18 (defmacro setq-local (var val)
19 "Set variable VAR to value VAL in current buffer."
20 (list 'set (list 'make-local-variable (list 'quote var)) val))))
22 ;; Syntax definitions and helpers
23 (defvar rust-mode-syntax-table
24 (let ((table (make-syntax-table)))
27 (dolist (i '(?+ ?- ?* ?/ ?& ?| ?^ ?! ?< ?> ?~ ?@))
28 (modify-syntax-entry i "." table))
31 (modify-syntax-entry ?\" "\"" table)
32 (modify-syntax-entry ?\\ "\\" table)
34 ;; mark _ as a word constituent so that identifiers
35 ;; such as xyz_type don't cause type to be highlighted
37 (modify-syntax-entry ?_ "w" table)
40 (modify-syntax-entry ?/ ". 124b" table)
41 (modify-syntax-entry ?* ". 23" table)
42 (modify-syntax-entry ?\n "> b" table)
43 (modify-syntax-entry ?\^m "> b" table)
47 (defgroup rust-mode nil
48 "Support for Rust code."
49 :link '(url-link "http://www.rust-lang.org/")
52 (defcustom rust-indent-offset 4
53 "Indent Rust code by this number of spaces."
57 (defun rust-paren-level () (nth 0 (syntax-ppss)))
58 (defun rust-in-str-or-cmnt () (nth 8 (syntax-ppss)))
59 (defun rust-rewind-past-str-cmnt () (goto-char (nth 8 (syntax-ppss))))
60 (defun rust-rewind-irrelevant ()
61 (let ((starting (point)))
62 (skip-chars-backward "[:space:]\n")
63 (if (looking-back "\\*/") (backward-char))
64 (if (rust-in-str-or-cmnt)
65 (rust-rewind-past-str-cmnt))
66 (if (/= starting (point))
67 (rust-rewind-irrelevant))))
69 (defun rust-align-to-expr-after-brace ()
72 ;; We don't want to indent out to the open bracket if the
73 ;; open bracket ends the line
74 (when (not (looking-at "[[:blank:]]*\\(?://.*\\)?$"))
75 (when (looking-at "[[:space:]]")
80 (defun rust-rewind-to-beginning-of-current-level-expr ()
81 (let ((current-level (rust-paren-level)))
83 (while (> (rust-paren-level) current-level)
85 (back-to-indentation))))
87 (defun rust-mode-indent-line ()
92 ;; Point is now at beginning of current line
93 (let* ((level (rust-paren-level))
95 ;; Our "baseline" is one level out from the indentation of the expression
96 ;; containing the innermost enclosing opening bracket. That
97 ;; way if we are within a block that has a different
98 ;; indentation than this mode would give it, we still indent
99 ;; the inside of it correctly relative to the outside.
104 (rust-rewind-to-beginning-of-current-level-expr)
105 (+ (current-column) rust-indent-offset)))))
107 ;; A function return type is indented to the corresponding function arguments
111 (or (rust-align-to-expr-after-brace)
112 (+ baseline rust-indent-offset))))
114 ;; A closing brace is 1 level unindended
115 ((looking-at "}") (- baseline rust-indent-offset))
117 ;; Doc comments in /** style with leading * indent to line up the *s
118 ((and (nth 4 (syntax-ppss)) (looking-at "*"))
121 ;; If we're in any other token-tree / sexp, then:
124 ;; If we are inside a pair of braces, with something after the
125 ;; open brace on the same line and ending with a comma, treat
126 ;; it as fields and align them.
129 (rust-rewind-irrelevant)
131 ;; Point is now at the beginning of the containing set of braces
132 (rust-align-to-expr-after-brace)))
135 (back-to-indentation)
136 ;; Point is now at the beginning of the current line
138 ;; If this line begins with "else" or "{", stay on the
139 ;; baseline as well (we are continuing an expression,
140 ;; but the "else" or "{" should align with the beginning
141 ;; of the expression it's in.)
142 (looking-at "\\<else\\>\\|{")
145 (rust-rewind-irrelevant)
146 ;; Point is now at the end of the previous ine
148 ;; If we are at the first line, no indentation is needed, so stay at baseline...
149 (= 1 (line-number-at-pos (point)))
150 ;; ..or if the previous line ends with any of these:
152 ;; then we are at the beginning of an expression, so stay on the baseline...
153 (looking-back "[(,:;?[{}]\\|[^|]|")
154 ;; or if the previous line is the end of an attribute, stay at the baseline...
155 (progn (rust-rewind-to-beginning-of-current-level-expr) (looking-at "#")))))
158 ;; Otherwise, we are continuing the same expression from the previous line,
159 ;; so add one additional indent level
160 (+ baseline rust-indent-offset))))))))))
162 ;; If we're at the beginning of the line (before or at the current
163 ;; indentation), jump with the indentation change. Otherwise, save the
164 ;; excursion so that adding the indentations will leave us at the
165 ;; equivalent position within the line to where we were before.
166 (if (<= (current-column) (current-indentation))
167 (indent-line-to indent)
168 (save-excursion (indent-line-to indent)))))
171 ;; Font-locking definitions and helpers
172 (defconst rust-mode-keywords
175 "const" "continue" "crate"
177 "else" "enum" "extern"
181 "match" "mod" "move" "mut"
184 "self" "static" "struct" "super"
185 "true" "trait" "type"
190 (defconst rust-special-types
197 "float" "int" "uint" "isize" "usize"
201 (defconst rust-re-ident "[[:word:][:multibyte:]_][[:word:][:multibyte:]_[:digit:]]*")
202 (defconst rust-re-CamelCase "[[:upper:]][[:word:][:multibyte:]_[:digit:]]*")
203 (defun rust-re-word (inner) (concat "\\<" inner "\\>"))
204 (defun rust-re-grab (inner) (concat "\\(" inner "\\)"))
205 (defun rust-re-grabword (inner) (rust-re-grab (rust-re-word inner)))
206 (defun rust-re-item-def (itype)
207 (concat (rust-re-word itype) "[[:space:]]+" (rust-re-grab rust-re-ident)))
209 (defvar rust-mode-font-lock-keywords
213 (,(regexp-opt rust-mode-keywords 'words) . font-lock-keyword-face)
216 (,(regexp-opt rust-special-types 'words) . font-lock-type-face)
218 ;; Attributes like `#[bar(baz)]` or `#![bar(baz)]` or `#[bar = "baz"]`
219 (,(rust-re-grab (concat "#\\!?\\[" rust-re-ident "[^]]*\\]"))
220 1 font-lock-preprocessor-face keep)
222 ;; Syntax extension invocations like `foo!`, highlight including the !
223 (,(concat (rust-re-grab (concat rust-re-ident "!")) "[({[:space:][]")
224 1 font-lock-preprocessor-face)
226 ;; Field names like `foo:`, highlight excluding the :
227 (,(concat (rust-re-grab rust-re-ident) ":[^:]") 1 font-lock-variable-name-face)
229 ;; Module names like `foo::`, highlight including the ::
230 (,(rust-re-grab (concat rust-re-ident "::")) 1 font-lock-type-face)
232 ;; Lifetimes like `'foo`
233 (,(concat "'" (rust-re-grab rust-re-ident) "[^']") 1 font-lock-variable-name-face)
235 ;; Character constants, since they're not treated as strings
236 ;; in order to have sufficient leeway to parse 'lifetime above.
237 (,(rust-re-grab "'[^']'") 1 font-lock-string-face)
238 (,(rust-re-grab "'\\\\[nrt]'") 1 font-lock-string-face)
239 (,(rust-re-grab "'\\\\x[[:xdigit:]]\\{2\\}'") 1 font-lock-string-face)
240 (,(rust-re-grab "'\\\\u[[:xdigit:]]\\{4\\}'") 1 font-lock-string-face)
241 (,(rust-re-grab "'\\\\U[[:xdigit:]]\\{8\\}'") 1 font-lock-string-face)
243 ;; CamelCase Means Type Or Constructor
244 (,(rust-re-grabword rust-re-CamelCase) 1 font-lock-type-face)
248 (mapcar #'(lambda (x)
249 (list (rust-re-item-def (car x))
251 '(("enum" . font-lock-type-face)
252 ("struct" . font-lock-type-face)
253 ("type" . font-lock-type-face)
254 ("mod" . font-lock-type-face)
255 ("use" . font-lock-type-face)
256 ("fn" . font-lock-function-name-face)
257 ("static" . font-lock-constant-face)))))
259 (defun rust-fill-prefix-for-comment-start (line-start)
260 "Determine what to use for `fill-prefix' based on what is at the beginning of a line."
262 ;; Replace /* with same number of spaces
263 (replace-regexp-in-string
266 ;; We want the * to line up with the first * of the comment start
267 (concat (make-string (- (length s) 2) ?\x20) "*"))
269 ;; Make sure we've got at least one space at the end
270 (if (not (= (aref result (- (length result) 1)) ?\x20))
271 (setq result (concat result " ")))
274 (defun rust-in-comment-paragraph (body)
275 ;; We might move the point to fill the next comment, but we don't want it
276 ;; seeming to jump around on the user
278 ;; If we're outside of a comment, with only whitespace and then a comment
279 ;; in front, jump to the comment and prepare to fill it.
280 (when (not (nth 4 (syntax-ppss)))
282 (when (looking-at (concat "[[:space:]\n]*" comment-start-skip))
283 (goto-char (match-end 0))))
285 ;; We need this when we're moving the point around and then checking syntax
286 ;; while doing paragraph fills, because the cache it uses isn't always
287 ;; invalidated during this.
288 (syntax-ppss-flush-cache 1)
289 ;; If we're at the beginning of a comment paragraph with nothing but
290 ;; whitespace til the next line, jump to the next line so that we use the
291 ;; existing prefix to figure out what the new prefix should be, rather than
292 ;; inferring it from the comment start.
293 (let ((next-bol (line-beginning-position 2)))
294 (while (save-excursion
296 (syntax-ppss-flush-cache 1)
297 (and (nth 4 (syntax-ppss))
300 (looking-at paragraph-start))
301 (looking-at "[[:space:]]*$")
302 (nth 4 (syntax-ppss next-bol))))
303 (goto-char next-bol)))
305 (syntax-ppss-flush-cache 1)
306 ;; If we're on the last line of a multiline-style comment that started
307 ;; above, back up one line so we don't mistake the * of the */ that ends
308 ;; the comment for a prefix.
309 (when (save-excursion
310 (and (nth 4 (syntax-ppss (line-beginning-position 1)))
311 (looking-at "[[:space:]]*\\*/")))
312 (goto-char (line-end-position 0)))
315 (defun rust-with-comment-fill-prefix (body)
317 ((line-string (buffer-substring-no-properties
318 (line-beginning-position) (line-end-position)))
320 (when (nth 4 (syntax-ppss))
322 ;; If we're inside the comment and see a * prefix, use it
323 ((string-match "^\\([[:space:]]*\\*+[[:space:]]*\\)"
325 (match-string 1 line-string))
326 ;; If we're at the start of a comment, figure out what prefix
327 ;; to use for the subsequent lines after it
328 ((string-match (concat "[[:space:]]*" comment-start-skip) line-string)
329 (rust-fill-prefix-for-comment-start
330 (match-string 0 line-string))))))
332 (or line-comment-start
336 (defun rust-find-fill-prefix ()
337 (rust-with-comment-fill-prefix (lambda () fill-prefix)))
339 (defun rust-fill-paragraph (&rest args)
340 "Special wrapping for `fill-paragraph' to handle multi-line comments with a * prefix on each line."
341 (rust-in-comment-paragraph
343 (rust-with-comment-fill-prefix
346 ((fill-paragraph-function
347 (if (not (eq fill-paragraph-function 'rust-fill-paragraph))
348 fill-paragraph-function))
349 (fill-paragraph-handle-comment t))
350 (apply 'fill-paragraph args)
353 (defun rust-do-auto-fill (&rest args)
354 "Special wrapping for `do-auto-fill' to handle multi-line comments with a * prefix on each line."
355 (rust-with-comment-fill-prefix
357 (apply 'do-auto-fill args)
360 (defun rust-fill-forward-paragraph (arg)
361 ;; This is to work around some funny behavior when a paragraph separator is
362 ;; at the very top of the file and there is a fill prefix.
363 (let ((fill-prefix nil)) (forward-paragraph arg)))
365 (defun rust-comment-indent-new-line (&optional arg)
366 (rust-with-comment-fill-prefix
367 (lambda () (comment-indent-new-line arg))))
370 (defvar rust-imenu-generic-expression
371 (append (mapcar #'(lambda (x)
372 (list nil (rust-re-item-def x) 1))
373 '("enum" "struct" "type" "mod" "fn" "trait"))
374 `(("Impl" ,(rust-re-item-def "impl") 1)))
375 "Value for `imenu-generic-expression' in Rust mode.
377 Create a flat index of the item definitions in a Rust file.
379 Imenu will show all the enums, structs, etc. at the same level.
380 Implementations will be shown under the `Impl` subheading. Use
381 idomenu (imenu with `ido-mode') for best mileage.")
385 ;;; Start of a Rust item
386 (defvar rust-top-item-beg-re
387 (concat "^\\s-*\\(?:priv\\|pub\\)?\\s-*"
389 '("enum" "struct" "type" "mod" "use" "fn" "static" "impl"
390 "extern" "impl" "static" "trait"))))
392 (defun rust-beginning-of-defun (&optional arg)
393 "Move backward to the beginning of the current defun.
395 With ARG, move backward multiple defuns. Negative ARG means
398 This is written mainly to be used as `beginning-of-defun-function' for Rust.
399 Don't move to the beginning of the line. `beginning-of-defun',
400 which calls this, does that afterwards."
402 (re-search-backward (concat "^\\(" rust-top-item-beg-re "\\)\\_>")
403 nil 'move (or arg 1)))
405 (defun rust-end-of-defun ()
406 "Move forward to the next end of defun.
408 With argument, do it that many times.
409 Negative argument -N means move back to Nth preceding end of defun.
411 Assume that this is called after beginning-of-defun. So point is
412 at the beginning of the defun body.
414 This is written mainly to be used as `end-of-defun-function' for Rust."
416 ;; Find the opening brace
417 (re-search-forward "[{]" nil t)
418 (goto-char (match-beginning 0))
419 ;; Go to the closing brace
422 ;; For compatibility with Emacs < 24, derive conditionally
423 (defalias 'rust-parent-mode
424 (if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode))
428 (define-derived-mode rust-mode rust-parent-mode "Rust"
429 "Major mode for Rust code."
431 :syntax-table rust-mode-syntax-table
434 (setq-local indent-line-function 'rust-mode-indent-line)
437 (setq-local font-lock-defaults '(rust-mode-font-lock-keywords nil nil nil nil))
440 (setq-local comment-start "// ")
441 (setq-local comment-end "")
442 (setq-local indent-tabs-mode nil)
444 ;; Allow paragraph fills for comments
445 (setq-local comment-start-skip "\\(?://[/!]*\\|/\\*[*!]?\\)[[:space:]]*")
446 (setq-local paragraph-start
447 (concat "[[:space:]]*\\(?:" comment-start-skip "\\|\\*/?[[:space:]]*\\|\\)$"))
448 (setq-local paragraph-separate paragraph-start)
449 (setq-local normal-auto-fill-function 'rust-do-auto-fill)
450 (setq-local fill-paragraph-function 'rust-fill-paragraph)
451 (setq-local fill-forward-paragraph-function 'rust-fill-forward-paragraph)
452 (setq-local adaptive-fill-function 'rust-find-fill-prefix)
453 (setq-local comment-multi-line t)
454 (setq-local comment-line-break-function 'rust-comment-indent-new-line)
455 (setq-local imenu-generic-expression rust-imenu-generic-expression)
456 (setq-local beginning-of-defun-function 'rust-beginning-of-defun)
457 (setq-local end-of-defun-function 'rust-end-of-defun))
460 (add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode))
462 (defun rust-mode-reload ()
464 (unload-feature 'rust-mode)
468 ;; Issue #6887: Rather than inheriting the 'gnu compilation error
469 ;; regexp (which is broken on a few edge cases), add our own 'rust
470 ;; compilation error regexp and use it instead.
471 (defvar rustc-compilation-regexps
472 (let ((file "\\([^\n]+\\)")
473 (start-line "\\([0-9]+\\)")
474 (start-col "\\([0-9]+\\)")
475 (end-line "\\([0-9]+\\)")
476 (end-col "\\([0-9]+\\)")
477 (error-or-warning "\\(?:[Ee]rror\\|\\([Ww]arning\\)\\)"))
478 (let ((re (concat "^" file ":" start-line ":" start-col
479 ": " end-line ":" end-col
480 " \\(?:[Ee]rror\\|\\([Ww]arning\\)\\):")))
481 (cons re '(1 (2 . 4) (3 . 5) (6)))))
482 "Specifications for matching errors in rustc invocations.
483 See `compilation-error-regexp-alist for help on their format.")
485 (eval-after-load 'compile
487 (add-to-list 'compilation-error-regexp-alist-alist
488 (cons 'rustc rustc-compilation-regexps))
489 (add-to-list 'compilation-error-regexp-alist 'rustc)))
493 ;;; rust-mode.el ends here