diff options
| author | Randy Taylor | 2022-12-16 16:05:29 -0500 |
|---|---|---|
| committer | Yuan Fu | 2022-12-17 15:40:53 -0800 |
| commit | cb8ccdd26702606710258e2e08bd1fc35bbc1674 (patch) | |
| tree | 69ba771eacefbd4ac9d1b1194d7d8332840093f7 | |
| parent | 9fcf764dd73449fb469a7c7eb29aec3c06cdf067 (diff) | |
| download | emacs-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/NEWS | 4 | ||||
| -rw-r--r-- | lisp/progmodes/eglot.el | 2 | ||||
| -rw-r--r-- | lisp/progmodes/rust-ts-mode.el | 371 |
3 files changed, 376 insertions, 1 deletions
| @@ -3099,6 +3099,10 @@ A major mode based on the tree-sitter library for editing files | |||
| 3099 | written in YAML. It is auto-enabled for files with the ".yaml" or | 3099 | written 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'. | ||
| 3103 | A major mode based on the tree-sitter library for editing programs in | ||
| 3104 | the 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'. | ||
| 278 | Find string representation for NODE and set marker, then recurse | ||
| 279 | the 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 | ||