dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

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