csharp-tree-sitter.el (14106B)
1 ;;; csharp-tree-sitter.el --- tree sitter support for C# -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2020-2021 Free Software Foundation, Inc. 4 5 ;; Author : Theodor Thornhill <theo@thornhill.no> 6 ;; Maintainer : Jostein Kjønigsen <jostein@gmail.com> 7 ;; Theodor Thornhill <theo@thornhill.no> 8 ;; Created : September 2020 9 ;; Modified : 2020 10 ;; Version : 1.1.1 11 ;; Keywords : c# languages oop mode 12 ;; X-URL : https://github.com/emacs-csharp/csharp-mode 13 ;; Package-Requires: ((emacs "26.1") (tree-sitter "0.12.1") (tree-sitter-indent "0.1") (tree-sitter-langs "0.9.1")) 14 15 ;; This program is free software; you can redistribute it and/or modify 16 ;; it under the terms of the GNU General Public License as published by 17 ;; the Free Software Foundation, either version 3 of the License, or 18 ;; (at your option) any later version. 19 20 ;; This program is distributed in the hope that it will be useful, 21 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 22 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 ;; GNU General Public License for more details. 24 25 ;; You should have received a copy of the GNU General Public License 26 ;; along with this program. If not, see <http://www.gnu.org/licenses/>. 27 28 29 ;;; Code: 30 (require 'cl-lib) 31 (require 'cl-extra) 32 (require 'seq) 33 34 (when t 35 ;; In order for the package to be usable and installable (and hence 36 ;; compilable) without tree-sitter, wrap the `require's within a dummy `when' 37 ;; so they're only executed when loading this file but not when compiling it. 38 (require 'tree-sitter) 39 (require 'tree-sitter-hl) 40 (require 'tree-sitter-indent) 41 (require 'tree-sitter-langs)) 42 ;; Vars and functions defined by the above packages: 43 (defvar tree-sitter-major-mode-language-alist) ;From `tree-sitter-langs'. 44 (declare-function tree-sitter-indent-mode "ext:tree-sitter-indent") 45 (declare-function tree-sitter-indent-line "ext:tree-sitter-indent") 46 (declare-function tree-sitter-hl-mode "ext:tree-sitter-hl") 47 (declare-function tsc-node-end-position "ext:tree-sitter") 48 (declare-function tsc-node-start-position "ext:tree-sitter") 49 (declare-function tree-sitter-node-at-point "ext:tree-sitter") 50 51 (require 'csharp-compilation) 52 53 (defvar csharp-mode-syntax-table) 54 (defvar csharp-mode-map) 55 56 ;;; Tree-sitter 57 58 (defconst csharp-mode-tree-sitter-patterns 59 [ ;; Various constructs 60 (comment) @comment 61 (modifier) @keyword 62 (this_expression) @keyword 63 (escape_sequence) @keyword 64 65 ;; Literals 66 [(real_literal) (integer_literal)] @number 67 (null_literal) @constant 68 (boolean_literal) @constant 69 [(string_literal) 70 (verbatim_string_literal) 71 (interpolated_string_text) 72 (interpolated_verbatim_string_text) 73 (character_literal) 74 "\"" 75 "$\"" 76 "@$\"" 77 "$@\""] @string 78 79 ;; Keywords 80 ["using" "namespace" "class" "if" "else" "throw" "new" "for" 81 "return" "await" "struct" "enum" "switch" "case" 82 "default" "typeof" "try" "catch" "finally" "break" 83 "foreach" "in" "yield" "get" "set" "when" "as" "out" 84 "is" "while" "continue" "this" "ref" "goto" "interface" 85 "from" "where" "select" "lock" "base" "record" "init" 86 "with" "let" "static" 87 ] @keyword 88 89 ;; Linq 90 (from_clause (identifier) @variable) 91 (group_clause) 92 (order_by_clause) 93 (select_clause (identifier) @variable) 94 (query_continuation (identifier) @variable) @keyword 95 96 ;; Enum 97 (enum_member_declaration (identifier) @variable) 98 (enum_declaration (identifier) @type) 99 100 ;; Interface 101 (interface_declaration 102 name: (identifier) @type) 103 104 ;; Struct 105 (struct_declaration (identifier) @type) 106 107 ;; Record 108 (record_declaration (identifier) @type) 109 110 (with_expression 111 (with_initializer_expression 112 (simple_assignment_expression 113 (identifier) @variable))) 114 115 ;; Namespace 116 (namespace_declaration 117 name: (identifier) @type) 118 119 ;; Class 120 (base_list (identifier) @type) 121 (property_declaration 122 (generic_name)) 123 (property_declaration 124 type: (nullable_type) @type 125 name: (identifier) @variable) 126 (property_declaration 127 type: (predefined_type) @type 128 name: (identifier) @variable) 129 (property_declaration 130 type: (identifier) @type 131 name: (identifier) @variable) 132 (class_declaration 133 name: (identifier) @type) 134 (constructor_declaration (identifier) @type) 135 136 ;; Method 137 (method_declaration (identifier) @type (identifier) @function) 138 (method_declaration (predefined_type) @type (identifier) @function) 139 (method_declaration (nullable_type) @type (identifier) @function) 140 (method_declaration (void_keyword) @type (identifier) @function) 141 (method_declaration (generic_name) (identifier) @function) 142 (method_declaration (qualified_name (identifier) @type) (identifier) @function) 143 144 ;; Function 145 (local_function_statement (identifier) @type (identifier) @function) 146 (local_function_statement (predefined_type) @type (identifier) @function) 147 (local_function_statement (nullable_type) @type (identifier) @function) 148 (local_function_statement (void_keyword) @type (identifier) @function) 149 (local_function_statement (generic_name) (identifier) @function) 150 151 ;; Lambda 152 (lambda_expression 153 (identifier) @variable) 154 155 ;; Parameter 156 (parameter 157 type: (qualified_name) @type) 158 (parameter 159 type: (identifier) @type 160 name: (identifier) @variable.parameter) 161 (parameter (identifier) @variable.parameter) 162 163 ;; Array 164 (array_rank_specifier (identifier) @variable) 165 (array_type (identifier) @type) 166 (array_creation_expression) 167 168 ;; Attribute 169 (attribute (identifier) @variable (attribute_argument_list)) 170 (attribute (identifier) @variable) 171 172 ;; Object init 173 (anonymous_object_creation_expression) 174 (object_creation_expression (identifier) @type) 175 (initializer_expression (identifier) @variable) 176 177 ;; Typeof 178 (type_of_expression (identifier) @variable) 179 180 ;; Member access 181 (invocation_expression (member_access_expression (generic_name (identifier) @method.call))) 182 (invocation_expression (member_access_expression (identifier)\? @method.call .)) 183 (member_access_expression (identifier) @variable) 184 185 ;; Variable 186 (variable_declaration (identifier) @type) 187 (variable_declarator (identifier) @variable) 188 189 ;; Equals value 190 (equals_value_clause (identifier) @variable) 191 192 ;; Return 193 (return_statement (identifier) @variable) 194 (yield_statement (identifier) @variable) 195 196 ;; Type 197 (type_parameter 198 (identifier) @type) 199 (type_argument_list 200 (identifier) @type.argument) 201 (generic_name 202 (identifier) @type) 203 (implicit_type) @type 204 (predefined_type) @type 205 (nullable_type) @type 206 ["operator"] @type 207 208 ;; Type constraints 209 (type_parameter_constraints_clause 210 (identifier) @type) 211 ;; (type_parameter_constraint 212 ;; (identifier) @type) ;; causes parsing error in tree-sitter 213 (type_constraint 214 (identifier) @type) 215 216 ;; Exprs 217 (binary_expression (identifier) @variable (identifier) @variable) 218 (binary_expression (identifier)* @variable) 219 (conditional_expression (identifier) @variable) 220 ;; (prefix_unary_expression (identifier)* @variable) ;; crashes tree-sitter c-code with SIGABRT 221 (postfix_unary_expression (identifier)* @variable) 222 (assignment_expression (identifier) @variable) 223 (cast_expression (identifier) @type) 224 225 ;; Preprocessor 226 (preprocessor_directive) @constant 227 (preprocessor_call (identifier) @string) 228 229 ;; Loop 230 (for_each_statement (implicit_type) @type (identifier) @variable) 231 (for_each_statement (predefined_type) @type (identifier) @variable) 232 (for_each_statement (identifier) @type (identifier) @variable) 233 234 ;; Exception 235 (catch_declaration (identifier) @type (identifier) @variable) 236 (catch_declaration (identifier) @type) 237 238 ;; Switch 239 (switch_statement (identifier) @variable) 240 (switch_expression (identifier) @variable) 241 242 ;; If 243 (if_statement (identifier) @variable) 244 245 ;; Declaration expression 246 (declaration_expression (implicit_type) (identifier) @variable) 247 248 ;; Arrow expression 249 (arrow_expression_clause (identifier) @variable) 250 251 ;; Lock statement 252 (lock_statement (identifier) @variable) 253 254 ;; Other 255 ;; (argument_list 256 ;; (identifier) @variable) ;; causes parsing error in tree-sitter 257 (label_name) @variable 258 (using_directive (identifier) @type.parameter) 259 (using_directive (qualified_name) @type.parameter) 260 (using_directive (name_equals (identifier) @type.parameter)) 261 ;; (await_expression (identifier)* @function) ;; crashes tree-sitter c-code with sigabrt! 262 (invocation_expression (identifier) @function) 263 (element_access_expression (identifier) @variable) 264 (conditional_access_expression (identifier) @variable) 265 (member_binding_expression (identifier) @variable) 266 (name_colon (identifier)* @variable.special) 267 (field_declaration) 268 (argument (identifier) @variable) 269 270 ;; Catch-alls 271 (identifier) @variable 272 273 ;; Interpolation 274 ;; (interpolated_string_expression) @string 275 ] 276 ) 277 278 ;;; Tree-sitter indentation 279 280 (defgroup csharp-mode-indent nil "Indent lines using Tree-sitter as backend" 281 :group 'tree-sitter) 282 283 (defcustom csharp-tree-sitter-indent-offset 4 284 "Indent offset for csharp-tree-sitter-mode." 285 :type 'integer 286 :group 'csharp) 287 288 (defvar tree-sitter-indent-csharp-tree-sitter-scopes 289 '((indent-all 290 ;; these nodes are always indented 291 . (accessor_declaration 292 break_statement 293 arrow_expression_clause 294 parameter_list 295 conditional_expression 296 constructor_initializer 297 argument_list 298 ".")) 299 (indent-rest 300 ;; if parent node is one of these and node is not first → indent 301 . (binary_expression 302 switch_section)) 303 (indent-body 304 ;; if parent node is one of these and current node is in middle → indent 305 . (enum_member_declaration_list 306 base_list 307 block 308 anonymous_object_creation_expression 309 initializer_expression 310 expression_statement 311 declaration_list 312 attribute_argument_list 313 switch_body 314 switch_expression)) 315 (paren-indent 316 ;; if parent node is one of these → indent to paren opener 317 . (parenthesized_expression)) 318 (align-char-to 319 ;; chaining char → node types we move parentwise to find the first chaining char 320 . ()) 321 (aligned-siblings 322 ;; siblings (nodes with same parent) should be aligned to the first child 323 . (parameter 324 argument)) 325 (multi-line-text 326 ;; if node is one of these, then don't modify the indent 327 ;; this is basically a peaceful way out by saying "this looks like something 328 ;; that cannot be indented using AST, so best I leave it as-is" 329 . (preprocessor_call 330 labeled_statement)) 331 (outdent 332 ;; these nodes always outdent (1 shift in opposite direction) 333 . (case_switch_label)) 334 (align-to-node-line 335 ;; this group has lists of alist (node type . (node types... )) 336 ;; we move parentwise, searching for one of the node 337 ;; types associated with the key node type. if found, 338 ;; align key node with line where the ancestor node 339 ;; was found. 340 . ((block . (lambda_expression))))) 341 "Scopes for indenting in C#.") 342 343 ;;; tree-sitter helper-functions. navigation, editing, etc. 344 ;;; may be subject to future upstreaming-effort 345 346 (defun csharp-beginning-of-defun () 347 "Replacement-function for `beginning-of-defun' for `csharp-tree-sitter-mode'." 348 (interactive) 349 (when-let ((declaration 350 (cl-some (lambda (decl) 351 (tree-sitter-node-at-point decl)) 352 '(method_declaration 353 constructor_declaration 354 class_declaration 355 namespace_declaration)))) 356 (goto-char (tsc-node-start-position declaration)))) 357 358 (defun csharp-end-of-defun () 359 "Replacement-function for `end-of-defun' for `csharp-tree-sitter-mode'." 360 (interactive) 361 (when-let ((declaration 362 (cl-some (lambda (decl) 363 (tree-sitter-node-at-point decl)) 364 '(method_declaration 365 constructor_declaration 366 class_declaration 367 namespace_declaration)))) 368 (goto-char (tsc-node-end-position declaration)))) 369 370 (defun csharp-delete-method-at-point () 371 "Deletes the method at point." 372 (interactive) 373 (when-let ((method (tree-sitter-node-at-point 'method_declaration))) 374 (delete-region (tsc-node-start-position method) 375 (tsc-node-end-position method)))) 376 377 (defun csharp-change-string-at-point () 378 "Change string at point." 379 (interactive) 380 (when-let ((method (tree-sitter-node-at-point 'string_literal))) 381 (delete-region (1+ (tsc-node-start-position method)) 382 (1- (tsc-node-end-position method))))) 383 384 ;;; end tree-sitter helper-functions. 385 386 (defvar csharp-tree-sitter-mode-map 387 (let ((map (make-sparse-keymap))) 388 map) 389 "Keymap used in csharp-mode buffers.") 390 391 (defvar csharp-tree-sitter-mode-syntax-table 392 (let ((table (make-syntax-table))) 393 (modify-syntax-entry ?@ "_" table) 394 table)) 395 396 ;;;###autoload 397 (define-derived-mode csharp-tree-sitter-mode prog-mode "C#" 398 "Major mode for editing Csharp code. 399 400 Key bindings: 401 \\{csharp-tree-sitter-mode-map}" 402 :group 'csharp 403 :syntax-table csharp-tree-sitter-mode-syntax-table 404 405 (setq-local indent-line-function #'tree-sitter-indent-line) 406 (setq-local beginning-of-defun-function #'csharp-beginning-of-defun) 407 (setq-local end-of-defun-function #'csharp-end-of-defun) 408 409 ;; https://github.com/ubolonton/emacs-tree-sitter/issues/84 410 (unless font-lock-defaults 411 (setq font-lock-defaults '(nil))) 412 (setq-local tree-sitter-hl-default-patterns csharp-mode-tree-sitter-patterns) 413 ;; Comments 414 (setq-local comment-start "// ") 415 (setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *") 416 (setq-local comment-end "") 417 418 (tree-sitter-hl-mode) 419 (tree-sitter-indent-mode)) 420 421 (add-to-list 'tree-sitter-major-mode-language-alist '(csharp-tree-sitter-mode . c-sharp)) 422 423 (provide 'csharp-tree-sitter) 424 425 ;;; csharp-tree-sitter.el ends here