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