csharp-mode.el (18455B)
1 ;;; csharp-mode.el --- C# mode derived mode -*- 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")) 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 31 32 (require 'cc-mode) 33 (require 'cc-langs) 34 35 (eval-when-compile 36 (require 'cc-fonts)) 37 38 (require 'csharp-compilation) 39 40 (defgroup csharp nil 41 "Major mode for editing C# code." 42 :group 'prog-mode) 43 44 (eval-and-compile 45 (defconst csharp--regex-identifier 46 "[A-Za-z][A-Za-z0-9_]*" 47 "Regex describing an dentifier in C#.") 48 49 (defconst csharp--regex-identifier-matcher 50 (concat "\\(" csharp--regex-identifier "\\)") 51 "Regex matching an identifier in C#.") 52 53 (defconst csharp--regex-type-name 54 "[A-Z][A-Za-z0-9_]*" 55 "Regex describing a type identifier in C#.") 56 57 (defconst csharp--regex-type-name-matcher 58 (concat "\\(" csharp--regex-type-name "\\)") 59 "Regex matching a type identifier in C#.") 60 61 (defconst csharp--regex-using-or-namespace 62 (concat "^using" "\\|" "namespace" 63 "\\s *" 64 csharp--regex-type-name-matcher) 65 "Regex matching identifiers after a using or namespace 66 declaration.")) 67 68 (eval-and-compile 69 (c-add-language 'csharp-mode 'java-mode)) 70 71 (c-lang-defconst c-make-mode-syntax-table 72 csharp `(lambda () 73 (let ((table (make-syntax-table))) 74 (c-populate-syntax-table table) 75 (modify-syntax-entry ?@ "_" table) 76 table))) 77 78 (c-lang-defconst c-identifier-syntax-modifications 79 csharp (append '((?@ . "w")) 80 (c-lang-const c-identifier-syntax-modifications))) 81 82 (c-lang-defconst c-symbol-start 83 csharp (concat "[" c-alpha "_@]")) 84 85 (c-lang-defconst c-opt-type-suffix-key 86 csharp (concat "\\(\\[" (c-lang-const c-simple-ws) "*\\]\\|\\?\\)")) 87 88 (c-lang-defconst c-identifier-ops 89 csharp '((left-assoc "."))) 90 91 (c-lang-defconst c-overloadable-operators 92 csharp '("+" "-" "*" "/" "%" "&" "|" "^" "<<" ">>" "==" 93 "!=" ">" "<" ">=" "<=")) 94 95 (c-lang-defconst c-multiline-string-start-char 96 csharp ?@) 97 98 (c-lang-defconst c-ml-string-opener-re 99 ;; "\\(\\(?:@\\$?\\)\\(\"\\)\\)" 100 csharp 101 (rx 102 (group 103 (or "@" "@$") 104 (group "\"")))) 105 106 (c-lang-defconst c-ml-string-max-opener-len 107 csharp 3) 108 109 (c-lang-defconst c-ml-string-max-closer-len 110 csharp 2) 111 112 (c-lang-defconst c-ml-string-any-closer-re 113 ;; "\\(?:\"\"\\)*\\(\\(\"\\)\\)\\(?:[^\"]\\|\\'\\)" 114 csharp 115 (rx 116 (seq 117 (zero-or-more "\"\"") 118 (group 119 (group "\"")) 120 (or (not (any "\"")) eos)))) 121 122 (c-lang-defconst c-ml-string-back-closer-re 123 ;; "\\(?:\\`\\|[^\"]\\)\"*" 124 csharp 125 (rx 126 (seq 127 (or bos 128 (not (any "\""))) 129 (zero-or-more "\"")))) 130 131 (c-lang-defconst c-type-prefix-kwds 132 csharp '("class" "interface" "struct")) 133 134 (c-lang-defconst c-class-decl-kwds 135 csharp '("class" "interface" "struct")) 136 137 ;;; Keyword lists 138 139 (c-lang-defconst c-primitive-type-kwds 140 csharp '("bool" "byte" "sbyte" "char" "decimal" "double" "float" "int" "uint" 141 "long" "ulong" "short" "ushort" "void" "object" "string" "var")) 142 143 (c-lang-defconst c-other-decl-kwds 144 csharp nil) 145 146 (c-lang-defconst c-type-list-kwds 147 csharp nil) 148 149 (c-lang-defconst c-other-block-decl-kwds 150 csharp nil) 151 152 (c-lang-defconst c-return-kwds 153 csharp '("return")) 154 155 (c-lang-defconst c-typedef-kwds 156 csharp nil) 157 158 (c-lang-defconst c-typeof-kwds 159 csharp '("typeof" "is" "as")) 160 161 (c-lang-defconst c-type-modifier-prefix-kwds 162 csharp '("volatile")) 163 164 (c-lang-defconst c-type-modifier-kwds 165 csharp '("readonly" "new")) 166 167 (c-lang-defconst c-brace-list-decl-kwds 168 csharp '("enum" "new")) 169 170 (c-lang-defconst c-recognize-post-brace-list-type-p 171 csharp t) 172 173 (c-lang-defconst c-ref-list-kwds 174 csharp nil) 175 176 (c-lang-defconst c-using-kwds 177 csharp '("using")) 178 179 (c-lang-defconst c-equals-type-clause-kwds 180 csharp '("using")) 181 182 (defun csharp-at-vsemi-p (&optional pos) 183 (if pos (goto-char pos)) 184 (save-excursion 185 (beginning-of-line) 186 (c-forward-syntactic-ws) 187 (looking-at "using\\s *("))) 188 189 (c-lang-defconst c-at-vsemi-p-fn 190 csharp 'csharp-at-vsemi-p) 191 192 (defun csharp-vsemi-status-unknown () t) 193 194 (c-lang-defconst c-vsemi-status-unknown-p-fn 195 csharp 'csharp-vsemi-status-unknown-p) 196 197 198 (c-lang-defconst c-modifier-kwds 199 csharp '("abstract" "default" "final" "native" "private" "protected" 200 "public" "partial" "internal" "readonly" "static" "event" "transient" 201 "volatile" "sealed" "ref" "out" "virtual" "implicit" "explicit" 202 "fixed" "override" "params" "async" "await" "extern" "unsafe" 203 "get" "set" "this" "const" "delegate")) 204 205 (c-lang-defconst c-other-kwds 206 csharp '("select" "from" "where" "join" "in" "on" "equals" "into" 207 "orderby" "ascending" "descending" "group" "when" 208 "let" "by" "namespace")) 209 210 (c-lang-defconst c-colon-type-list-kwds 211 csharp '("class" "struct" "interface")) 212 213 (c-lang-defconst c-block-stmt-1-kwds 214 csharp '("do" "else" "finally" "try")) 215 216 (c-lang-defconst c-block-stmt-1-2-kwds 217 csharp '("try")) 218 219 (c-lang-defconst c-block-stmt-2-kwds 220 csharp '("for" "if" "switch" "while" "catch" "foreach" "fixed" "checked" 221 "unchecked" "using" "lock")) 222 223 (c-lang-defconst c-simple-stmt-kwds 224 csharp '("break" "continue" "goto" "throw" "return" "yield")) 225 226 (c-lang-defconst c-constant-kwds 227 csharp '("true" "false" "null" "value")) 228 229 (c-lang-defconst c-primary-expr-kwds 230 csharp '("this" "base" "operator")) 231 232 (c-lang-defconst c-inexpr-class-kwds 233 csharp nil) 234 235 (c-lang-defconst c-class-decl-kwds 236 csharp '("class" "struct" "interface")) 237 238 (c-lang-defconst c-std-abbrev-keywords 239 csharp (append (c-lang-const c-std-abbrev-keywords) '("catch" "finally"))) 240 241 (c-lang-defconst c-decl-prefix-re 242 csharp "\\([{}(;,<]+\\)") 243 244 (c-lang-defconst c-recognize-typeless-decls 245 csharp t) 246 247 (c-lang-defconst c-recognize-<>-arglists 248 csharp t) 249 250 (c-lang-defconst c-opt-cpp-prefix 251 csharp "\\s *#\\s *") 252 253 (c-lang-defconst c-opt-cpp-macro-define 254 csharp (if (c-lang-const c-opt-cpp-prefix) 255 "define")) 256 257 (c-lang-defconst c-cpp-message-directives 258 csharp '("error" "warning" "region")) 259 260 (c-lang-defconst c-cpp-expr-directives 261 csharp '("if" "elif")) 262 263 (c-lang-defconst c-other-op-syntax-tokens 264 csharp (append '("#") 265 (c-lang-const c-other-op-syntax-tokens))) 266 267 (c-lang-defconst c-line-comment-starter 268 csharp "//") 269 270 (c-lang-defconst c-doc-comment-start-regexp 271 csharp "///") 272 273 (c-add-style "csharp" 274 '("java" 275 (c-basic-offset . 4) 276 (c-comment-only-line-offset . (0 . 0)) 277 (c-offsets-alist . ((inline-open . 0) 278 (arglist-intro . +) 279 (arglist-close . 0) 280 (inexpr-class . 0) 281 (case-label . +) 282 (cpp-macro . c-lineup-dont-change) 283 (substatement-open . 0))))) 284 285 (eval-and-compile 286 (unless (or (stringp c-default-style) 287 (assoc 'csharp-mode c-default-style)) 288 (setq c-default-style 289 (cons '(csharp-mode . "csharp") 290 c-default-style)))) 291 292 (defun csharp--color-forwards (font-lock-face) 293 (let (id-beginning) 294 (goto-char (match-beginning 0)) 295 (forward-word) 296 (while (and (not (or (eq (char-after) ?\;) 297 (eq (char-after) ?\{))) 298 (progn 299 (forward-char) 300 (c-forward-syntactic-ws) 301 (setq id-beginning (point)) 302 (> (skip-chars-forward 303 (c-lang-const c-symbol-chars)) 304 0)) 305 (not (get-text-property (point) 'face))) 306 (c-put-font-lock-face id-beginning (point) font-lock-face) 307 (c-forward-syntactic-ws)))) 308 309 (c-lang-defconst c-basic-matchers-before 310 csharp `( 311 ;; Warning face on unclosed strings 312 ,@(if (version< emacs-version "27.0") 313 ;; Taken from 26.1 branch 314 `(,(c-make-font-lock-search-function 315 (concat ".\\(" c-string-limit-regexp "\\)") 316 '((c-font-lock-invalid-string)))) 317 `(("\\s|" 0 font-lock-warning-face t nil))) 318 319 ;; Invalid single quotes 320 c-font-lock-invalid-single-quotes 321 322 ;; Keyword constants 323 ,@(when (c-lang-const c-constant-kwds) 324 (let ((re (c-make-keywords-re nil (c-lang-const c-constant-kwds)))) 325 `((eval . (list ,(concat "\\<\\(" re "\\)\\>") 326 1 c-constant-face-name))))) 327 328 ;; Keywords except the primitive types. 329 ,`(,(concat "\\<" (c-lang-const c-regular-keywords-regexp)) 330 1 font-lock-keyword-face) 331 332 ;; Chained identifiers in using/namespace statements 333 ,`(,(c-make-font-lock-search-function 334 csharp--regex-using-or-namespace 335 `((csharp--color-forwards font-lock-variable-name-face) 336 nil 337 (goto-char (match-end 0))))) 338 339 340 ;; Negation character 341 (eval . (list "\\(!\\)[^=]" 1 c-negation-char-face-name)) 342 343 ;; Types after 'new' 344 (eval . (list (concat "\\<new\\> *" csharp--regex-type-name-matcher) 345 1 font-lock-type-face)) 346 347 ;; Single identifier in attribute 348 (eval . (list (concat "\\[" csharp--regex-type-name-matcher "\\][^;]") 349 1 font-lock-variable-name-face t)) 350 351 ;; Function names 352 (eval . (list "\\([A-Za-z0-9_]+\\)\\(<[a-zA-Z0-9, ]+>\\)?(" 353 1 font-lock-function-name-face)) 354 355 ;; Nameof 356 (eval . (list (concat "\\(\\<nameof\\>\\) *(") 357 1 font-lock-function-name-face)) 358 359 (eval . (list (concat "\\<nameof\\> *( *" 360 csharp--regex-identifier-matcher 361 " *) *") 362 1 font-lock-variable-name-face)) 363 364 ;; Catch statements with type only 365 (eval . (list (concat "\\<catch\\> *( *" 366 csharp--regex-type-name-matcher 367 " *) *") 368 1 font-lock-type-face)) 369 )) 370 371 (c-lang-defconst c-basic-matchers-after 372 csharp (append 373 ;; Merge with cc-mode defaults - enables us to add more later 374 (c-lang-const c-basic-matchers-after))) 375 376 (defcustom csharp-codedoc-tag-face 'c-doc-markup-face-name 377 "Face to be used on the codedoc docstring tags. 378 379 Should be one of the font lock faces, such as 380 `font-lock-variable-name-face' and friends. 381 382 Needs to be set before `csharp-mode' is loaded, because of 383 compilation and evaluation time conflicts." 384 :type 'symbol) 385 386 (defcustom csharp-font-lock-extra-types 387 (list csharp--regex-type-name) 388 (c-make-font-lock-extra-types-blurb "C#" "csharp-mode" (concat)) 389 :type 'c-extra-types-widget 390 :group 'c) 391 392 (defconst csharp-font-lock-keywords-1 (c-lang-const c-matchers-1 csharp) 393 "Minimal font locking for C# mode.") 394 395 (defconst csharp-font-lock-keywords-2 (c-lang-const c-matchers-2 csharp) 396 "Fast normal font locking for C# mode.") 397 398 (defconst csharp-font-lock-keywords-3 (c-lang-const c-matchers-3 csharp) 399 "Accurate normal font locking for C# mode.") 400 401 (defvar csharp-font-lock-keywords csharp-font-lock-keywords-3 402 "Default expressions to highlight in C# mode.") 403 404 (defun csharp-font-lock-keywords-2 () 405 (c-compose-keywords-list csharp-font-lock-keywords-2)) 406 (defun csharp-font-lock-keywords-3 () 407 (c-compose-keywords-list csharp-font-lock-keywords-3)) 408 (defun csharp-font-lock-keywords () 409 (c-compose-keywords-list csharp-font-lock-keywords)) 410 411 ;;; Doc comments 412 413 (defconst codedoc-font-lock-doc-comments 414 ;; Most of this is taken from the javadoc example, however, we don't use the 415 ;; '@foo' syntax, so I removed that. Supports the XML tags only 416 `((,(concat "</?\\sw" ; XML tags. 417 "\\(" 418 (concat "\\sw\\|\\s \\|[=\n\r*.:]\\|" 419 "\"[^\"]*\"\\|'[^']*'") 420 "\\)*/?>") 421 0 ,csharp-codedoc-tag-face prepend nil) 422 ;; ("\\([a-zA-Z0-9_]+\\)=" 0 font-lock-variable-name-face prepend nil) 423 ;; ("\".*\"" 0 font-lock-string-face prepend nil) 424 ("&\\(\\sw\\|[.:]\\)+;" ; XML entities. 425 0 ,csharp-codedoc-tag-face prepend nil))) 426 427 (defconst codedoc-font-lock-keywords 428 `((,(lambda (limit) 429 (c-font-lock-doc-comments "///" limit 430 codedoc-font-lock-doc-comments))))) 431 432 ;;; End of doc comments 433 434 ;;; Adding syntax constructs 435 436 (advice-add 'c-looking-at-inexpr-block 437 :around #'csharp-looking-at-inexpr-block) 438 439 (defun csharp-looking-at-inexpr-block (orig-fun &rest args) 440 (let ((res (csharp-at-lambda-header))) 441 (if res 442 res 443 (apply orig-fun args)))) 444 445 (defun csharp-at-lambda-header () 446 (save-excursion 447 (c-backward-syntactic-ws) 448 (unless (bobp) 449 (backward-char) 450 (c-safe (goto-char (scan-sexps (point) -1))) 451 (when (or (looking-at "([[:alnum:][:space:]_,]*)[ \t\n]*=>[ \t\n]*{") 452 (looking-at "[[:alnum:]_]+[ \t\n]*=>[ \t\n]*{")) 453 ;; If we are at a C# lambda header 454 (cons 'inexpr (point)))))) 455 456 (advice-add 'c-guess-basic-syntax 457 :around #'csharp-guess-basic-syntax) 458 459 (defun csharp-guess-basic-syntax (orig-fun &rest args) 460 (cond 461 (;; Attributes 462 (save-excursion 463 (goto-char (c-point 'iopl)) 464 (and 465 (eq (char-after) ?\[) 466 (save-excursion 467 (c-go-list-forward) 468 (and (eq (char-before) ?\]) 469 (not (eq (char-after) ?\;)))))) 470 `((annotation-top-cont ,(c-point 'iopl)))) 471 472 ((and 473 ;; Heuristics to find object initializers 474 (save-excursion 475 ;; Next non-whitespace character should be '{' 476 (goto-char (c-point 'boi)) 477 (eq (char-after) ?{)) 478 (save-excursion 479 ;; 'new' should be part of the line 480 (goto-char (c-point 'iopl)) 481 (looking-at ".*\\s *new\\s *.*")) 482 ;; Line should not already be terminated 483 (save-excursion 484 (goto-char (c-point 'eopl)) 485 (or (not (eq (char-before) ?\;)) 486 (not (eq (char-before) ?\{))))) 487 (if (save-excursion 488 ;; if we have a hanging brace on line before 489 (goto-char (c-point 'eopl)) 490 (eq (char-before) ?\{)) 491 `((brace-list-intro ,(c-point 'iopl))) 492 `((block-open) (statement ,(c-point 'iopl))))) 493 (t 494 (apply orig-fun args)))) 495 496 ;;; End of new syntax constructs 497 498 499 500 ;;; Fix for strings on version 27.1 501 502 (when (version= emacs-version "27.1") 503 ;; See: 504 ;; https://github.com/emacs-csharp/csharp-mode/issues/175 505 ;; https://github.com/emacs-csharp/csharp-mode/issues/151 506 ;; for the full story. 507 (defun c-pps-to-string-delim (end) 508 (let* ((start (point)) 509 (no-st-s `(0 nil nil ?\" nil nil 0 nil ,start nil nil)) 510 (st-s `(0 nil nil t nil nil 0 nil ,start nil nil)) 511 no-st-pos st-pos 512 ) 513 (parse-partial-sexp start end nil nil no-st-s 'syntax-table) 514 (setq no-st-pos (point)) 515 (goto-char start) 516 (while (progn 517 (parse-partial-sexp (point) end nil nil st-s 'syntax-table) 518 (unless (bobp) 519 (c-clear-syn-tab (1- (point)))) 520 (setq st-pos (point)) 521 (and (< (point) end) 522 (not (eq (char-before) ?\"))))) 523 (goto-char (min no-st-pos st-pos)) 524 nil)) 525 526 (defun c-multiline-string-check-final-quote () 527 (let (pos-ll pos-lt) 528 (save-excursion 529 (goto-char (point-max)) 530 (skip-chars-backward "^\"") 531 (while 532 (and 533 (not (bobp)) 534 (cond 535 ((progn 536 (setq pos-ll (c-literal-limits) 537 pos-lt (c-literal-type pos-ll)) 538 (memq pos-lt '(c c++))) 539 ;; In a comment. 540 (goto-char (car pos-ll))) 541 ((save-excursion 542 (backward-char) ; over " 543 (c-is-escaped (point))) 544 ;; At an escaped string. 545 (backward-char) 546 t) 547 (t 548 ;; At a significant " 549 (c-clear-syn-tab (1- (point))) 550 (setq pos-ll (c-literal-limits) 551 pos-lt (c-literal-type pos-ll)) 552 nil))) 553 (skip-chars-backward "^\"")) 554 (cond 555 ((bobp)) 556 ((eq pos-lt 'string) 557 (c-put-syn-tab (1- (point)) '(15))) 558 (t nil)))))) 559 560 ;;; End of fix for strings on version 27.1 561 562 563 564 (defvar csharp-mode-syntax-table 565 (funcall (c-lang-const c-make-mode-syntax-table csharp)) 566 "Syntax table used in csharp-mode buffers.") 567 568 (defvar csharp-mode-map 569 (let ((map (c-make-inherited-keymap))) 570 map) 571 "Keymap used in csharp-mode buffers.") 572 573 (easy-menu-define csharp-mode-menu csharp-mode-map "C# Mode Commands" 574 (cons "C#" (c-lang-const c-mode-menu csharp))) 575 576 ;;;###autoload 577 (add-to-list 'auto-mode-alist '("\\.cs\\'" . csharp-mode)) 578 579 ;; Custom variables 580 ;;;###autoload 581 (define-derived-mode csharp-mode prog-mode "C#" 582 "Major mode for editing Csharp code. 583 584 Key bindings: 585 \\{csharp-mode-map}" 586 :after-hook (c-update-modeline) 587 (c-initialize-cc-mode t) 588 (c-init-language-vars csharp-mode) 589 (c-common-init 'csharp-mode) 590 (setq-local c-doc-comment-style '((csharp-mode . codedoc))) 591 (run-mode-hooks 'c-mode-common-hook)) 592 593 (provide 'csharp-mode) 594 595 ;;; csharp-mode.el ends here