aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRandy Taylor2022-12-16 16:05:29 -0500
committerYuan Fu2022-12-17 15:40:53 -0800
commitcb8ccdd26702606710258e2e08bd1fc35bbc1674 (patch)
tree69ba771eacefbd4ac9d1b1194d7d8332840093f7
parent9fcf764dd73449fb469a7c7eb29aec3c06cdf067 (diff)
downloademacs-cb8ccdd26702606710258e2e08bd1fc35bbc1674.tar.gz
emacs-cb8ccdd26702606710258e2e08bd1fc35bbc1674.zip
Add rust-ts-mode (Bug#60136)
* etc/NEWS: Mention it. * lisp/progmodes/eglot.el (eglot-server-programs): Add it. * lisp/progmodes/rust-ts-mode.el: New major mode with tree-sitter support.
-rw-r--r--etc/NEWS4
-rw-r--r--lisp/progmodes/eglot.el2
-rw-r--r--lisp/progmodes/rust-ts-mode.el371
3 files changed, 376 insertions, 1 deletions
diff --git a/etc/NEWS b/etc/NEWS
index c5820a5f045..d4f96b26f75 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3099,6 +3099,10 @@ A major mode based on the tree-sitter library for editing files
3099written in YAML. It is auto-enabled for files with the ".yaml" or 3099written in YAML. It is auto-enabled for files with the ".yaml" or
3100".yml" extensions. 3100".yml" extensions.
3101 3101
3102*** New major mode 'rust-ts-mode'.
3103A major mode based on the tree-sitter library for editing programs in
3104the Rust language. It is auto-enabled for files with the ".rs" extension.
3105
3102 3106
3103* Incompatible Lisp Changes in Emacs 29.1 3107* Incompatible Lisp Changes in Emacs 29.1
3104 3108
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 5b9277d73f6..ce4ca4f3d92 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -182,7 +182,7 @@ chosen (interactively or automatically)."
182 when probe return (cons probe args) 182 when probe return (cons probe args)
183 finally (funcall err))))))) 183 finally (funcall err)))))))
184 184
185(defvar eglot-server-programs `((rust-mode . ,(eglot-alternatives '("rust-analyzer" "rls"))) 185(defvar eglot-server-programs `(((rust-ts-mode rust-mode) . ,(eglot-alternatives '("rust-analyzer" "rls")))
186 ((cmake-mode cmake-ts-mode) . ("cmake-language-server")) 186 ((cmake-mode cmake-ts-mode) . ("cmake-language-server"))
187 (vimrc-mode . ("vim-language-server" "--stdio")) 187 (vimrc-mode . ("vim-language-server" "--stdio"))
188 ((python-mode python-ts-mode) 188 ((python-mode python-ts-mode)
diff --git a/lisp/progmodes/rust-ts-mode.el b/lisp/progmodes/rust-ts-mode.el
new file mode 100644
index 00000000000..8b2ed191019
--- /dev/null
+++ b/lisp/progmodes/rust-ts-mode.el
@@ -0,0 +1,371 @@
1;;; rust-ts-mode.el --- tree-sitter support for Rust -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2022 Free Software Foundation, Inc.
4
5;; Author : Randy Taylor <dev@rjt.dev>
6;; Maintainer : Randy Taylor <dev@rjt.dev>
7;; Created : December 2022
8;; Keywords : rust languages tree-sitter
9
10;; This file is part of GNU Emacs.
11
12;; GNU Emacs is free software: you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
23;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
24
25;;; Commentary:
26;;
27
28;;; Code:
29
30(require 'treesit)
31(eval-when-compile (require 'rx))
32
33(declare-function treesit-parser-create "treesit.c")
34(declare-function treesit-induce-sparse-tree "treesit.c")
35(declare-function treesit-node-child "treesit.c")
36(declare-function treesit-node-child-by-field-name "treesit.c")
37(declare-function treesit-node-start "treesit.c")
38(declare-function treesit-node-type "treesit.c")
39
40(defcustom rust-ts-mode-indent-offset 4
41 "Number of spaces for each indentation step in `rust-ts-mode'."
42 :version "29.1"
43 :type 'integer
44 :safe 'integerp
45 :group 'rust)
46
47(defvar rust-ts-mode--syntax-table
48 (let ((table (make-syntax-table)))
49 (modify-syntax-entry ?+ "." table)
50 (modify-syntax-entry ?- "." table)
51 (modify-syntax-entry ?= "." table)
52 (modify-syntax-entry ?% "." table)
53 (modify-syntax-entry ?& "." table)
54 (modify-syntax-entry ?| "." table)
55 (modify-syntax-entry ?^ "." table)
56 (modify-syntax-entry ?! "." table)
57 (modify-syntax-entry ?@ "." table)
58 (modify-syntax-entry ?~ "." table)
59 (modify-syntax-entry ?< "." table)
60 (modify-syntax-entry ?> "." table)
61 (modify-syntax-entry ?/ ". 124b" table)
62 (modify-syntax-entry ?* ". 23" table)
63 (modify-syntax-entry ?\n "> b" table)
64 (modify-syntax-entry ?\^m "> b" table)
65 table)
66 "Syntax table for `rust-ts-mode'.")
67
68(defvar rust-ts-mode--indent-rules
69 `((rust
70 ((node-is ")") parent-bol 0)
71 ((node-is "]") parent-bol 0)
72 ((node-is "}") (and parent parent-bol) 0)
73 ((parent-is "arguments") parent-bol rust-ts-mode-indent-offset)
74 ((parent-is "await_expression") parent-bol rust-ts-mode-indent-offset)
75 ((parent-is "array_expression") parent-bol rust-ts-mode-indent-offset)
76 ((parent-is "binary_expression") parent-bol rust-ts-mode-indent-offset)
77 ((parent-is "block") parent-bol rust-ts-mode-indent-offset)
78 ((parent-is "declaration_list") parent-bol rust-ts-mode-indent-offset)
79 ((parent-is "enum_variant_list") parent-bol rust-ts-mode-indent-offset)
80 ((parent-is "field_declaration_list") parent-bol rust-ts-mode-indent-offset)
81 ((parent-is "field_expression") parent-bol rust-ts-mode-indent-offset)
82 ((parent-is "field_initializer_list") parent-bol rust-ts-mode-indent-offset)
83 ((parent-is "let_declaration") parent-bol rust-ts-mode-indent-offset)
84 ((parent-is "macro_definition") parent-bol rust-ts-mode-indent-offset)
85 ((parent-is "parameters") parent-bol rust-ts-mode-indent-offset)
86 ((parent-is "token_tree") parent-bol rust-ts-mode-indent-offset)
87 ((parent-is "use_list") parent-bol rust-ts-mode-indent-offset)))
88 "Tree-sitter indent rules for `rust-ts-mode'.")
89
90(defvar rust-ts-mode--builtin-macros
91 '("concat_bytes" "concat_idents" "const_format_args"
92 "format_args_nl" "log_syntax" "trace_macros" "assert" "assert_eq"
93 "assert_ne" "cfg" "column" "compile_error" "concat" "dbg"
94 "debug_assert" "debug_assert_eq" "debug_assert_ne" "env" "eprint"
95 "eprintln" "file" "format" "format_args" "include" "include_bytes"
96 "include_str" "is_x86_feature_detected" "line" "matches"
97 "module_path" "option_env" "panic" "print" "println" "stringify"
98 "thread_local" "todo" "try" "unimplemented" "unreachable" "vec"
99 "write" "writeln")
100 "Rust built-in macros for tree-sitter font-locking.")
101
102(defvar rust-ts-mode--keywords
103 '("as" "async" "await" "break" "const" "continue" "dyn" "else"
104 "enum" "extern" "fn" "for" "if" "impl" "in" "let" "loop" "match"
105 "mod" "move" "pub" "ref" "return" "static" "struct" "trait" "type"
106 "union" "unsafe" "use" "where" "while" (crate) (self) (super)
107 (mutable_specifier))
108 "Rust keywords for tree-sitter font-locking.")
109
110(defvar rust-ts-mode--operators
111 '("!" "!=" "%" "%=" "&" "&=" "&&" "*" "*=" "+" "+=" "," "-" "-="
112 "->" "." ".." "..=" "..." "/" "/=" ":" ";" "<<" "<<=" "<" "<="
113 "=" "==" "=>" ">" ">=" ">>" ">>=" "@" "^" "^=" "|" "|=" "||" "?")
114 "Rust operators for tree-sitter font-locking.")
115
116(defvar rust-ts-mode--font-lock-settings
117 (treesit-font-lock-rules
118 :language 'rust
119 :feature 'attribute
120 '((attribute_item) @font-lock-constant-face
121 (inner_attribute_item) @font-lock-constant-face)
122
123 :language 'rust
124 :feature 'bracket
125 '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face)
126
127 :language 'rust
128 :feature 'builtin
129 `((macro_invocation
130 macro: ((identifier) @font-lock-builtin-face
131 (:match ,(rx-to-string
132 `(seq bol
133 (or ,@rust-ts-mode--builtin-macros)
134 eol))
135 @font-lock-builtin-face)))
136 ((identifier) @font-lock-type-face
137 (:match "^\\(:?Err\\|Ok\\|None\\|Some\\)$" @font-lock-type-face)))
138
139 :language 'rust
140 :feature 'comment
141 '(([(block_comment) (line_comment)]) @font-lock-comment-face)
142
143 :language 'rust
144 :feature 'constant
145 `((boolean_literal) @font-lock-constant-face
146 ((identifier) @font-lock-constant-face
147 (:match "^[A-Z][A-Z\\d_]*$" @font-lock-constant-face)))
148
149 :language 'rust
150 :feature 'delimiter
151 '((["," "." ";" ":" "::"]) @font-lock-delimiter-face)
152
153 :language 'rust
154 :feature 'function
155 '((call_expression
156 function:
157 [(identifier) @font-lock-function-name-face
158 (field_expression
159 field: (field_identifier) @font-lock-function-name-face)
160 (scoped_identifier
161 name: (identifier) @font-lock-function-name-face)])
162 (function_item (identifier) @font-lock-function-name-face)
163 (generic_function
164 function: [(identifier) @font-lock-function-name-face
165 (field_expression
166 field: (field_identifier) @font-lock-function-name-face)
167 (scoped_identifier
168 name: (identifier) @font-lock-function-name-face)])
169 (macro_definition "macro_rules!" @font-lock-constant-face)
170 (macro_definition (identifier) @font-lock-preprocessor-face)
171 (macro_invocation macro: (identifier) @font-lock-preprocessor-face))
172
173 :language 'rust
174 :feature 'keyword
175 `([,@rust-ts-mode--keywords] @font-lock-keyword-face)
176
177 :language 'rust
178 :feature 'number
179 '([(float_literal) (integer_literal)] @font-lock-number-face)
180
181 :language 'rust
182 :feature 'operator
183 `([,@rust-ts-mode--operators] @font-lock-operator-face)
184
185 :language 'rust
186 :feature 'string
187 '([(char_literal)
188 (raw_string_literal)
189 (string_literal)] @font-lock-string-face)
190
191 :language 'rust
192 :feature 'type
193 `((call_expression
194 function: (scoped_identifier
195 path: (identifier) @font-lock-type-face))
196 (enum_variant name: (identifier) @font-lock-type-face)
197 (match_arm
198 pattern: (match_pattern (_ type: (identifier) @font-lock-type-face)))
199 (match_arm
200 pattern: (match_pattern
201 (_ type: (scoped_identifier
202 path: (identifier) @font-lock-type-face))))
203 (mod_item name: (identifier) @font-lock-constant-face)
204 (primitive_type) @font-lock-type-face
205 (type_identifier) @font-lock-type-face
206 (scoped_identifier name: (identifier) @font-lock-type-face)
207 (scoped_identifier path: (identifier) @font-lock-constant-face)
208 (scoped_identifier
209 (scoped_identifier
210 path: (identifier) @font-lock-constant-face))
211 ((scoped_identifier
212 path: [(identifier) @font-lock-type-face
213 (scoped_identifier
214 name: (identifier) @font-lock-type-face)])
215 (:match "^[A-Z]" @font-lock-type-face))
216 (scoped_type_identifier path: (identifier) @font-lock-constant-face)
217 (scoped_use_list
218 path: [(identifier) @font-lock-constant-face
219 (scoped_identifier (identifier) @font-lock-constant-face)])
220 (type_identifier) @font-lock-type-face
221 (use_as_clause alias: (identifier) @font-lock-type-face)
222 (use_list (identifier) @font-lock-type-face))
223
224 :language 'rust
225 :feature 'variable
226 '((identifier) @font-lock-variable-name-face
227 ;; Everything in a token_tree is an identifier.
228 (token_tree (identifier) @default))
229
230 :language 'rust
231 :feature 'escape-sequence
232 :override t
233 '((escape_sequence) @font-lock-escape-face)
234
235 :language 'rust
236 :feature 'property
237 :override t
238 '((field_identifier) @font-lock-property-face
239 (shorthand_field_initializer (identifier) @font-lock-property-face))
240
241 :language 'rust
242 :feature 'error
243 :override t
244 '((ERROR) @font-lock-warning-face))
245 "Tree-sitter font-lock settings for `rust-ts-mode'.")
246
247(defun rust-ts-mode--imenu ()
248 "Return Imenu alist for the current buffer."
249 (let* ((node (treesit-buffer-root-node))
250 (enum-tree (treesit-induce-sparse-tree
251 node "enum_item" nil))
252 (enum-index (rust-ts-mode--imenu-1 enum-tree))
253 (func-tree (treesit-induce-sparse-tree
254 node "function_item" nil))
255 (func-index (rust-ts-mode--imenu-1 func-tree))
256 (impl-tree (treesit-induce-sparse-tree
257 node "impl_item" nil))
258 (impl-index (rust-ts-mode--imenu-1 impl-tree))
259 (mod-tree (treesit-induce-sparse-tree
260 node "mod_item" nil))
261 (mod-index (rust-ts-mode--imenu-1 mod-tree))
262 (struct-tree (treesit-induce-sparse-tree
263 node "struct_item" nil))
264 (struct-index (rust-ts-mode--imenu-1 struct-tree))
265 (type-tree (treesit-induce-sparse-tree
266 node "type_item" nil))
267 (type-index (rust-ts-mode--imenu-1 type-tree)))
268 (append
269 (when mod-index `(("Module" . ,mod-index)))
270 (when enum-index `(("Enum" . ,enum-index)))
271 (when impl-index `(("Impl" . ,impl-index)))
272 (when type-index `(("Type" . ,type-index)))
273 (when struct-index `(("Struct" . ,struct-index)))
274 (when func-index `(("Fn" . ,func-index))))))
275
276(defun rust-ts-mode--imenu-1 (node)
277 "Helper for `rust-ts-mode--imenu'.
278Find string representation for NODE and set marker, then recurse
279the subtrees."
280 (let* ((ts-node (car node))
281 (children (cdr node))
282 (subtrees (mapcan #'rust-ts-mode--imenu-1
283 children))
284 (name (when ts-node
285 (pcase (treesit-node-type ts-node)
286 ("enum_item"
287 (treesit-node-text
288 (treesit-node-child-by-field-name ts-node "name") t))
289 ("function_item"
290 (treesit-node-text
291 (treesit-node-child-by-field-name ts-node "name") t))
292 ("impl_item"
293 (let ((trait-node (treesit-node-child-by-field-name ts-node "trait")))
294 (concat
295 (treesit-node-text
296 trait-node t)
297 (when trait-node
298 " for ")
299 (treesit-node-text
300 (treesit-node-child-by-field-name ts-node "type") t))))
301 ("mod_item"
302 (treesit-node-text
303 (treesit-node-child-by-field-name ts-node "name") t))
304 ("struct_item"
305 (treesit-node-text
306 (treesit-node-child-by-field-name ts-node "name") t))
307 ("type_item"
308 (treesit-node-text
309 (treesit-node-child-by-field-name ts-node "name") t)))))
310 (marker (when ts-node
311 (set-marker (make-marker)
312 (treesit-node-start ts-node)))))
313 (cond
314 ((or (null ts-node) (null name)) subtrees)
315 (subtrees
316 `((,name ,(cons name marker) ,@subtrees)))
317 (t
318 `((,name . ,marker))))))
319
320;;;###autoload
321(add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-ts-mode))
322
323;;;###autoload
324(define-derived-mode rust-ts-mode prog-mode "Rust"
325 "Major mode for editing Rust, powered by tree-sitter."
326 :group 'rust
327 :syntax-table rust-ts-mode--syntax-table
328
329 (when (treesit-ready-p 'rust)
330 (treesit-parser-create 'rust)
331
332 ;; Comments.
333 (setq-local comment-start "// ")
334 (setq-local comment-end "")
335 (setq-local comment-start-skip (rx (or (seq "/" (+ "/"))
336 (seq "/" (+ "*")))
337 (* (syntax whitespace))))
338 (setq-local comment-end-skip
339 (rx (* (syntax whitespace))
340 (group (or (syntax comment-end)
341 (seq (+ "*") "/")))))
342
343 ;; Font-lock.
344 (setq-local treesit-font-lock-settings rust-ts-mode--font-lock-settings)
345 (setq-local treesit-font-lock-feature-list
346 '(( comment)
347 ( keyword string)
348 ( attribute builtin constant escape-sequence
349 function number property type variable)
350 ( bracket delimiter error operator)))
351
352 ;; Imenu.
353 (setq-local imenu-create-index-function #'rust-ts-mode--imenu)
354 (setq-local which-func-functions nil)
355
356 ;; Indent.
357 (setq-local indent-tabs-mode nil
358 treesit-simple-indent-rules rust-ts-mode--indent-rules)
359
360 ;; Navigation.
361 (setq-local treesit-defun-type-regexp
362 (regexp-opt '("enum_item"
363 "function_item"
364 "impl_item"
365 "struct_item")))
366
367 (treesit-major-mode-setup)))
368
369(provide 'rust-ts-mode)
370
371;;; rust-ts-mode.el ends here