]> git.lizzy.rs Git - rust.git/blob - src/etc/emacs/rust-mode.el
debuginfo: Make debuginfo source location assignment more stable (Pt. 1)
[rust.git] / src / etc / emacs / rust-mode.el
1 ;;; rust-mode.el --- A major emacs mode for editing Rust source code
2
3 ;; Version: 0.2.0
4 ;; Author: Mozilla
5 ;; Url: https://github.com/rust-lang/rust
6 ;; Keywords: languages
7
8 ;;; Commentary:
9 ;;
10
11 ;;; Code:
12
13 (eval-when-compile (require 'misc))
14
15 ;; for GNU Emacs < 24.3
16 (eval-when-compile
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))))
21
22 ;; Syntax definitions and helpers
23 (defvar rust-mode-syntax-table
24   (let ((table (make-syntax-table)))
25
26     ;; Operators
27     (dolist (i '(?+ ?- ?* ?/ ?& ?| ?^ ?! ?< ?> ?~ ?@))
28       (modify-syntax-entry i "." table))
29
30     ;; Strings
31     (modify-syntax-entry ?\" "\"" table)
32     (modify-syntax-entry ?\\ "\\" table)
33
34     ;; mark _ as a word constituent so that identifiers
35     ;; such as xyz_type don't cause type to be highlighted
36     ;; as a keyword
37     (modify-syntax-entry ?_ "w" table)
38
39     ;; Comments
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)
44
45     table))
46
47 (defgroup rust-mode nil
48   "Support for Rust code."
49   :link '(url-link "http://www.rust-lang.org/")
50   :group 'languages)
51
52 (defcustom rust-indent-offset 4
53   "Indent Rust code by this number of spaces."
54   :type 'integer
55   :group 'rust-mode)
56
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))))
68
69 (defun rust-align-to-expr-after-brace ()
70   (save-excursion
71     (forward-char)
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:]]")
76         (forward-word 1)
77         (backward-word 1))
78       (current-column))))
79
80 (defun rust-rewind-to-beginning-of-current-level-expr ()
81   (let ((current-level (rust-paren-level)))
82     (back-to-indentation)
83     (while (> (rust-paren-level) current-level)
84       (backward-up-list)
85       (back-to-indentation))))
86
87 (defun rust-mode-indent-line ()
88   (interactive)
89   (let ((indent
90          (save-excursion
91            (back-to-indentation)
92            ;; Point is now at beginning of current line
93            (let* ((level (rust-paren-level))
94                   (baseline
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.
100                    (if (= 0 level)
101                        0
102                      (save-excursion
103                        (backward-up-list)
104                        (rust-rewind-to-beginning-of-current-level-expr)
105                        (+ (current-column) rust-indent-offset)))))
106              (cond
107               ;; A function return type is indented to the corresponding function arguments
108               ((looking-at "->")
109                (save-excursion
110                  (backward-list)
111                  (or (rust-align-to-expr-after-brace)
112                      (+ baseline rust-indent-offset))))
113
114               ;; A closing brace is 1 level unindended
115               ((looking-at "}") (- baseline rust-indent-offset))
116
117               ;; Doc comments in /** style with leading * indent to line up the *s
118               ((and (nth 4 (syntax-ppss)) (looking-at "*"))
119                (+ 1 baseline))
120
121               ;; If we're in any other token-tree / sexp, then:
122               (t
123                (or
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.
127                 (when (> level 0)
128                   (save-excursion
129                     (rust-rewind-irrelevant)
130                     (backward-up-list)
131                     ;; Point is now at the beginning of the containing set of braces
132                     (rust-align-to-expr-after-brace)))
133
134                 (progn
135                   (back-to-indentation)
136                   ;; Point is now at the beginning of the current line
137                   (if (or
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\\>\\|{")
143
144                        (save-excursion
145                          (rust-rewind-irrelevant)
146                          ;; Point is now at the end of the previous ine
147                          (or
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:
151                           ;;     { ? : ( , ; [ }
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 "#")))))
156                       baseline
157
158                     ;; Otherwise, we are continuing the same expression from the previous line,
159                     ;; so add one additional indent level
160                     (+ baseline rust-indent-offset))))))))))
161
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)))))
169
170
171 ;; Font-locking definitions and helpers
172 (defconst rust-mode-keywords
173   '("as"
174     "box" "break"
175     "const" "continue" "crate"
176     "do"
177     "else" "enum" "extern"
178     "false" "fn" "for"
179     "if" "impl" "in"
180     "let" "loop"
181     "match" "mod" "move" "mut"
182     "priv" "pub"
183     "ref" "return"
184     "self" "static" "struct" "super"
185     "true" "trait" "type"
186     "unsafe" "use"
187     "virtual"
188     "where" "while"))
189
190 (defconst rust-special-types
191   '("u8" "i8"
192     "u16" "i16"
193     "u32" "i32"
194     "u64" "i64"
195
196     "f32" "f64"
197     "float" "int" "uint" "isize" "usize"
198     "bool"
199     "str" "char"))
200
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)))
208
209 (defvar rust-mode-font-lock-keywords
210   (append
211    `(
212      ;; Keywords proper
213      (,(regexp-opt rust-mode-keywords 'words) . font-lock-keyword-face)
214
215      ;; Special types
216      (,(regexp-opt rust-special-types 'words) . font-lock-type-face)
217
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)
221
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)
225
226      ;; Field names like `foo:`, highlight excluding the :
227      (,(concat (rust-re-grab rust-re-ident) ":[^:]") 1 font-lock-variable-name-face)
228
229      ;; Module names like `foo::`, highlight including the ::
230      (,(rust-re-grab (concat rust-re-ident "::")) 1 font-lock-type-face)
231
232      ;; Lifetimes like `'foo`
233      (,(concat "'" (rust-re-grab rust-re-ident) "[^']") 1 font-lock-variable-name-face)
234
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)
242
243      ;; CamelCase Means Type Or Constructor
244      (,(rust-re-grabword rust-re-CamelCase) 1 font-lock-type-face)
245      )
246
247    ;; Item definitions
248    (mapcar #'(lambda (x)
249                (list (rust-re-item-def (car x))
250                      1 (cdr 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)))))
258
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."
261   (let ((result
262          ;; Replace /* with same number of spaces
263          (replace-regexp-in-string
264           "\\(?:/\\*+\\)[!*]"
265           (lambda (s)
266             ;; We want the * to line up with the first * of the comment start
267             (concat (make-string (- (length s) 2) ?\x20) "*"))
268           line-start)))
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 " ")))
272     result))
273
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
277   (save-excursion
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)))
281       (beginning-of-line)
282       (when (looking-at (concat "[[:space:]\n]*" comment-start-skip))
283         (goto-char (match-end 0))))
284
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
295                (end-of-line)
296                (syntax-ppss-flush-cache 1)
297                (and (nth 4 (syntax-ppss))
298                     (save-excursion
299                       (beginning-of-line)
300                       (looking-at paragraph-start))
301                     (looking-at "[[:space:]]*$")
302                     (nth 4 (syntax-ppss next-bol))))
303         (goto-char next-bol)))
304
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)))
313     (funcall body)))
314
315 (defun rust-with-comment-fill-prefix (body)
316   (let*
317       ((line-string (buffer-substring-no-properties
318                      (line-beginning-position) (line-end-position)))
319        (line-comment-start
320         (when (nth 4 (syntax-ppss))
321           (cond
322            ;; If we're inside the comment and see a * prefix, use it
323            ((string-match "^\\([[:space:]]*\\*+[[:space:]]*\\)"
324                           line-string)
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))))))
331        (fill-prefix
332         (or line-comment-start
333             fill-prefix)))
334     (funcall body)))
335
336 (defun rust-find-fill-prefix ()
337   (rust-with-comment-fill-prefix (lambda () fill-prefix)))
338
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
342    (lambda ()
343      (rust-with-comment-fill-prefix
344       (lambda ()
345         (let
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)
351           t))))))
352
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
356    (lambda ()
357      (apply 'do-auto-fill args)
358      t)))
359
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)))
364
365 (defun rust-comment-indent-new-line (&optional arg)
366   (rust-with-comment-fill-prefix
367    (lambda () (comment-indent-new-line arg))))
368
369 ;;; Imenu support
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.
376
377 Create a flat index of the item definitions in a Rust file.
378
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.")
382
383 ;;; Defun Motions
384
385 ;;; Start of a Rust item
386 (defvar rust-top-item-beg-re
387   (concat "^\\s-*\\(?:priv\\|pub\\)?\\s-*"
388           (regexp-opt
389            '("enum" "struct" "type" "mod" "use" "fn" "static" "impl"
390              "extern" "impl" "static" "trait"))))
391
392 (defun rust-beginning-of-defun (&optional arg)
393   "Move backward to the beginning of the current defun.
394
395 With ARG, move backward multiple defuns.  Negative ARG means
396 move forward.
397
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."
401   (interactive "p")
402   (re-search-backward (concat "^\\(" rust-top-item-beg-re "\\)\\_>")
403                       nil 'move (or arg 1)))
404
405 (defun rust-end-of-defun ()
406   "Move forward to the next end of defun.
407
408 With argument, do it that many times.
409 Negative argument -N means move back to Nth preceding end of defun.
410
411 Assume that this is called after beginning-of-defun. So point is
412 at the beginning of the defun body.
413
414 This is written mainly to be used as `end-of-defun-function' for Rust."
415   (interactive "p")
416   ;; Find the opening brace
417   (re-search-forward "[{]" nil t)
418   (goto-char (match-beginning 0))
419   ;; Go to the closing brace
420   (forward-sexp))
421
422 ;; For compatibility with Emacs < 24, derive conditionally
423 (defalias 'rust-parent-mode
424   (if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode))
425
426
427 ;;;###autoload
428 (define-derived-mode rust-mode rust-parent-mode "Rust"
429   "Major mode for Rust code."
430   :group 'rust-mode
431   :syntax-table rust-mode-syntax-table
432
433   ;; Indentation
434   (setq-local indent-line-function 'rust-mode-indent-line)
435
436   ;; Fonts
437   (setq-local font-lock-defaults '(rust-mode-font-lock-keywords nil nil nil nil))
438
439   ;; Misc
440   (setq-local comment-start "// ")
441   (setq-local comment-end   "")
442   (setq-local indent-tabs-mode nil)
443
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))
458
459 ;;;###autoload
460 (add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode))
461
462 (defun rust-mode-reload ()
463   (interactive)
464   (unload-feature 'rust-mode)
465   (require 'rust-mode)
466   (rust-mode))
467
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.")
484
485 (eval-after-load 'compile
486   '(progn
487      (add-to-list 'compilation-error-regexp-alist-alist
488                   (cons 'rustc rustc-compilation-regexps))
489      (add-to-list 'compilation-error-regexp-alist 'rustc)))
490
491 (provide 'rust-mode)
492
493 ;;; rust-mode.el ends here