branch: externals/csharp-mode commit 3cff337b41f39eebe77185d8f07dcd1db2f2c405 Merge: 76bbf26 00a3cd4 Author: Theodor Thornhill <t...@thornhill.no> Commit: GitHub <nore...@github.com>
Merge pull request #204 from emacs-csharp/tree-sitter Tree sitter support for csharp mode --- README.org | 26 +++- csharp-tree-sitter.el | 291 ++++++++++++++++++++++++++++++++++++++++ test-files/indentation-tests.cs | 2 - 3 files changed, 311 insertions(+), 8 deletions(-) diff --git a/README.org b/README.org index 4b9fcb9..4e82552 100644 --- a/README.org +++ b/README.org @@ -4,8 +4,7 @@ * csharp-mode -This is a mode for editing C# in emacs. It's based on cc-mode, v5.30.3 and above. - +This is a mode for editing C# in emacs. It's using [[https://github.com/ubolonton/emacs-tree-sitter][tree-sitter]] for highlighting and indentation. ** Main features - font-lock and indent of C# syntax including: @@ -17,11 +16,26 @@ This is a mode for editing C# in emacs. It's based on cc-mode, v5.30.3 and above - anonymous functions and methods - verbatim literal strings (those that begin with @) - generics -- automagic code-doc generation when you type three slashes. - intelligent insertion of matched pairs of curly braces. -- imenu indexing of C# source, for easy menu-based navigation. - compilation-mode support for msbuild, devenv and xbuild. +** tree-sitter support +You can enable experimental tree sitter support for indentation and highlighting using +#+begin_src elisp + (use-package tree-sitter) + (use-package tree-sitter-langs) + + (use-package csharp-mode + :straight + (csharp-mode :type git + :host github + :repo "emacs-csharp/csharp-mode" + :branch "tree-sitter") + :config + (add-to-list 'auto-mode-alist '("\\.cs\\'" . csharp-tree-sitter-mode))) +#+end_src +If you are using this, clearly state so if you find any issues. + ** Usage This package is currently available on MELPA. Install using ~M-x @@ -44,10 +58,10 @@ To do so, add the following to your .emacs-file: (add-hook 'csharp-mode-hook 'my-csharp-mode-hook) #+END_SRC -For further mode-specific customization, ~M-x customize-group RET csharp RET~ will show available settings with documentation. For configuring ~cc-mode~ settings (which csharp-mode derives from) see the [[https://www.gnu.org/software/emacs/manual/html_mono/ccmode.html][cc-mode manual]]. +For further mode-specific customization, ~M-x customize-group RET csharp RET~ will show available settings with documentation. For more advanced and IDE-like functionality we recommend using csharp-mode together -with [[https://github.com/OmniSharp/omnisharp-emacs][Omnisharp-Emacs]]. +with [[https://github.com/emacs-lsp/lsp-mode][lsp-mode]] or [[https://github.com/joaotavora/eglot][eglot]] * Attribution diff --git a/csharp-tree-sitter.el b/csharp-tree-sitter.el new file mode 100644 index 0000000..90f633a --- /dev/null +++ b/csharp-tree-sitter.el @@ -0,0 +1,291 @@ +;;; csharp-tree-sitter.el --- tree sitter support for C# -*- lexical-binding: t; -*- + +;; Author : Theodor Thornhill <t...@thornhill.no> +;; Maintainer : Jostein Kjønigsen <jost...@gmail.com> +;; : Theodor Thornhill <t...@thornhill.no> +;; Created : September 2020 +;; Modified : 2020 +;; Version : 0.10.0 +;; Keywords : c# languages oop mode +;; X-URL : https://github.com/emacs-csharp/csharp-mode +;; Package-Requires: ((emacs "26.1") (tree-sitter "0.12.1") (tree-sitter-indent "0.1")) + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + + +;;; Code: +(require 'cl-lib) +(require 'seq) +(require 'tree-sitter) +(require 'tree-sitter-hl) +(require 'tree-sitter-indent) + +;;; Tree-sitter + +(defvar-local csharp-mode-tree-sitter-patterns + [ ;; Various constructs + (comment) @comment + (modifier) @keyword + (this_expression) @keyword + + ;; Literals + [(real_literal) (integer_literal)] @number + (null_literal) @constant + (boolean_literal) @constant + (character_literal) @string + + ;; Keywords + ["using" "namespace" "class" "if" "else" "throw" "new" "for" + "return" "await" "struct" "enum" "switch" "case" + "default" "typeof" "try" "catch" "finally" "break" + "foreach" "in" "yield" "get" "set" "when" "as" "out" + "is" "while" "continue" "this" "ref" "goto" "interface" + "from" "where" "select" + ] @keyword + + ;; Linq + (from_clause (identifier) @variable) + (group_clause) + (order_by_clause) + (select_clause (identifier) @variable) + (query_continuation (identifier) @variable) @keyword + + ;; String + (interpolation (identifier) (interpolation_format_clause) @variable) + (interpolation (identifier)* @variable) + [(string_literal) (verbatim_string_literal) (interpolated_string_expression)] @string + + ;; Enum + (enum_member_declaration (identifier) @variable) + (enum_declaration (identifier) @type) + + ;; Interface + (interface_declaration + name: (identifier) @type) + + ;; Struct + (struct_declaration (identifier) @type) + + ;; Namespace + (namespace_declaration + name: (identifier) @type) + + ;; Class + (base_list (identifier) @type) + (property_declaration + type: (nullable_type) @type + name: (identifier) @variable) + (property_declaration + type: (predefined_type) @type + name: (identifier) @variable) + (property_declaration + type: (identifier) @type + name: (identifier) @variable) + (class_declaration + name: (identifier) @type) + (constructor_declaration (identifier) @type) + + ;; Method + (method_declaration (identifier) @type (identifier) @function) + (method_declaration (predefined_type) @type (identifier) @function) + (method_declaration (nullable_type) @type (identifier) @function) + (method_declaration (void_keyword) @type (identifier) @function) + (method_declaration (generic_name) (identifier) @function) + + ;; Function + (local_function_statement (identifier) @type (identifier) @function) + (local_function_statement (predefined_type) @type (identifier) @function) + (local_function_statement (nullable_type) @type (identifier) @function) + (local_function_statement (void_keyword) @type (identifier) @function) + (local_function_statement (generic_name) (identifier) @function) + + ;; Parameter + (parameter + type: (identifier) @type + name: (identifier) @variable) + (parameter (identifier) @variable) + + ;; Array + (array_rank_specifier (identifier) @variable) + (array_type (identifier) @type) + (array_creation_expression) + + ;; Attribute + (attribute (identifier) @variable (attribute_argument_list)) + (attribute (identifier) @variable) + + ;; Object init + (anonymous_object_creation_expression) + (object_creation_expression (identifier) @type) + (initializer_expression (identifier) @variable) + + ;; Variable + (variable_declaration (identifier) @type) + (variable_declarator (identifier) @variable) + + ;; Equals value + (equals_value_clause (identifier) @variable) + + ;; Return + (return_statement (identifier) @variable) + (yield_statement (identifier) @variable) + + ;; Type + (type_parameter + (identifier) @type) + (type_argument_list + (identifier) @type) + (generic_name + (identifier) @type) + (implicit_type) @type + (predefined_type) @type + (nullable_type) @type + ["operator"] @type + + ;; Exprs + (binary_expression (identifier) @variable (identifier) @variable) + (binary_expression (identifier)* @variable) + (conditional_expression (identifier) @variable) + (prefix_unary_expression (identifier)* @variable) + (postfix_unary_expression (identifier)* @variable) + (type_of_expression (identifier) @variable) + (assignment_expression (identifier) @variable) + (cast_expression (identifier) @type) + + ;; Preprocessor + (preprocessor_directive) @constant + (preprocessor_call (identifier) @string) + + ;; Loop + (for_each_statement (identifier) @type (identifier) @variable) + (for_each_statement (implicit_type) @type (identifier) @variable) + (for_each_statement (predefined_type) @type (identifier) @variable) + + ;; Exception + (catch_declaration (identifier) @type (identifier) @variable) + (catch_declaration (identifier) @type) + + ;; Switch + (switch_statement (identifier) @variable) + (switch_expression (identifier) @variable) + + ;; If + (if_statement (identifier) @variable) + + ;; Declaration expression + (declaration_expression (implicit_type) (identifier) @variable) + + ;; Arrow expression + (arrow_expression_clause (identifier) @variable) + + ;; Other + (label_name) @variable + (qualified_name (identifier) @type) + (using_directive (identifier)* @type) + (await_expression (identifier)* @function) + (invocation_expression (identifier) @function) + (element_access_expression (identifier) @variable) + (conditional_access_expression (identifier) @variable) + (member_binding_expression (identifier) @variable) + (member_access_expression (identifier) @function) + (name_colon (identifier)* @variable) + (name_equals (identifier) @type) + (field_declaration) + (argument (identifier) @variable) + ] + "Default patterns for tree-sitter support.") + +;;; Tree-sitter indentation + +(defgroup csharp-mode-indent nil "Indent lines using Tree-sitter as backend" + :group 'tree-sitter) + +(defcustom csharp-mode-indent-offset 4 + "Indent offset for csharp-mode" + :type 'integer + :group 'csharp) + +(defvar csharp-mode-indent-scopes + '((indent-all . ;; these nodes are always indented + (accessor_declaration + break_statement + arrow_expression_clause + parameter_list + conditional_expression + ".")) + (indent-rest . ;; if parent node is one of these and node is not first → indent + ( + binary_expression + switch_section + )) + (indent-body . ;; if parent node is one of these and current node is in middle → indent + (block + anonymous_object_creation_expression + enum_member_declaration_list + initializer_expression + expression_statement + declaration_list + attribute_argument_list + switch_body)) + + (paren-indent . ;; if parent node is one of these → indent to paren opener + (parenthesized_expression)) + (align-char-to . ;; chaining char → node types we move parentwise to find the first chaining char + ()) + (aligned-siblings . ;; siblings (nodes with same parent) should be aligned to the first child + (parameter)) + + (multi-line-text . ;; if node is one of these, then don't modify the indent + ;; this is basically a peaceful way out by saying "this looks like something + ;; that cannot be indented using AST, so best I leave it as-is" + (comment + preprocessor_call + labeled_statement)) + (outdent . ;; these nodes always outdent (1 shift in opposite direction) + (;; "}" + case_switch_label + )) + ) + "Scopes for indenting in C#.") + +;;;###autoload +(define-derived-mode csharp-tree-sitter-mode prog-mode "C#" + "Major mode for editing Csharp code. + +Key bindings: +\\{csharp-mode-map}" + :group 'csharp + + (setq csharp-mode-syntax-table nil) + (setq csharp-mode-map nil) + (setq-local tree-sitter-indent-current-scopes csharp-mode-indent-scopes) + (setq-local tree-sitter-indent-offset csharp-mode-indent-offset) + (setq-local indent-line-function #'tree-sitter-indent-line) + + ;; https://github.com/ubolonton/emacs-tree-sitter/issues/84 + (unless font-lock-defaults + (setq font-lock-defaults '(nil))) + (setq-local tree-sitter-hl-default-patterns csharp-mode-tree-sitter-patterns) + ;; Comments + (setq-local comment-start "// ") + (setq-local comment-end "") + + (tree-sitter-hl-mode)) + +;;;###autoload +(add-to-list 'tree-sitter-major-mode-language-alist '(csharp-tree-sitter-mode . c-sharp)) + +(provide 'csharp-tree-sitter) + +;;; csharp-tree-sitter.el ends here diff --git a/test-files/indentation-tests.cs b/test-files/indentation-tests.cs index e0355f9..650ac1e 100644 --- a/test-files/indentation-tests.cs +++ b/test-files/indentation-tests.cs @@ -119,7 +119,6 @@ namespace Boo PropB = 2 }; - // Commented out for rework -- Theodor Thornhill // yield return new InnerA.InnerB { // PropA = 1, // PropB = 2 @@ -131,7 +130,6 @@ namespace Boo May = "Yay" }; - // Commented out for rework -- Theodor Thornhill // yield return new InnerA.InnerB // { // Boo = "Foo",