]> git.lizzy.rs Git - rust.git/blob - src/etc/emacs/rust-mode.el
rust-mode: Default rust-indent-offset to 4, not default-tab-width
[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/mozilla/rust
6
7 (eval-when-compile (require 'cl))
8
9 ;; Syntax definitions and helpers
10 (defvar rust-mode-syntax-table
11   (let ((table (make-syntax-table)))
12
13     ;; Operators
14     (loop for i in '(?+ ?- ?* ?/ ?& ?| ?^ ?! ?< ?> ?~ ?@)
15           do (modify-syntax-entry i "." table))
16
17     ;; Strings
18     (modify-syntax-entry ?\" "\"" table)
19     (modify-syntax-entry ?\\ "\\" table)
20
21     ;; _ is a word-char
22     (modify-syntax-entry ?_ "w" table)
23
24     ;; Comments
25     (modify-syntax-entry ?/  ". 124b" table)
26     (modify-syntax-entry ?*  ". 23"   table)
27     (modify-syntax-entry ?\n "> b"    table)
28     (modify-syntax-entry ?\^m "> b"   table)
29
30     table))
31
32 (defcustom rust-indent-offset 4
33   "*Indent Rust code by this number of spaces.")
34
35 (defun rust-paren-level () (nth 0 (syntax-ppss)))
36 (defun rust-in-str-or-cmnt () (nth 8 (syntax-ppss)))
37 (defun rust-rewind-past-str-cmnt () (goto-char (nth 8 (syntax-ppss))))
38 (defun rust-rewind-irrelevant ()
39   (let ((starting (point)))
40     (skip-chars-backward "[:space:]\n")
41     (if (looking-back "\\*/") (backward-char))
42     (if (rust-in-str-or-cmnt)
43         (rust-rewind-past-str-cmnt))
44     (if (/= starting (point))
45         (rust-rewind-irrelevant))))
46
47 (defun rust-mode-indent-line ()
48   (interactive)
49   (let ((indent
50          (save-excursion
51            (back-to-indentation)
52            (let ((level (rust-paren-level)))
53              (cond
54               ;; A function return type is 1 level indented
55               ((looking-at "->") (* rust-indent-offset (+ level 1)))
56
57               ;; A closing brace is 1 level unindended
58               ((looking-at "}") (* rust-indent-offset (- level 1)))
59
60               ;; If we're in any other token-tree / sexp, then:
61               ;;  - [ or ( means line up with the opening token
62               ;;  - { means indent to either nesting-level * rust-indent-offset,
63               ;;    or one further indent from that if either current line
64               ;;    begins with 'else', or previous line didn't end in
65               ;;    semi, comma or brace, and wasn't an attribute. PHEW.
66               ((> level 0)
67                (let ((pt (point)))
68                  (rust-rewind-irrelevant)
69                  (backward-up-list)
70                  (if (looking-at "[[(]")
71                      (+ 1 (current-column))
72                    (progn
73                      (goto-char pt)
74                      (back-to-indentation)
75                      (if (looking-at "\\<else\\>")
76                          (* rust-indent-offset (+ 1 level))
77                        (progn
78                          (goto-char pt)
79                          (beginning-of-line)
80                          (rust-rewind-irrelevant)
81                          (end-of-line)
82                          (if (looking-back "[{};,]")
83                              (* rust-indent-offset level)
84                            (back-to-indentation)
85                            (if (looking-at "#")
86                                (* rust-indent-offset level)
87                              (* rust-indent-offset (+ 1 level))))))))))
88
89               ;; Otherwise we're in a column-zero definition
90               (t 0))))))
91     (cond
92      ;; If we're to the left of the indentation, reindent and jump to it.
93      ((<= (current-column) indent)
94       (indent-line-to indent))
95
96      ;; We're to the right; if it needs indent, do so but save excursion.
97      ((not (eq (current-indentation) indent))
98       (save-excursion (indent-line-to indent))))))
99
100
101 ;; Font-locking definitions and helpers
102 (defconst rust-mode-keywords
103   '("as"
104     "break"
105     "do"
106     "else" "enum" "extern"
107     "false" "fn" "for"
108     "if" "impl" "in"
109     "let" "loop"
110     "match" "mod" "mut"
111     "priv" "pub"
112     "ref" "return"
113     "self" "static" "struct" "super"
114     "true" "trait" "type"
115     "unsafe" "use"
116     "while"))
117
118 (defconst rust-special-types
119   '("u8" "i8"
120     "u16" "i16"
121     "u32" "i32"
122     "u64" "i64"
123
124     "f32" "f64"
125     "float" "int" "uint"
126     "bool"
127     "str" "char"))
128
129 (defconst rust-re-ident "[[:word:][:multibyte:]_][[:word:][:multibyte:]_[:digit:]]*")
130 (defconst rust-re-CamelCase "[[:upper:]][[:word:][:multibyte:]_[:digit:]]*")
131 (defun rust-re-word (inner) (concat "\\<" inner "\\>"))
132 (defun rust-re-grab (inner) (concat "\\(" inner "\\)"))
133 (defun rust-re-grabword (inner) (rust-re-grab (rust-re-word inner)))
134 (defun rust-re-item-def (itype)
135   (concat (rust-re-word itype) "[[:space:]]+" (rust-re-grab rust-re-ident)))
136
137 (defvar rust-mode-font-lock-keywords
138   (append
139    `(
140      ;; Keywords proper
141      (,(regexp-opt rust-mode-keywords 'words) . font-lock-keyword-face)
142
143      ;; Special types
144      (,(regexp-opt rust-special-types 'words) . font-lock-type-face)
145
146      ;; Attributes like `#[bar(baz)]`
147      (,(rust-re-grab (concat "#\\[" rust-re-ident "[^]]*\\]"))
148       1 font-lock-preprocessor-face)
149
150      ;; Syntax extension invocations like `foo!`, highlight including the !
151      (,(concat (rust-re-grab (concat rust-re-ident "!")) "[({[:space:]]")
152       1 font-lock-preprocessor-face)
153
154      ;; Field names like `foo:`, highlight excluding the :
155      (,(concat (rust-re-grab rust-re-ident) ":[^:]") 1 font-lock-variable-name-face)
156
157      ;; Module names like `foo::`, highlight including the ::
158      (,(rust-re-grab (concat rust-re-ident "::")) 1 font-lock-type-face)
159
160      ;; Lifetimes like `'foo`
161      (,(concat "'" (rust-re-grab rust-re-ident) "[^']") 1 font-lock-variable-name-face)
162
163      ;; Character constants, since they're not treated as strings
164      ;; in order to have sufficient leeway to parse 'lifetime above.
165      (,(rust-re-grab "'[^']'") 1 font-lock-string-face)
166      (,(rust-re-grab "'\\\\[nrt]'") 1 font-lock-string-face)
167      (,(rust-re-grab "'\\\\x[[:xdigit:]]\\{2\\}'") 1 font-lock-string-face)
168      (,(rust-re-grab "'\\\\u[[:xdigit:]]\\{4\\}'") 1 font-lock-string-face)
169      (,(rust-re-grab "'\\\\U[[:xdigit:]]\\{8\\}'") 1 font-lock-string-face)
170
171      ;; CamelCase Means Type Or Constructor
172      (,(rust-re-grabword rust-re-CamelCase) 1 font-lock-type-face)
173      )
174
175    ;; Item definitions
176    (loop for (item . face) in
177
178          '(("enum" . font-lock-type-face)
179            ("struct" . font-lock-type-face)
180            ("type" . font-lock-type-face)
181            ("mod" . font-lock-type-face)
182            ("use" . font-lock-type-face)
183            ("fn" . font-lock-function-name-face)
184            ("static" . font-lock-constant-face))
185
186          collect `(,(rust-re-item-def item) 1 ,face))))
187
188
189 ;; For compatibility with Emacs < 24, derive conditionally
190 (defalias 'rust-parent-mode
191   (if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode))
192
193
194 ;;;###autoload
195 (define-derived-mode rust-mode rust-parent-mode "Rust"
196   "Major mode for Rust code."
197
198   ;; Basic syntax
199   (set-syntax-table rust-mode-syntax-table)
200
201   ;; Indentation
202   (set (make-local-variable 'indent-line-function)
203        'rust-mode-indent-line)
204
205   ;; Fonts
206   (set (make-local-variable 'font-lock-defaults)
207        '(rust-mode-font-lock-keywords nil nil nil nil))
208
209   ;; Misc
210   (set (make-local-variable 'comment-start) "// ")
211   (set (make-local-variable 'comment-end)   "")
212   (set (make-local-variable 'indent-tabs-mode) nil))
213
214
215 ;;;###autoload
216 (add-to-list 'auto-mode-alist '("\\.rs$" . rust-mode))
217
218 (defun rust-mode-reload ()
219   (interactive)
220   (unload-feature 'rust-mode)
221   (require 'rust-mode)
222   (rust-mode))
223
224 (provide 'rust-mode)
225
226 ;; Issue #6887: Rather than inheriting the 'gnu compilation error
227 ;; regexp (which is broken on a few edge cases), add our own 'rust
228 ;; compilation error regexp and use it instead.
229 (defvar rustc-compilation-regexps
230   (let ((file "\\([^\n]+\\)")
231         (start-line "\\([0-9]+\\)")
232         (start-col  "\\([0-9]+\\)")
233         (end-line   "\\([0-9]+\\)")
234         (end-col    "\\([0-9]+\\)")
235         (error-or-warning "\\(?:[Ee]rror\\|\\([Ww]arning\\)\\)"))
236     (let ((re (concat "^" file ":" start-line ":" start-col
237                       ": " end-line ":" end-col
238                       " \\(?:[Ee]rror\\|\\([Ww]arning\\)\\):")))
239       (cons re '(1 (2 . 4) (3 . 5) (6)))))
240   "Specifications for matching errors in rustc invocations.
241 See `compilation-error-regexp-alist for help on their format.")
242
243 (eval-after-load 'compile
244   '(progn
245      (add-to-list 'compilation-error-regexp-alist-alist
246                   (cons 'rustc rustc-compilation-regexps))
247      (add-to-list 'compilation-error-regexp-alist 'rustc)))
248
249 ;;; rust-mode.el ends here