diff options
| author | Theodor Thornhill | 2022-10-11 10:27:55 +0200 |
|---|---|---|
| committer | Yuan Fu | 2022-10-11 23:41:53 -0700 |
| commit | 45b8204e09b7d399b792bb26c799efe48835c4a7 (patch) | |
| tree | 1ddae2f1189a13134f9757bd19d8892b64c774ac | |
| parent | c4179117afd37a03a7ba322e66b5bd6bae5637bd (diff) | |
| download | emacs-45b8204e09b7d399b792bb26c799efe48835c4a7.tar.gz emacs-45b8204e09b7d399b792bb26c799efe48835c4a7.zip | |
Add TypeScript support with tree-sitter
* lisp/progmodes/ts-mode.el (ts-mode): New major mode for TypeScript
with support for tree-sitter. It uses the TSX parser, so that we get
support for TSX as well as TypeScript. If we cannot find tree-sitter,
we default to using js-mode.
| -rw-r--r-- | etc/NEWS | 7 | ||||
| -rw-r--r-- | lisp/progmodes/ts-mode.el | 364 |
2 files changed, 371 insertions, 0 deletions
| @@ -2775,6 +2775,13 @@ This is a lightweight variant of 'js-mode' that is used by default | |||
| 2775 | when visiting JSON files. | 2775 | when visiting JSON files. |
| 2776 | 2776 | ||
| 2777 | 2777 | ||
| 2778 | ** New mode ts-mode'. | ||
| 2779 | Support is added for TypeScript, based on the new integration with | ||
| 2780 | Tree-Sitter. There's support for font-locking, indentation and | ||
| 2781 | navigation. Tree-Sitter is required for this mode to function, but if | ||
| 2782 | it is not available, we will default to use 'js-mode'. | ||
| 2783 | |||
| 2784 | |||
| 2778 | * Incompatible Lisp Changes in Emacs 29.1 | 2785 | * Incompatible Lisp Changes in Emacs 29.1 |
| 2779 | 2786 | ||
| 2780 | +++ | 2787 | +++ |
diff --git a/lisp/progmodes/ts-mode.el b/lisp/progmodes/ts-mode.el new file mode 100644 index 00000000000..99ffe0c0f63 --- /dev/null +++ b/lisp/progmodes/ts-mode.el | |||
| @@ -0,0 +1,364 @@ | |||
| 1 | ;;; ts-mode.el --- tree sitter support for TypeScript -*- lexical-binding: t; -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2022 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author : Theodor Thornhill <theo@thornhill.no> | ||
| 6 | ;; Maintainer : Theodor Thornhill <theo@thornhill.no> | ||
| 7 | ;; Created : October 2022 | ||
| 8 | ;; Keywords : typescript tsx languages tree-sitter | ||
| 9 | |||
| 10 | ;; This file is part of GNU Emacs. | ||
| 11 | |||
| 12 | ;; This program 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 | ;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 24 | |||
| 25 | (require 'treesit) | ||
| 26 | (require 'rx) | ||
| 27 | (require 'js) | ||
| 28 | |||
| 29 | (defcustom ts-mode-indent-offset 2 | ||
| 30 | "Number of spaces for each indentation step in `ts-mode'." | ||
| 31 | :type 'integer | ||
| 32 | :safe 'integerp | ||
| 33 | :group 'typescript) | ||
| 34 | |||
| 35 | (defvar ts-mode--syntax-table | ||
| 36 | (let ((table (make-syntax-table))) | ||
| 37 | ;; Taken from the cc-langs version | ||
| 38 | (modify-syntax-entry ?_ "_" table) | ||
| 39 | (modify-syntax-entry ?$ "_" table) | ||
| 40 | (modify-syntax-entry ?\\ "\\" table) | ||
| 41 | (modify-syntax-entry ?+ "." table) | ||
| 42 | (modify-syntax-entry ?- "." table) | ||
| 43 | (modify-syntax-entry ?= "." table) | ||
| 44 | (modify-syntax-entry ?% "." table) | ||
| 45 | (modify-syntax-entry ?< "." table) | ||
| 46 | (modify-syntax-entry ?> "." table) | ||
| 47 | (modify-syntax-entry ?& "." table) | ||
| 48 | (modify-syntax-entry ?| "." table) | ||
| 49 | (modify-syntax-entry ?` "\"" table) | ||
| 50 | (modify-syntax-entry ?\240 "." table) | ||
| 51 | table) | ||
| 52 | "Syntax table for `ts-mode'.") | ||
| 53 | |||
| 54 | (defvar ts-mode--indent-rules | ||
| 55 | `((tsx | ||
| 56 | ((node-is "}") parent-bol 0) | ||
| 57 | ((node-is ")") parent-bol 0) | ||
| 58 | ((node-is "]") parent-bol 0) | ||
| 59 | ((node-is ">") parent-bol 0) | ||
| 60 | ((node-is ".") | ||
| 61 | parent-bol ,ts-mode-indent-offset) | ||
| 62 | ((parent-is "ternary_expression") | ||
| 63 | parent-bol ,ts-mode-indent-offset) | ||
| 64 | ((parent-is "named_imports") | ||
| 65 | parent-bol ,ts-mode-indent-offset) | ||
| 66 | ((parent-is "statement_block") | ||
| 67 | parent-bol ,ts-mode-indent-offset) | ||
| 68 | ((parent-is "type_arguments") | ||
| 69 | parent-bol ,ts-mode-indent-offset) | ||
| 70 | ((parent-is "variable_declarator") | ||
| 71 | parent-bol ,ts-mode-indent-offset) | ||
| 72 | ((parent-is "arguments") | ||
| 73 | parent-bol ,ts-mode-indent-offset) | ||
| 74 | ((parent-is "array") | ||
| 75 | parent-bol ,ts-mode-indent-offset) | ||
| 76 | ((parent-is "formal_parameters") | ||
| 77 | parent-bol ,ts-mode-indent-offset) | ||
| 78 | ((parent-is "template_substitution") | ||
| 79 | parent-bol ,ts-mode-indent-offset) | ||
| 80 | ((parent-is "object_pattern") | ||
| 81 | parent-bol ,ts-mode-indent-offset) | ||
| 82 | ((parent-is "object") | ||
| 83 | parent-bol ,ts-mode-indent-offset) | ||
| 84 | ((parent-is "object_type") | ||
| 85 | parent-bol ,ts-mode-indent-offset) | ||
| 86 | ((parent-is "enum_body") | ||
| 87 | parent-bol ,ts-mode-indent-offset) | ||
| 88 | ((parent-is "arrow_function") | ||
| 89 | parent-bol ,ts-mode-indent-offset) | ||
| 90 | ((parent-is "parenthesized_expression") | ||
| 91 | parent-bol ,ts-mode-indent-offset) | ||
| 92 | |||
| 93 | ;; TSX | ||
| 94 | ((parent-is "jsx_opening_element") | ||
| 95 | parent ,ts-mode-indent-offset) | ||
| 96 | ((node-is "jsx_closing_element") parent 0) | ||
| 97 | ((parent-is "jsx_element") | ||
| 98 | parent ,ts-mode-indent-offset) | ||
| 99 | ((node-is "/") parent 0) | ||
| 100 | ((parent-is "jsx_self_closing_element") | ||
| 101 | parent ,ts-mode-indent-offset) | ||
| 102 | (no-node parent-bol 0)))) | ||
| 103 | |||
| 104 | (defvar ts-mode--settings | ||
| 105 | (treesit-font-lock-rules | ||
| 106 | :language 'tsx | ||
| 107 | :override t | ||
| 108 | '( | ||
| 109 | (template_string) @font-lock-string-face | ||
| 110 | |||
| 111 | ((identifier) @font-lock-constant-face | ||
| 112 | (:match "^[A-Z_][A-Z_\\d]*$" @font-lock-constant-face)) | ||
| 113 | |||
| 114 | (nested_type_identifier | ||
| 115 | module: (identifier) @font-lock-type-face) | ||
| 116 | (type_identifier) @font-lock-type-face | ||
| 117 | (predefined_type) @font-lock-type-face | ||
| 118 | |||
| 119 | (new_expression | ||
| 120 | constructor: (identifier) @font-lock-type-face) | ||
| 121 | |||
| 122 | (function | ||
| 123 | name: (identifier) @font-lock-function-name-face) | ||
| 124 | |||
| 125 | (function_declaration | ||
| 126 | name: (identifier) @font-lock-function-name-face) | ||
| 127 | |||
| 128 | (method_definition | ||
| 129 | name: (property_identifier) @font-lock-function-name-face) | ||
| 130 | |||
| 131 | (variable_declarator | ||
| 132 | name: (identifier) @font-lock-function-name-face | ||
| 133 | value: [(function) (arrow_function)]) | ||
| 134 | |||
| 135 | (variable_declarator | ||
| 136 | name: (array_pattern | ||
| 137 | (identifier) | ||
| 138 | (identifier) @font-lock-function-name-face) | ||
| 139 | value: (array (number) (function))) | ||
| 140 | |||
| 141 | (assignment_expression | ||
| 142 | left: [(identifier) @font-lock-function-name-face | ||
| 143 | (member_expression | ||
| 144 | property: (property_identifier) @font-lock-function-name-face)] | ||
| 145 | right: [(function) (arrow_function)]) | ||
| 146 | |||
| 147 | (call_expression | ||
| 148 | function: | ||
| 149 | [(identifier) @font-lock-function-name-face | ||
| 150 | (member_expression | ||
| 151 | property: (property_identifier) @font-lock-function-name-face)]) | ||
| 152 | |||
| 153 | (variable_declarator | ||
| 154 | name: (identifier) @font-lock-variable-name-face) | ||
| 155 | |||
| 156 | (enum_declaration (identifier) @font-lock-type-face) | ||
| 157 | |||
| 158 | (enum_body (property_identifier) @font-lock-type-face) | ||
| 159 | |||
| 160 | (enum_assignment name: (property_identifier) @font-lock-type-face) | ||
| 161 | |||
| 162 | (assignment_expression | ||
| 163 | left: [(identifier) @font-lock-variable-name-face | ||
| 164 | (member_expression | ||
| 165 | property: (property_identifier) @font-lock-variable-name-face)]) | ||
| 166 | |||
| 167 | (for_in_statement | ||
| 168 | left: (identifier) @font-lock-variable-name-face) | ||
| 169 | |||
| 170 | (arrow_function | ||
| 171 | parameter: (identifier) @font-lock-variable-name-face) | ||
| 172 | |||
| 173 | (arrow_function | ||
| 174 | parameters: | ||
| 175 | [(_ (identifier) @font-lock-variable-name-face) | ||
| 176 | (_ (_ (identifier) @font-lock-variable-name-face)) | ||
| 177 | (_ (_ (_ (identifier) @font-lock-variable-name-face)))]) | ||
| 178 | |||
| 179 | |||
| 180 | (pair key: (property_identifier) @font-lock-variable-name-face) | ||
| 181 | |||
| 182 | (pair value: (identifier) @font-lock-variable-name-face) | ||
| 183 | |||
| 184 | (pair | ||
| 185 | key: (property_identifier) @font-lock-function-name-face | ||
| 186 | value: [(function) (arrow_function)]) | ||
| 187 | |||
| 188 | (property_signature | ||
| 189 | name: (property_identifier) @font-lock-variable-name-face) | ||
| 190 | |||
| 191 | ((shorthand_property_identifier) @font-lock-variable-name-face) | ||
| 192 | |||
| 193 | (pair_pattern | ||
| 194 | key: (property_identifier) @font-lock-variable-name-face) | ||
| 195 | |||
| 196 | ((shorthand_property_identifier_pattern) | ||
| 197 | @font-lock-variable-name-face) | ||
| 198 | |||
| 199 | (array_pattern (identifier) @font-lock-variable-name-face) | ||
| 200 | |||
| 201 | (jsx_opening_element | ||
| 202 | [(nested_identifier (identifier)) (identifier)] | ||
| 203 | @font-lock-function-name-face) | ||
| 204 | |||
| 205 | (jsx_closing_element | ||
| 206 | [(nested_identifier (identifier)) (identifier)] | ||
| 207 | @font-lock-function-name-face) | ||
| 208 | |||
| 209 | (jsx_self_closing_element | ||
| 210 | [(nested_identifier (identifier)) (identifier)] | ||
| 211 | @font-lock-function-name-face) | ||
| 212 | |||
| 213 | (jsx_attribute (property_identifier) @font-lock-constant-face) | ||
| 214 | |||
| 215 | [(this) (super)] @font-lock-keyword-face | ||
| 216 | |||
| 217 | [(true) (false) (null)] @font-lock-constant-face | ||
| 218 | (regex pattern: (regex_pattern)) @font-lock-string-face | ||
| 219 | (number) @font-lock-constant-face | ||
| 220 | |||
| 221 | (string) @font-lock-string-face | ||
| 222 | (template_string) @font-lock-string-face | ||
| 223 | |||
| 224 | (template_substitution | ||
| 225 | ["${" "}"] @font-lock-constant-face) | ||
| 226 | |||
| 227 | ["!" | ||
| 228 | "abstract" | ||
| 229 | "as" | ||
| 230 | "async" | ||
| 231 | "await" | ||
| 232 | "break" | ||
| 233 | "case" | ||
| 234 | "catch" | ||
| 235 | "class" | ||
| 236 | "const" | ||
| 237 | "continue" | ||
| 238 | "debugger" | ||
| 239 | "declare" | ||
| 240 | "default" | ||
| 241 | "delete" | ||
| 242 | "do" | ||
| 243 | "else" | ||
| 244 | "enum" | ||
| 245 | "export" | ||
| 246 | "extends" | ||
| 247 | "finally" | ||
| 248 | "for" | ||
| 249 | "from" | ||
| 250 | "function" | ||
| 251 | "get" | ||
| 252 | "if" | ||
| 253 | "implements" | ||
| 254 | "import" | ||
| 255 | "in" | ||
| 256 | "instanceof" | ||
| 257 | "interface" | ||
| 258 | "keyof" | ||
| 259 | "let" | ||
| 260 | "namespace" | ||
| 261 | "new" | ||
| 262 | "of" | ||
| 263 | "private" | ||
| 264 | "protected" | ||
| 265 | "public" | ||
| 266 | "readonly" | ||
| 267 | "return" | ||
| 268 | "set" | ||
| 269 | "static" | ||
| 270 | "switch" | ||
| 271 | "target" | ||
| 272 | "throw" | ||
| 273 | "try" | ||
| 274 | "type" | ||
| 275 | "typeof" | ||
| 276 | "var" | ||
| 277 | "void" | ||
| 278 | "while" | ||
| 279 | "with" | ||
| 280 | "yield" | ||
| 281 | ] @font-lock-keyword-face | ||
| 282 | |||
| 283 | (comment) @font-lock-comment-face | ||
| 284 | ))) | ||
| 285 | |||
| 286 | (defvar ts-mode--defun-type-regexp | ||
| 287 | (rx (or "class_declaration" | ||
| 288 | "method_definition" | ||
| 289 | "function_declaration" | ||
| 290 | "lexical_declaration")) | ||
| 291 | "Regular expression that matches type of defun nodes. | ||
| 292 | Used in `ts-mode--beginning-of-defun' and friends.") | ||
| 293 | |||
| 294 | (defun ts-mode--beginning-of-defun (&optional arg) | ||
| 295 | "Tree-sitter `beginning-of-defun' function. | ||
| 296 | ARG is the same as in `beginning-of-defun." | ||
| 297 | (let ((arg (or arg 1))) | ||
| 298 | (if (> arg 0) | ||
| 299 | ;; Go backward. | ||
| 300 | (while (and (> arg 0) | ||
| 301 | (treesit-search-forward-goto | ||
| 302 | ts-mode--defun-type-regexp 'start nil t)) | ||
| 303 | (setq arg (1- arg))) | ||
| 304 | ;; Go forward. | ||
| 305 | (while (and (< arg 0) | ||
| 306 | (treesit-search-forward-goto | ||
| 307 | ts-mode--defun-type-regexp 'start)) | ||
| 308 | (setq arg (1+ arg)))))) | ||
| 309 | |||
| 310 | (defun ts-mode--end-of-defun (&optional arg) | ||
| 311 | "Tree-sitter `end-of-defun' function. | ||
| 312 | ARG is the same as in `end-of-defun." | ||
| 313 | (let ((arg (or arg 1))) | ||
| 314 | (if (< arg 0) | ||
| 315 | ;; Go backward. | ||
| 316 | (while (and (< arg 0) | ||
| 317 | (treesit-search-forward-goto | ||
| 318 | ts-mode--defun-type-regexp 'end nil t)) | ||
| 319 | (setq arg (1+ arg))) | ||
| 320 | ;; Go forward. | ||
| 321 | (while (and (> arg 0) | ||
| 322 | (treesit-search-forward-goto | ||
| 323 | ts-mode--defun-type-regexp 'end)) | ||
| 324 | (setq arg (1- arg)))))) | ||
| 325 | |||
| 326 | ;;;###autoload | ||
| 327 | (add-to-list 'auto-mode-alist '("\\.ts\\'" . ts-mode)) | ||
| 328 | |||
| 329 | ;;;###autoload | ||
| 330 | (add-to-list 'auto-mode-alist '("\\.tsx\\'" . ts-mode)) | ||
| 331 | |||
| 332 | ;;;###autoload | ||
| 333 | (define-derived-mode ts-mode prog-mode "TypeScript" | ||
| 334 | "Major mode for editing TypeScript." | ||
| 335 | :group 'typescript | ||
| 336 | :syntax-table ts-mode--syntax-table | ||
| 337 | |||
| 338 | (cond | ||
| 339 | ((and (treesit-can-enable-p) | ||
| 340 | (treesit-language-available-p 'tsx)) | ||
| 341 | ;; Comments | ||
| 342 | (setq-local comment-start "// ") | ||
| 343 | (setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *") | ||
| 344 | (setq-local comment-end "") | ||
| 345 | |||
| 346 | (setq-local treesit-simple-indent-rules ts-mode--indent-rules) | ||
| 347 | (setq-local indent-line-function #'treesit-indent) | ||
| 348 | |||
| 349 | (setq-local beginning-of-defun-function #'ts-mode--beginning-of-defun) | ||
| 350 | (setq-local end-of-defun-function #'ts-mode--end-of-defun) | ||
| 351 | |||
| 352 | (unless font-lock-defaults | ||
| 353 | (setq font-lock-defaults '(nil t))) | ||
| 354 | |||
| 355 | (setq-local treesit-font-lock-settings ts-mode--settings) | ||
| 356 | |||
| 357 | (treesit-font-lock-enable)) | ||
| 358 | (t | ||
| 359 | (message "Tree sitter for TypeScript isn't available, defaulting to js-mode") | ||
| 360 | (js-mode)))) | ||
| 361 | |||
| 362 | (provide 'ts-mode) | ||
| 363 | |||
| 364 | ;;; ts-mode.el ends here | ||