Subject: bug#43559: 28.0.50; [PATCH] Add csharp support to cc-mode

From: Theodor Thornhill <[email-masked]>
Date: Tue, 22 Sep 2020 23:15:04 +0200

Jostein Kjønigsen <[email-masked]> writes:

> On 22.09.2020 15:10, Theodor Thornhill wrote:
>> Jostein Kjønigsen <[email-masked]> writes:

[...]

> I still haven't looked at the code in your patch, but I've applied it to 
> latest Emacs Git master, and given it a test-run.
> When going over existing code, everything looks and feels nice. And it's 
> much faster than my "old", non-transferable code. So I definitely think 
> this is a /very good/ start.

Nice!

>
> While I hold no "position" in GNU or Emacs as a whole, I would consider 
> myself a C# veteran (since 2004), and as the old maintainer of old 
> csharp-mode, I think I have some experience which may be useful.
Most definitively.  Thank you for taking your time!

>
> So for the time being, I'll just promote myself to being the "critical 
> Emacs C#-user", and provide some feedback based on my expectations for 
> how editing C# should feel or behave.
>
> Please don't consider the following comments as negative towards your 
> work, or me in any way trying to dissuade you from getting this 
> mainlined. I'm just here to try to help you ensure that what you land is 
> going to be "good enough" so that it will be easier to argue for 
> inclusion in core-Emacs later on :)
None of your comment are taken as negative.  I'd much rather you'd be
critical early, so that we can get something nice quicker!

>
> So with all that said, so far I've noticed a few things which I think 
> arguably goes against C# convention, and would be nice to get fixed, if 
> possible. There's also a some things where I just feel it would "better" 
> to do things differently.
>

Before I answer your comments, I'll just let you know I'm still wrapping
my head around the whole cc-mode.  I might find better solutions to all
this later on.

>
> *First off: **Attributes are not handled properly. **
> *
>
> In java-mode annotations gets fontified and indented properly:
>
> In your current csharp-mode draft, we get no fontification, and we also 
> get a trailing indentation bug for the equivalent C# code:

Yeah, this is a difficult one, because the java version of this
implements lots of logic in the cc-engine.el, and for now, we don't.
However, my most recent patch seems to deal with this properly, as far
as I can tell.  I've tested with your example code and also with random
files in the roslyn repository.  Fontification is still sparse here.

>
> *Second: Object initializers are not indented properly.*
>
> Consider the following fairly simple case. Using the provided patch we 
> get the following indentation:

This one should be fixed in the attached patch.

> This one is arguably very hard to get right, because it's a conceptually 
> infinitely recursive problem. (You may initialize a property with 
> another object-initializer.)

I think the recursion case works as well.  I've made an attempt in the
attached 'test.cs'

>
> While solving this perfectly for all cases is clearly out the window, do 
> you think it would be possible at least to make 1-level 
> object-initilizers work?

Hopefully, we have it now!

>
> One the bright side /collection-/initializers seems to work just fine :)
>

Great!

>
> *Third: Lamda-function indentation when used with higher order functions
> *
>
> Lambdas also suffer from some unexpected indentation issues:

This one should be fixed in the latest patch.

>
> *Fourth: variable-fontification*
>
> Here I have no absolute C# convention to quote for absolute correctness, 
> but it kinda "feels wrong" to me at places.

> So we'll have to make due with imperfections, make some pragmatical 
> decisions on what we think will be good default/fallback values, and 
> that's OK.
>

Agreed

> Right now though, all implicitly typed variables (vars), local variables 
> with method-access and local fields with property-access are shown using 
> /font-lock-constant-face/ and that seems a bit off:

This case is fixed now.  It was due to the 'var' keyword was put in the
wrong basket.

> As for "_field" and "foo.", I'm not sure what the best fallback would 
> be. Without a language-engine to guide us, this is genuinely hard stuff 
> to get right.
>

Yeah, this one is kind of hard, so I've been ignoring it for a little
while.  The easiest thing should be to remove the highlighting, but not
sure if that is the best move so far.

>
> I'm sure just/discussing/ the///soft-rules/ for these aspects could take 
> months alone, not speaking for the effort required to actually making it 
> happen in code.
>
> Consider it something to aim for, but I wouldn't expect anyone to get 
> this right in a version 1 of anything.
>

Yeah, more cases are coming up all the time.  My initial focus has been
mostly on the indentation part, since font-locking is a 'bonus-feature'
in some sense.  However, they seem intimately connected in cc-mode.

>
> Otherwise, great work, and thank you for putting in this effort!

My pleasure :)

One issue I have now is that there are many rules in the c-style-alist,
and I think it would be better to design or reuse some constructs from
cc-engine to apply some other syntax rules to the code.  One example and
consequence of this is the fix of the attributes from your code.  It
aligns nicely, but destroys the indentation of the 'aligned ternary' (on
the bottom of the file).

Please, if you have time, take a look at the second patch, as well as
the test file.  Maybe you get some ideas, but I think some of the low
hanging fruits are found, and it is time to dive into the engine itself.

I really appreciate you taking your time to find these bugs for me.
Thanks.

--
Theo

P.S
Pasting the contents of test.cs here, since I apparently suck at email

/// Working

[Test]
public class Foo
{
    [Zoo]
    [Goo]
    public void Bar()
    {
        
    }
};

var x = new SomeType[]
{
    Prop1 = "foo",
    Prop2 = new SomeType2[]
    {
        Prop3 = true ? "this" : "that";
    }
    //
};

var x = new SomeType[] {
    new SomeType(a, b),
    new SomeType(b, c)
    // lol
};

/// Kind of working

goo.Select(i => {
    return goo.Select(i)
    .Foo()
    .Bar()
});

Foo foo = new Foo();
var bar1 = foo.Bar(_field);
Bar bar3 = foo.Bar(_field.Prop);

/// Not working

var x = foo
? bar
: baz;

>From e3707e7d45d708c20733c7c496b98c1c2724b808 Mon Sep 17 00:00:00 2001
From: Theodor Thornhill <[email-masked]>
Date: Tue, 22 Sep 2020 12:09:38 +0200
Subject: [PATCH v2] Add csharp support to cc-mode

---
 lisp/progmodes/cc-defs.el   |  27 +++----
 lisp/progmodes/cc-fonts.el  |  33 ++++++++
 lisp/progmodes/cc-langs.el  | 148 +++++++++++++++++++++++-------------
 lisp/progmodes/cc-mode.el   |  65 +++++++++++++---
 lisp/progmodes/cc-styles.el |  36 +++++++++
 lisp/progmodes/cc-vars.el   |  22 ++++++
 6 files changed, 253 insertions(+), 78 deletions(-)

diff --git a/lisp/progmodes/cc-defs.el b/lisp/progmodes/cc-defs.el
index 77e263f1aa..ac1c421e37 100644
--- a/lisp/progmodes/cc-defs.el
+++ b/lisp/progmodes/cc-defs.el
@@ -95,12 +95,12 @@ c-version-sym
 
 (defvar c-buffer-is-cc-mode nil
   "Non-nil for all buffers with a major mode derived from CC Mode.
-Otherwise, this variable is nil.  I.e. this variable is non-nil for
-`c-mode', `c++-mode', `objc-mode', `java-mode', `idl-mode',
-`pike-mode', `awk-mode', and any other non-CC Mode mode that calls
-`c-initialize-cc-mode'.  The value is the mode symbol itself
-\(i.e. `c-mode' etc) of the original CC Mode mode, or just t if it's
-not known.")
+Otherwise, this variable is nil.  I.e. this variable is non-nil
+for `c-mode', `c++-mode', `objc-mode', `csharp-mode',
+`java-mode', `idl-mode', `pike-mode', `awk-mode', and any other
+non-CC Mode mode that calls `c-initialize-cc-mode'.  The value is
+the mode symbol itself \(i.e. `c-mode' etc) of the original CC
+Mode mode, or just t if it's not known.")
 (make-variable-buffer-local 'c-buffer-is-cc-mode)
 
 ;; Have to make `c-buffer-is-cc-mode' permanently local so that it
@@ -1874,13 +1874,14 @@ c-keep-region-active
   (and (boundp 'zmacs-region-stays)
        (setq zmacs-region-stays t)))
 
-(put 'c-mode    'c-mode-prefix "c-")
-(put 'c++-mode  'c-mode-prefix "c++-")
-(put 'objc-mode 'c-mode-prefix "objc-")
-(put 'java-mode 'c-mode-prefix "java-")
-(put 'idl-mode  'c-mode-prefix "idl-")
-(put 'pike-mode 'c-mode-prefix "pike-")
-(put 'awk-mode  'c-mode-prefix "awk-")
+(put 'c-mode      'c-mode-prefix "c-")
+(put 'c++-mode    'c-mode-prefix "c++-")
+(put 'objc-mode   'c-mode-prefix "objc-")
+(put 'csharp-mode 'c-mode-prefix "csharp-")
+(put 'java-mode   'c-mode-prefix "java-")
+(put 'idl-mode    'c-mode-prefix "idl-")
+(put 'pike-mode   'c-mode-prefix "pike-")
+(put 'awk-mode    'c-mode-prefix "awk-")
 
 (defsubst c-mode-symbol (suffix)
   "Prefix the current mode prefix (e.g. \"c-\") to SUFFIX and return
diff --git a/lisp/progmodes/cc-fonts.el b/lisp/progmodes/cc-fonts.el
index bb7e5bea6e..c361609e0f 100644
--- a/lisp/progmodes/cc-fonts.el
+++ b/lisp/progmodes/cc-fonts.el
@@ -2579,6 +2579,39 @@ objc-font-lock-keywords
   (setq objc-font-lock-extra-types
 	(cc-eval-when-compile (list (concat "[" c-upper "]\\sw*")))))
 
+
+;;; Csharp.
+
+(c-override-default-keywords 'csharp-font-lock-keywords)
+
+(defconst csharp-font-lock-keywords-1 (c-lang-const c-matchers-1 csharp)
+  "Minimal font locking for C# mode.
+Fontifies nothing except the syntactic fontification of strings and
+comments.")
+
+(defconst csharp-font-lock-keywords-2 (c-lang-const c-matchers-2 csharp)
+  "Fast normal font locking for C# mode.
+In addition to `csharp-font-lock-keywords-1', this adds fontification of
+keywords, simple types, declarations that are easy to recognize, the
+user defined types on `csharp-font-lock-extra-types', and the doc
+comment styles specified by `c-doc-comment-style'.")
+
+(defconst csharp-font-lock-keywords-3 (c-lang-const c-matchers-3 csharp)
+  "Accurate normal font locking for C# mode.
+Like variable `csharp-font-lock-keywords-2' but detects declarations in a more
+accurate way that works in most cases for arbitrary types without the
+need for `csharp-font-lock-extra-types'.")
+
+(defvar csharp-font-lock-keywords csharp-font-lock-keywords-3
+  "Default expressions to highlight in C# mode.")
+
+(defun csharp-font-lock-keywords-2 ()
+  (c-compose-keywords-list csharp-font-lock-keywords-2))
+(defun csharp-font-lock-keywords-3 ()
+  (c-compose-keywords-list csharp-font-lock-keywords-3))
+(defun csharp-font-lock-keywords ()
+  (c-compose-keywords-list csharp-font-lock-keywords))
+
 
 ;;; Java.
 
diff --git a/lisp/progmodes/cc-langs.el b/lisp/progmodes/cc-langs.el
index 13e70a3251..e6c314b445 100644
--- a/lisp/progmodes/cc-langs.el
+++ b/lisp/progmodes/cc-langs.el
@@ -378,7 +378,8 @@ c-make-mode-syntax-table
        (let ((table (make-syntax-table)))
 	 (c-populate-syntax-table table)
 	 ;; Mode specific syntaxes.
-	 ,(cond ((or (c-major-mode-is 'objc-mode) (c-major-mode-is 'java-mode))
+	 ,(cond ((or (c-major-mode-is 'objc-mode) (c-major-mode-is 'java-mode)
+		     (c-major-mode-is 'csharp-mode))
 		 ;; Let '@' be part of symbols in ObjC to cope with
 		 ;; its compiler directives as single keyword tokens.
 		 ;; This is then necessary since it's assumed that
@@ -430,7 +431,7 @@ c-identifier-syntax-modifications
   ;; it as an identifier character since it's often used in various
   ;; machine generated identifiers.
   t    '((?_ . "w") (?$ . "w"))
-  (objc java) (append '((?@ . "w"))
+  (objc java csharp) (append '((?@ . "w"))
 	       (c-lang-const c-identifier-syntax-modifications))
   awk  '((?_ . "w")))
 (c-lang-defvar c-identifier-syntax-modifications
@@ -467,6 +468,9 @@ c-get-state-before-change-functions
 	     c-truncate-bs-cache
 	     c-before-change-check-unbalanced-strings
 	     c-parse-quotes-before-change)
+  csharp '(c-parse-quotes-before-change
+	   c-before-change-check-unbalanced-strings
+	   c-before-change-check-<>-operators)
   java '(c-parse-quotes-before-change
 	 c-before-change-check-unbalanced-strings
 	 c-before-change-check-<>-operators)
@@ -516,6 +520,12 @@ c-before-font-lock-functions
 	c-neutralize-syntax-in-CPP
 	c-restore-<>-properties
 	c-change-expand-fl-region)
+  csharp '(c-depropertize-new-text
+	   c-after-change-escape-NL-in-string
+	   c-parse-quotes-after-change
+	   c-after-change-mark-abnormal-strings
+	   c-restore-<>-properties
+	   c-change-expand-fl-region)
   java '(c-depropertize-new-text
 	 c-after-change-escape-NL-in-string
 	 c-parse-quotes-after-change
@@ -648,10 +658,11 @@ c-symbol-start
   "Regexp that matches the start of a symbol, i.e. any identifier or
 keyword.  It's unspecified how far it matches.  Does not contain a \\|
 operator at the top level."
-  t    (concat "[" c-alpha "_]")
-  java (concat "[" c-alpha "_@]")
-  objc (concat "[" c-alpha "_@]")
-  pike (concat "[" c-alpha "_`]"))
+  t      (concat "[" c-alpha "_]")
+  csharp (concat "[" c-alpha "_@]")
+  java   (concat "[" c-alpha "_@]")
+  objc   (concat "[" c-alpha "_@]")
+  pike   (concat "[" c-alpha "_`]"))
 (c-lang-defvar c-symbol-start (c-lang-const c-symbol-start))
 
 (c-lang-defconst c-symbol-chars
@@ -718,6 +729,7 @@ c-identifier-ops
   ;; naming conventions.  We still define "." here to make
   ;; `c-forward-name' move over as long names as possible which is
   ;; necessary to e.g. handle throws clauses correctly.
+  csharp '((left-assoc "."))
   java '((left-assoc "."))
   idl  '((left-assoc "::")
 	 (prefix "::"))
@@ -1247,6 +1259,8 @@ c-overloadable-operators
 	 "<<" ">>" ">>=" "<<=" "==" "!=" "not_eq" "<=" ">="
 	 "&&" "and" "||" "??!??!" "or" "++" "--" "," "->*" "->"
 	 "()" "[]" "<::>" "??(??)")
+  csharp '("+" "-" "*" "/" "%" "&" "|" "^" "<<" ">>" "=="
+	   "!=" ">" "<" ">=" "<=")
   ;; These work like identifiers in Pike.
   pike '("`+" "`-" "`&" "`|" "`^" "`<<" "`>>" "`*" "`/" "`%" "`~"
 	 "`==" "`<" "`>" "`!" "`[]" "`[]=" "`->" "`->=" "`()" "``+"
@@ -2023,6 +2037,8 @@ c-primitive-type-kwds
   objc (append
 	'("id" "Class" "SEL" "IMP" "BOOL")
 	(c-lang-const c-primitive-type-kwds))
+  csharp '("bool" "byte" "sbyte" "char" "decimal" "double" "float" "int" "uint"
+	   "long" "ulong" "short" "ushort" "void" "object" "string" "var")
   java '("boolean" "byte" "char" "double" "float" "int" "long" "short" "void")
   idl  '("Object" "ValueBase" "any" "boolean" "char" "double" "fixed" "float"
 	 "long" "octet" "sequence" "short" "string" "void" "wchar" "wstring"
@@ -2137,7 +2153,8 @@ c-type-modifier-kwds
 on `c-primitive-type-kwds', they are fontified with the keyword face and not
 the type face."
   t (c-lang-const c-type-modifier-prefix-kwds)
-  c++ (append (c-lang-const c-type-modifier-prefix-kwds) '("throw")))
+  c++ (append (c-lang-const c-type-modifier-prefix-kwds) '("throw"))
+  csharp '("readonly" "const" "volatile" "new"))
 
 (c-lang-defconst c-opt-type-modifier-key
   ;; Adorned regexp matching `c-type-modifier-kwds', or nil in
@@ -2229,8 +2246,9 @@ c-brace-list-decl-kwds
 `c-colon-type-list-kwds', `c-paren-nontype-kwds', `c-paren-type-kwds',
 `c-<>-type-kwds', or `c-<>-arglist-kwds' then the associated clauses
 will be handled."
-  t    '("enum")
-  (awk) nil)
+  t      '("enum")
+  csharp '("enum" "new")
+  (awk)  nil)
 
 (c-lang-defconst c-brace-list-key
   ;; Regexp matching the start of declarations where the following
@@ -2281,6 +2299,7 @@ c-other-block-decl-kwds
   t   nil
   (c objc) '("extern")
   c++ '("namespace" "extern")
+  csharp '("namespace")
   idl '("module"
 	;; In CORBA CIDL:
 	"composition"))
@@ -2341,7 +2360,8 @@ c-typedef-decl-key
 (c-lang-defconst c-using-kwds
   "Keywords which behave like `using' in C++"
   t nil
-  c++ '("using"))
+  c++ '("using")
+  csharp '("using"))
 
 (c-lang-defconst c-using-key
   ;; Regexp matching C++'s `using'.
@@ -2410,6 +2430,10 @@ c-modifier-kwds
 	 "primary" "state"
 	 ;; In CORBA CIDL:
 	 "bindsTo" "delegatesTo" "implements" "proxy" "storedOn")
+  csharp '("abstract" "const" "default" "final" "native" "private" "protected"
+	   "public" "partial" "internal" "readonly" "static" "event" "transient"
+	   "volatile" "sealed" "ref" "out" "virtual" "implicit" "explicit"
+	   "fixed" "override" "params" "async" "extern" "unsafe")
   ;; Note: "const" is not used in Java, but it's still a reserved keyword.
   java '("abstract" "const" "default" "final" "native" "private" "protected"
 	 "public" "static" "strictfp" "synchronized" "transient" "volatile")
@@ -2634,14 +2658,16 @@ c-type-list-kwds
 
 Note: Use `c-typeless-decl-kwds' for keywords followed by a function
 or variable identifier (that's being defined)."
-  t    nil
-  c++  '("operator")
-  objc '("@class")
-  java '("import" "new" "extends" "super" "implements" "throws")
-  idl  '("manages" "native" "primarykey" "supports"
-	 ;; In CORBA PSDL:
-	 "as" "implements" "of" "scope")
-  pike '("inherit"))
+  t      nil
+  c++    '("operator")
+  objc   '("@class")
+  csharp '("struct" "class" "interface" "delegate" "event" "get" "set" "add"
+	   "remove" "is" "as" "operator")
+  java   '("import" "new" "extends" "super" "implements" "throws")
+  idl    '("manages" "native" "primarykey" "supports"
+	   ;; In CORBA PSDL:
+	   "as" "implements" "of" "scope")
+  pike   '("inherit"))
 
 (c-lang-defconst c-ref-list-kwds
   "Keywords that may be followed by a comma separated list of
@@ -2652,24 +2678,25 @@ c-ref-list-kwds
 
 Note: Use `c-typeless-decl-kwds' for keywords followed by a function
 or variable identifier (that's being defined)."
-  t    nil
-  c++  '("namespace")
-  java '("package")
-  idl  '("import" "module"
-	 ;; In CORBA CIDL:
-	 "composition")
-  pike '("import"))
+  t      nil
+  c++    '("namespace")
+  java   '("package")
+  idl    '("import" "module"
+	   ;; In CORBA CIDL:
+	   "composition")
+  pike   '("import"))
 
 (c-lang-defconst c-colon-type-list-kwds
   "Keywords that may be followed (not necessarily directly) by a colon
 and then a comma separated list of type identifiers, where each
 optionally can be prefixed by keywords.  (Can also be used for the
 special case when the list can contain only one element.)"
-  t    nil
-  c++  '("class" "struct")
-  idl  '("component" "eventtype" "home" "interface" "valuetype"
-	 ;; In CORBA PSDL:
-	 "storagehome" "storagetype"))
+  t      nil
+  c++    '("class" "struct")
+  csharp '("class" "struct" "interface")
+  idl    '("component" "eventtype" "home" "interface" "valuetype"
+	   ;; In CORBA PSDL:
+	   "storagehome" "storagetype"))
 
 (c-lang-defconst c-colon-type-list-re
   "Regexp matched after the keywords in `c-colon-type-list-kwds' to skip
@@ -2765,11 +2792,12 @@ c-brace-id-list-kwds
 
 (c-lang-defconst c-block-stmt-1-kwds
   "Statement keywords followed directly by a substatement."
-  t    '("do" "else")
-  c++  '("do" "else" "try")
-  objc '("do" "else" "@finally" "@try" "@autoreleasepool")
-  java '("do" "else" "finally" "try")
-  idl  nil)
+  t      '("do" "else")
+  c++    '("do" "else" "try")
+  objc   '("do" "else" "@finally" "@try" "@autoreleasepool")
+  csharp '("do" "else" "finally" "try")
+  java   '("do" "else" "finally" "try")
+  idl    nil)
 
 (c-lang-defconst c-block-stmt-1-key
   ;; Regexp matching the start of any statement followed directly by a
@@ -2781,7 +2809,8 @@ c-block-stmt-1-2-kwds
   "Statement keywords optionally followed by a paren sexp.
 Keywords here should also be in `c-block-stmt-1-kwds'."
   t nil
-  java '("try"))
+  csharp '("try")
+  java   '("try"))
 
 (c-lang-defconst c-block-stmt-1-2-key
   ;; Regexp matching the start of a statement which may be followed by a
@@ -2791,13 +2820,15 @@ c-block-stmt-1-2-key
 
 (c-lang-defconst c-block-stmt-2-kwds
   "Statement keywords followed by a paren sexp and then by a substatement."
-  t    '("for" "if" "switch" "while")
-  c++  '("for" "if" "switch" "while" "catch")
-  objc '("for" "if" "switch" "while" "@catch" "@synchronized")
-  java '("for" "if" "switch" "while" "catch" "synchronized")
-  idl  nil
-  pike '("for" "if" "switch" "while" "foreach")
-  awk  '("for" "if" "switch" "while"))
+  t      '("for" "if" "switch" "while")
+  c++    '("for" "if" "switch" "while" "catch")
+  objc   '("for" "if" "switch" "while" "@catch" "@synchronized")
+  csharp '("for" "if" "switch" "while" "catch" "foreach" "fixed" "checked"
+	   "unchecked" "using" "lock")
+  java   '("for" "if" "switch" "while" "catch" "synchronized")
+  idl    nil
+  pike   '("for" "if" "switch" "while" "foreach")
+  awk    '("for" "if" "switch" "while"))
 
 (c-lang-defconst c-block-stmt-2-key
   ;; Regexp matching the start of any statement followed by a paren sexp
@@ -2838,6 +2869,7 @@ c-simple-stmt-kwds
   t    '("break" "continue" "goto" "return")
   c++    '("break" "continue" "goto" "return" "co_return")
   objc '("break" "continue" "goto" "return" "@throw")
+  csharp '("break" "continue" "goto" "return" "throw")
   ;; Note: `goto' is not valid in Java, but the keyword is still reserved.
   java '("break" "continue" "goto" "return" "throw")
   idl  nil
@@ -2912,16 +2944,18 @@ c-constant-kwds
   objc    '("nil" "Nil" "YES" "NO" "IBAction" "IBOutlet"
 	    "NS_DURING" "NS_HANDLER" "NS_ENDHANDLER")
   idl     '("TRUE" "FALSE")
+  csharp  '("true" "false" "null" "value")
   java    '("true" "false" "null") ; technically "literals", not keywords
   pike    '("UNDEFINED")) ;; Not a keyword, but practically works as one.
 
 (c-lang-defconst c-primary-expr-kwds
   "Keywords besides constants and operators that start primary expressions."
-  t    nil
-  c++  '("operator" "this")
-  objc '("super" "self")
-  java '("this")
-  pike '("this")) ;; Not really a keyword, but practically works as one.
+  t      nil
+  c++    '("operator" "this")
+  objc   '("super" "self")
+  csharp '("this" "base" "operator")
+  java   '("this")
+  pike   '("this")) ;; Not really a keyword, but practically works as one.
 
 (c-lang-defconst c-expr-kwds
   ;; Keywords that can occur anywhere in expressions.  Built from
@@ -2948,16 +2982,18 @@ c-inexpr-block-kwds
 
 (c-lang-defconst c-inexpr-class-kwds
   "Keywords that can start classes inside expressions."
-  t    nil
-  java '("new")
-  pike '("class"))
+  t      nil
+  csharp '("new")
+  java   '("new")
+  pike   '("class"))
 
 (c-lang-defconst c-inexpr-brace-list-kwds
   "Keywords that can start brace list blocks inside expressions.
 Note that Java specific rules are currently applied to tell this from
 `c-inexpr-class-kwds'."
-  t    nil
-  java '("new"))
+  t      nil
+  csharp '("new")
+  java   '("new"))
 
 (c-lang-defconst c-opt-inexpr-brace-list-key
   ;; Regexp matching the start of a brace list in an expression, or
@@ -3049,6 +3085,7 @@ c-std-abbrev-keywords
   "List of keywords which may need to cause electric indentation."
   t '("else" "while")
   c++ (append (c-lang-const c-std-abbrev-keywords) '("catch"))
+  csharp (append (c-lang-const c-std-abbrev-keywords) '("catch" "finally"))
   java (append (c-lang-const c-std-abbrev-keywords) '("catch" "finally"))
   idl nil)
 (c-lang-defvar c-std-abbrev-keywords (c-lang-const c-std-abbrev-keywords))
@@ -3274,6 +3311,7 @@ c-decl-prefix-re
   ;; in all languages except Java for when a cpp macro definition
   ;; begins with a declaration.
   t "\\([{}();,]+\\)"
+  csharp "\\([{}(;,<]+\\)"
   java "\\([{}(;,<]+\\)"
   ;; Match "<" in C++ to get the first argument in a template arglist.
   ;; In that case there's an additional check in `c-find-decl-spots'
@@ -3585,6 +3623,7 @@ c-opt-type-suffix-key
 is in effect or not."
   t nil
   (c c++ objc pike) "\\(\\.\\.\\.\\)"
+  csharp (concat "\\(\\[" (c-lang-const c-simple-ws) "*\\]\\|\\.\\.\\.\\)")
   java (concat "\\(\\[" (c-lang-const c-simple-ws) "*\\]\\|\\.\\.\\.\\)"))
 (c-lang-defvar c-opt-type-suffix-key (c-lang-const c-opt-type-suffix-key))
 
@@ -3644,7 +3683,7 @@ c-recognize-typeless-decls
 that are preceded by a declaration starting keyword, so
 e.g. `c-typeless-decl-kwds' may still be used when it's set to nil."
   t nil
-  (c c++ objc java) t)
+  (c c++ objc csharp java) t)
 (c-lang-defvar c-recognize-typeless-decls
   (c-lang-const c-recognize-typeless-decls))
 
@@ -3657,6 +3696,7 @@ c-recognize-<>-arglists
 expression is considered to be a type."
   t (or (consp (c-lang-const c-<>-type-kwds))
 	(consp (c-lang-const c-<>-arglist-kwds)))
+  csharp t
   java t)	    ; 2008-10-19.  This is crude.  The syntax for java
 		    ; generics is not yet coded in CC Mode.
 (c-lang-defvar c-recognize-<>-arglists (c-lang-const c-recognize-<>-arglists))
diff --git a/lisp/progmodes/cc-mode.el b/lisp/progmodes/cc-mode.el
index c6dd671051..56cc476fad 100644
--- a/lisp/progmodes/cc-mode.el
+++ b/lisp/progmodes/cc-mode.el
@@ -36,12 +36,12 @@
 ;; Note: The version string is in cc-defs.
 
 ;; This package provides GNU Emacs major modes for editing C, C++,
-;; Objective-C, Java, CORBA's IDL, Pike and AWK code.  As of the
+;; Objective-C, C#, Java, CORBA's IDL, Pike and AWK code.  As of the
 ;; latest Emacs and XEmacs releases, it is the default package for
 ;; editing these languages.  This package is called "CC Mode", and
 ;; should be spelled exactly this way.
 
-;; CC Mode supports K&R and ANSI C, ANSI C++, Objective-C, Java,
+;; CC Mode supports K&R and ANSI C, ANSI C++, Objective-C, C#, Java,
 ;; CORBA's IDL, Pike and AWK with a consistent indentation model
 ;; across all modes.  This indentation model is intuitive and very
 ;; flexible, so that almost any desired style of indentation can be
@@ -75,7 +75,6 @@
 
 ;; Externally maintained major modes which use CC-mode's engine include:
 ;; - cuda-mode
-;; - csharp-mode (https://github.com/josteink/csharp-mode)
 ;; - haxe-mode
 ;; - d-mode
 ;; - dart-mode
@@ -195,13 +194,14 @@ c-init-language-vars-for
 `c-init-language-vars' macro if the language you want to use is one of
 those, rather than a derived language defined through the language
 variable system (see \"cc-langs.el\")."
-  (cond ((eq mode 'c-mode)    (c-init-language-vars c-mode))
-	((eq mode 'c++-mode)  (c-init-language-vars c++-mode))
-	((eq mode 'objc-mode) (c-init-language-vars objc-mode))
-	((eq mode 'java-mode) (c-init-language-vars java-mode))
-	((eq mode 'idl-mode)  (c-init-language-vars idl-mode))
-	((eq mode 'pike-mode) (c-init-language-vars pike-mode))
-	((eq mode 'awk-mode)  (c-init-language-vars awk-mode))
+  (cond ((eq mode 'c-mode)      (c-init-language-vars c-mode))
+	((eq mode 'c++-mode)    (c-init-language-vars c++-mode))
+	((eq mode 'objc-mode)   (c-init-language-vars objc-mode))
+	((eq mode 'csharp-mode) (c-init-language-vars csharp-mode))
+	((eq mode 'java-mode)   (c-init-language-vars java-mode))
+	((eq mode 'idl-mode)    (c-init-language-vars idl-mode))
+	((eq mode 'pike-mode)   (c-init-language-vars pike-mode))
+	((eq mode 'awk-mode)    (c-init-language-vars awk-mode))
 	(t (error "Unsupported mode %s" mode))))
 
 ;;;###autoload
@@ -649,7 +649,8 @@ c-basic-common-init
 
   (when (or c-recognize-<>-arglists
 	    (c-major-mode-is 'awk-mode)
-	    (c-major-mode-is '(java-mode c-mode c++-mode objc-mode pike-mode)))
+	    (c-major-mode-is
+	     '(java-mode c-mode c++-mode objc-mode pike-mode csharp-mode)))
     ;; We'll use the syntax-table text property to change the syntax
     ;; of some chars for this language, so do the necessary setup for
     ;; that.
@@ -2739,6 +2740,48 @@ objc-mode
   (cc-imenu-init nil 'cc-imenu-objc-function)
   (c-run-mode-hooks 'c-mode-common-hook))
 
+
+;; Support for C#
+
+(defvar csharp-mode-syntax-table
+  (funcall (c-lang-const c-make-mode-syntax-table csharp))
+  "Syntax table used in csharp-mode buffers.")
+
+(defvar csharp-mode-map
+  (let ((map (c-make-inherited-keymap)))
+    map)
+  "Keymap used in csharp-mode buffers.")
+;; Add bindings which are only useful for C#.
+
+(easy-menu-define c-csharp-menu csharp-mode-map "C# Mode Commands"
+		  (cons "C#" (c-lang-const c-mode-menu csharp)))
+
+;;;###autoload (add-to-list 'auto-mode-alist '("\\.cs\\'" . csharp-mode))
+
+;;;###autoload
+(define-derived-mode csharp-mode prog-mode "C#"
+  "Major mode for editing Csharp code.
+To submit a problem report, enter `\\[c-submit-bug-report]' from a
+csharp-mode buffer.  This automatically sets up a mail buffer with
+version information already added.  You just need to add a description
+of the problem, including a reproducible test case, and send the
+message.
+
+To see what version of CC Mode you are running, enter `\\[c-version]'.
+
+The hook `c-mode-common-hook' is run with no args at mode
+initialization, then `csharp-mode-hook'.
+
+Key bindings:
+\\{csharp-mode-map}"
+  :after-hook (c-update-modeline)
+  (c-initialize-cc-mode t)
+  (setq abbrev-mode t)
+  (c-init-language-vars-for 'csharp-mode)
+  (c-common-init 'csharp-mode)
+  (easy-menu-add c-csharp-menu)
+  (c-run-mode-hooks 'c-mode-common-hook))
+
 
 ;; Support for Java
 
diff --git a/lisp/progmodes/cc-styles.el b/lisp/progmodes/cc-styles.el
index 36be9f6c74..be025810a9 100644
--- a/lisp/progmodes/cc-styles.el
+++ b/lisp/progmodes/cc-styles.el
@@ -215,6 +215,42 @@ c-style-alist
 				(arglist-cont-nonempty)))
      (c-block-comment-prefix . ""))
 
+    ("csharp"
+     (c-basic-offset . 4)
+     (c-comment-only-line-offset . (0 . 0))
+     (c-offsets-alist . ((inline-open . 0)
+			 (topmost-intro         . 0)
+			 (topmost-intro-cont    . 0)
+			 (statement-block-intro . +)
+			 (statement-case-intro  . +)
+			 (statement-case-open   . +)
+			 (substatement          . 0)
+			 (substatement-open     . 0)
+			 (substatement-label    . +)
+			 (label                 . 0)
+			 (statement-case-open   . +)
+			 (statement-cont        . 0)
+			 (arglist-cont-nonempty . 0)
+			 (inexpr-class          . -)
+			 (arglist-intro  . c-lineup-arglist-intro-after-paren)
+			 (arglist-close  . c-lineup-arglist)
+			 (string         . c-lineup-dont-change)
+			 (arglist-cont          . 0)
+			 (block-open            . 0)
+			 (block-close           . 0)
+			 (case-label            . +)
+			 (class-open            . 0)
+			 (class-close           . 0)
+			 (defun-open            . 0)
+			 (defun-close           . 0)
+			 (else-clause           . 0)
+			 (brace-list-intro . (first
+					      c-lineup-class-decl-init-+ +
+					      c-lineup-2nd-brace-entry-in-arglist))
+			 (brace-list-close      . 0)
+			 (access-label   . -)
+			 (inher-cont     . c-lineup-java-inher)
+			 (func-decl-cont . c-lineup-java-throws))))
     ("java"
      (c-basic-offset . 4)
      (c-comment-only-line-offset . (0 . 0))
diff --git a/lisp/progmodes/cc-vars.el b/lisp/progmodes/cc-vars.el
index 9e6f9527ca..559531fafe 100644
--- a/lisp/progmodes/cc-vars.el
+++ b/lisp/progmodes/cc-vars.el
@@ -549,6 +549,8 @@ c-comment-prefix-regexp
 		  (const :format "C++   " c++-mode) (regexp :format "%v"))
 	    (cons :format "%v"
 		  (const :format "ObjC  " objc-mode) (regexp :format "%v"))
+	    (cons :format "%v"
+		  (const :format "C#  " csharp-mode) (regexp :format "%v"))
 	    (cons :format "%v"
 		  (const :format "Java  " java-mode) (regexp :format "%v"))
 	    (cons :format "%v"
@@ -919,6 +921,9 @@ c-require-final-newline
 	      (cons :format "%v"
 		    (const :format "ObjC  " objc-mode)
 		    (symbol :format "%v" :value ,require-final-newline))
+	      (cons :format "%v"
+		    (const :format "C#  " csharp-mode)
+		    (symbol :format "%v" :value ,require-final-newline))
 	      (cons :format "%v"
 		    (const :format "Java  " java-mode)
 		    (symbol :format "%v" :value ,require-final-newline))
@@ -1003,6 +1008,7 @@ c-objc-method-parameter-offset
   :group 'c)
 
 (defcustom c-default-style '((java-mode . "java") (awk-mode . "awk")
+			     (csharp-mode . "csharp")
 			     (other . "gnu"))
   "Style which gets installed by default when a file is visited.
 
@@ -1031,6 +1037,8 @@ c-default-style
 		  (const :format "C++   " c++-mode) (string :format "%v"))
 	    (cons :format "%v"
 		  (const :format "ObjC  " objc-mode) (string :format "%v"))
+	    (cons :format "%v"
+		  (const :format "C#  " csharp-mode) (string :format "%v"))
 	    (cons :format "%v"
 		  (const :format "Java  " java-mode) (string :format "%v"))
 	    (cons :format "%v"
@@ -1459,6 +1467,11 @@ objc-mode-hook
   :type 'hook
   :group 'c)
 
+(defcustom csharp-mode-hook nil
+  "Hook called by `csharp-mode'."
+  :type 'hook
+  :group 'c)
+
 (defcustom java-mode-hook nil
   "Hook called by `java-mode'."
   :type 'hook
@@ -1613,6 +1626,15 @@ objc-font-lock-extra-types
   :type 'c-extra-types-widget
   :group 'c)
 
+(defcustom csharp-font-lock-extra-types
+  (list (concat "[" c-upper "]\\sw*[" c-lower "]\\sw"))
+  (c-make-font-lock-extra-types-blurb "C#" "csharp-mode" (concat
+"For example, a value of (\"[" c-upper "]\\\\sw*[" c-lower "]\\\\sw*\") means
+capitalized words are treated as type names (the requirement for a
+lower case char is to avoid recognizing all-caps constant names)."))
+  :type 'c-extra-types-widget
+  :group 'c)
+
 (defcustom java-font-lock-extra-types
   (list (concat "[" c-upper "]\\sw*[" c-lower "]\\sw"))
   (c-make-font-lock-extra-types-blurb "Java" "java-mode" (concat
-- 
2.28.0