dotemacs

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

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