Skip to content

Commit 3cff337

Browse files
Merge pull request #204 from emacs-csharp/tree-sitter
Tree sitter support for csharp mode
2 parents 76bbf26 + 00a3cd4 commit 3cff337

File tree

3 files changed

+311
-8
lines changed

3 files changed

+311
-8
lines changed

README.org

+20-6
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44

55
* csharp-mode
66

7-
This is a mode for editing C# in emacs. It's based on cc-mode, v5.30.3 and above.
8-
7+
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.
98
** Main features
109

1110
- 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
1716
- anonymous functions and methods
1817
- verbatim literal strings (those that begin with @)
1918
- generics
20-
- automagic code-doc generation when you type three slashes.
2119
- intelligent insertion of matched pairs of curly braces.
22-
- imenu indexing of C# source, for easy menu-based navigation.
2320
- compilation-mode support for msbuild, devenv and xbuild.
2421

22+
** tree-sitter support
23+
You can enable experimental tree sitter support for indentation and highlighting using
24+
#+begin_src elisp
25+
(use-package tree-sitter)
26+
(use-package tree-sitter-langs)
27+
28+
(use-package csharp-mode
29+
:straight
30+
(csharp-mode :type git
31+
:host github
32+
:repo "emacs-csharp/csharp-mode"
33+
:branch "tree-sitter")
34+
:config
35+
(add-to-list 'auto-mode-alist '("\\.cs\\'" . csharp-tree-sitter-mode)))
36+
#+end_src
37+
If you are using this, clearly state so if you find any issues.
38+
2539
** Usage
2640

2741
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:
4458
(add-hook 'csharp-mode-hook 'my-csharp-mode-hook)
4559
#+END_SRC
4660

47-
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]].
61+
For further mode-specific customization, ~M-x customize-group RET csharp RET~ will show available settings with documentation.
4862

4963
For more advanced and IDE-like functionality we recommend using csharp-mode together
50-
with [[https://github.com/OmniSharp/omnisharp-emacs][Omnisharp-Emacs]].
64+
with [[https://github.com/emacs-lsp/lsp-mode][lsp-mode]] or [[https://github.com/joaotavora/eglot][eglot]]
5165

5266
* Attribution
5367

csharp-tree-sitter.el

+291
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
;;; csharp-tree-sitter.el --- tree sitter support for C# -*- lexical-binding: t; -*-
2+
3+
;; Author : Theodor Thornhill <theo@thornhill.no>
4+
;; Maintainer : Jostein Kjønigsen <jostein@gmail.com>
5+
;; : Theodor Thornhill <theo@thornhill.no>
6+
;; Created : September 2020
7+
;; Modified : 2020
8+
;; Version : 0.10.0
9+
;; Keywords : c# languages oop mode
10+
;; X-URL : https://github.com/emacs-csharp/csharp-mode
11+
;; Package-Requires: ((emacs "26.1") (tree-sitter "0.12.1") (tree-sitter-indent "0.1"))
12+
13+
;; This program is free software; you can redistribute it and/or modify
14+
;; it under the terms of the GNU General Public License as published by
15+
;; the Free Software Foundation, either version 3 of the License, or
16+
;; (at your option) any later version.
17+
18+
;; This program is distributed in the hope that it will be useful,
19+
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21+
;; GNU General Public License for more details.
22+
23+
;; You should have received a copy of the GNU General Public License
24+
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
25+
26+
27+
;;; Code:
28+
(require 'cl-lib)
29+
(require 'seq)
30+
(require 'tree-sitter)
31+
(require 'tree-sitter-hl)
32+
(require 'tree-sitter-indent)
33+
34+
;;; Tree-sitter
35+
36+
(defvar-local csharp-mode-tree-sitter-patterns
37+
[ ;; Various constructs
38+
(comment) @comment
39+
(modifier) @keyword
40+
(this_expression) @keyword
41+
42+
;; Literals
43+
[(real_literal) (integer_literal)] @number
44+
(null_literal) @constant
45+
(boolean_literal) @constant
46+
(character_literal) @string
47+
48+
;; Keywords
49+
["using" "namespace" "class" "if" "else" "throw" "new" "for"
50+
"return" "await" "struct" "enum" "switch" "case"
51+
"default" "typeof" "try" "catch" "finally" "break"
52+
"foreach" "in" "yield" "get" "set" "when" "as" "out"
53+
"is" "while" "continue" "this" "ref" "goto" "interface"
54+
"from" "where" "select"
55+
] @keyword
56+
57+
;; Linq
58+
(from_clause (identifier) @variable)
59+
(group_clause)
60+
(order_by_clause)
61+
(select_clause (identifier) @variable)
62+
(query_continuation (identifier) @variable) @keyword
63+
64+
;; String
65+
(interpolation (identifier) (interpolation_format_clause) @variable)
66+
(interpolation (identifier)* @variable)
67+
[(string_literal) (verbatim_string_literal) (interpolated_string_expression)] @string
68+
69+
;; Enum
70+
(enum_member_declaration (identifier) @variable)
71+
(enum_declaration (identifier) @type)
72+
73+
;; Interface
74+
(interface_declaration
75+
name: (identifier) @type)
76+
77+
;; Struct
78+
(struct_declaration (identifier) @type)
79+
80+
;; Namespace
81+
(namespace_declaration
82+
name: (identifier) @type)
83+
84+
;; Class
85+
(base_list (identifier) @type)
86+
(property_declaration
87+
type: (nullable_type) @type
88+
name: (identifier) @variable)
89+
(property_declaration
90+
type: (predefined_type) @type
91+
name: (identifier) @variable)
92+
(property_declaration
93+
type: (identifier) @type
94+
name: (identifier) @variable)
95+
(class_declaration
96+
name: (identifier) @type)
97+
(constructor_declaration (identifier) @type)
98+
99+
;; Method
100+
(method_declaration (identifier) @type (identifier) @function)
101+
(method_declaration (predefined_type) @type (identifier) @function)
102+
(method_declaration (nullable_type) @type (identifier) @function)
103+
(method_declaration (void_keyword) @type (identifier) @function)
104+
(method_declaration (generic_name) (identifier) @function)
105+
106+
;; Function
107+
(local_function_statement (identifier) @type (identifier) @function)
108+
(local_function_statement (predefined_type) @type (identifier) @function)
109+
(local_function_statement (nullable_type) @type (identifier) @function)
110+
(local_function_statement (void_keyword) @type (identifier) @function)
111+
(local_function_statement (generic_name) (identifier) @function)
112+
113+
;; Parameter
114+
(parameter
115+
type: (identifier) @type
116+
name: (identifier) @variable)
117+
(parameter (identifier) @variable)
118+
119+
;; Array
120+
(array_rank_specifier (identifier) @variable)
121+
(array_type (identifier) @type)
122+
(array_creation_expression)
123+
124+
;; Attribute
125+
(attribute (identifier) @variable (attribute_argument_list))
126+
(attribute (identifier) @variable)
127+
128+
;; Object init
129+
(anonymous_object_creation_expression)
130+
(object_creation_expression (identifier) @type)
131+
(initializer_expression (identifier) @variable)
132+
133+
;; Variable
134+
(variable_declaration (identifier) @type)
135+
(variable_declarator (identifier) @variable)
136+
137+
;; Equals value
138+
(equals_value_clause (identifier) @variable)
139+
140+
;; Return
141+
(return_statement (identifier) @variable)
142+
(yield_statement (identifier) @variable)
143+
144+
;; Type
145+
(type_parameter
146+
(identifier) @type)
147+
(type_argument_list
148+
(identifier) @type)
149+
(generic_name
150+
(identifier) @type)
151+
(implicit_type) @type
152+
(predefined_type) @type
153+
(nullable_type) @type
154+
["operator"] @type
155+
156+
;; Exprs
157+
(binary_expression (identifier) @variable (identifier) @variable)
158+
(binary_expression (identifier)* @variable)
159+
(conditional_expression (identifier) @variable)
160+
(prefix_unary_expression (identifier)* @variable)
161+
(postfix_unary_expression (identifier)* @variable)
162+
(type_of_expression (identifier) @variable)
163+
(assignment_expression (identifier) @variable)
164+
(cast_expression (identifier) @type)
165+
166+
;; Preprocessor
167+
(preprocessor_directive) @constant
168+
(preprocessor_call (identifier) @string)
169+
170+
;; Loop
171+
(for_each_statement (identifier) @type (identifier) @variable)
172+
(for_each_statement (implicit_type) @type (identifier) @variable)
173+
(for_each_statement (predefined_type) @type (identifier) @variable)
174+
175+
;; Exception
176+
(catch_declaration (identifier) @type (identifier) @variable)
177+
(catch_declaration (identifier) @type)
178+
179+
;; Switch
180+
(switch_statement (identifier) @variable)
181+
(switch_expression (identifier) @variable)
182+
183+
;; If
184+
(if_statement (identifier) @variable)
185+
186+
;; Declaration expression
187+
(declaration_expression (implicit_type) (identifier) @variable)
188+
189+
;; Arrow expression
190+
(arrow_expression_clause (identifier) @variable)
191+
192+
;; Other
193+
(label_name) @variable
194+
(qualified_name (identifier) @type)
195+
(using_directive (identifier)* @type)
196+
(await_expression (identifier)* @function)
197+
(invocation_expression (identifier) @function)
198+
(element_access_expression (identifier) @variable)
199+
(conditional_access_expression (identifier) @variable)
200+
(member_binding_expression (identifier) @variable)
201+
(member_access_expression (identifier) @function)
202+
(name_colon (identifier)* @variable)
203+
(name_equals (identifier) @type)
204+
(field_declaration)
205+
(argument (identifier) @variable)
206+
]
207+
"Default patterns for tree-sitter support.")
208+
209+
;;; Tree-sitter indentation
210+
211+
(defgroup csharp-mode-indent nil "Indent lines using Tree-sitter as backend"
212+
:group 'tree-sitter)
213+
214+
(defcustom csharp-mode-indent-offset 4
215+
"Indent offset for csharp-mode"
216+
:type 'integer
217+
:group 'csharp)
218+
219+
(defvar csharp-mode-indent-scopes
220+
'((indent-all . ;; these nodes are always indented
221+
(accessor_declaration
222+
break_statement
223+
arrow_expression_clause
224+
parameter_list
225+
conditional_expression
226+
"."))
227+
(indent-rest . ;; if parent node is one of these and node is not first → indent
228+
(
229+
binary_expression
230+
switch_section
231+
))
232+
(indent-body . ;; if parent node is one of these and current node is in middle → indent
233+
(block
234+
anonymous_object_creation_expression
235+
enum_member_declaration_list
236+
initializer_expression
237+
expression_statement
238+
declaration_list
239+
attribute_argument_list
240+
switch_body))
241+
242+
(paren-indent . ;; if parent node is one of these → indent to paren opener
243+
(parenthesized_expression))
244+
(align-char-to . ;; chaining char → node types we move parentwise to find the first chaining char
245+
())
246+
(aligned-siblings . ;; siblings (nodes with same parent) should be aligned to the first child
247+
(parameter))
248+
249+
(multi-line-text . ;; if node is one of these, then don't modify the indent
250+
;; this is basically a peaceful way out by saying "this looks like something
251+
;; that cannot be indented using AST, so best I leave it as-is"
252+
(comment
253+
preprocessor_call
254+
labeled_statement))
255+
(outdent . ;; these nodes always outdent (1 shift in opposite direction)
256+
(;; "}"
257+
case_switch_label
258+
))
259+
)
260+
"Scopes for indenting in C#.")
261+
262+
;;;###autoload
263+
(define-derived-mode csharp-tree-sitter-mode prog-mode "C#"
264+
"Major mode for editing Csharp code.
265+
266+
Key bindings:
267+
\\{csharp-mode-map}"
268+
:group 'csharp
269+
270+
(setq csharp-mode-syntax-table nil)
271+
(setq csharp-mode-map nil)
272+
(setq-local tree-sitter-indent-current-scopes csharp-mode-indent-scopes)
273+
(setq-local tree-sitter-indent-offset csharp-mode-indent-offset)
274+
(setq-local indent-line-function #'tree-sitter-indent-line)
275+
276+
;; https://github.com/ubolonton/emacs-tree-sitter/issues/84
277+
(unless font-lock-defaults
278+
(setq font-lock-defaults '(nil)))
279+
(setq-local tree-sitter-hl-default-patterns csharp-mode-tree-sitter-patterns)
280+
;; Comments
281+
(setq-local comment-start "// ")
282+
(setq-local comment-end "")
283+
284+
(tree-sitter-hl-mode))
285+
286+
;;;###autoload
287+
(add-to-list 'tree-sitter-major-mode-language-alist '(csharp-tree-sitter-mode . c-sharp))
288+
289+
(provide 'csharp-tree-sitter)
290+
291+
;;; csharp-tree-sitter.el ends here

test-files/indentation-tests.cs

-2
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ public void Test()
119119
PropB = 2
120120
};
121121

122-
// Commented out for rework -- Theodor Thornhill
123122
// yield return new InnerA.InnerB {
124123
// PropA = 1,
125124
// PropB = 2
@@ -131,7 +130,6 @@ public void Test()
131130
May = "Yay"
132131
};
133132

134-
// Commented out for rework -- Theodor Thornhill
135133
// yield return new InnerA.InnerB
136134
// {
137135
// Boo = "Foo",

0 commit comments

Comments
 (0)