dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

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