;;; csharp-mode.el --- C# mode derived mode ;; Author: Dylan R. E. Moonfire ;; Maintainer: Dylan R. E. Moonfire ;; Created: Feburary 2005 ;; Modified: February 2010 ;; Version: 0.7.4 - Dino Chiesa ;; Keywords: c# languages oop mode ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 2 of the License, or ;; (at your option) any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; see the file COPYING. If not, write to ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, ;; Boston, MA 02111-1307, USA. ;;; Commentary: ;; ;; This is a separate mode to implement the C# constructs and ;; font-locking. It is based on the java-mode example from cc-mode. ;; ;; csharp-mode requires CC Mode 5.30 or later. It works with ;; cc-mode 5.31.3, which is current at this time. ;; ;; Features: ;; ;; - font-lock and indent of C# syntax including: ;; all c# keywords and major syntax ;; attributes that decorate methods, classes, fields, properties ;; enum types ;; #if/#endif #region/#endregion ;; instance initializers ;; anonymous functions and methods ;; verbatim literal strings (those that begin with @) ;; generics ;; ;; - automagic code-doc generation when you type three slashes. ;; ;; - intelligent inserttion of matched pairs of curly braces. ;; ;; - sets the compiler regex for next-error, for csc.exe output. ;; ;; ;;; To use: ;; ;; put this in your .emacs: ;; ;; (autoload 'csharp-mode "csharp-mode" "Major mode for editing C# code." t) ;; ;; or: ;; ;; (require 'csharp-mode) ;; ;; ;; AND: ;; ;; (setq auto-mode-alist ;; (append '(("\\.cs$" . csharp-mode)) auto-mode-alist)) ;; (defun my-csharp-mode-fn () ;; "function that runs when csharp-mode is initialized for a buffer." ;; ...insert your code here... ;; ...most commonly, your custom key bindings ... ;; ) ;; (add-hook 'csharp-mode-hook 'my-csharp-mode-fn t) ;; ;; ;;; Bugs: ;; ;; Namespaces in the using statements are not fontified. Should do in ;; c-basic-matchers-before or c-basic-matchers-after. ;; ;; Method names with a preceding attribute are not fontified. ;; ;; Field/Prop names inside object initializers are fontified only ;; if the null constructor is used, with no parens. ;; ;; This code doesn't seem to work when you compile it, then ;; load/require in the emacs file. You will get an error (error ;; "`c-lang-defconst' must be used in a file") which happens because ;; cc-mode doesn't think it is in a buffer while loading directly ;; from the init. However, if you call it based on a file extension, ;; it works properly. Interestingly enough, this doesn't happen if ;; you don't byte-compile cc-mode. ;; ;; ;; ;; Todo: ;; ;; Get csharp-mode.el accepted as part of the emacs standard distribution. ;; Must contact monnier at iro.umontreal.ca to make this happen. ;; ;; ;; ;; Acknowledgements: ;; ;; Thanks to Alan Mackenzie and Stefan Monnier for answering questions ;; and making suggestions. ;; ;; ;;; Versions: ;; ;; 0.1.0 - Initial release. ;; 0.2.0 - Fixed the identification on the "enum" keyword. ;; - Fixed the font-lock on the "base" keyword ;; 0.3.0 - Added a regex to fontify attributes. It isn't the ;; the best method, but it handles single-like attributes ;; well. ;; - Got "super" not to fontify as a keyword. ;; - Got extending classes and interfaces to fontify as something. ;; 0.4.0 - Removed the attribute matching because it broke more than ;; it fixed. ;; - Corrected a bug with namespace not being properly identified ;; and treating the class level as an inner object, which screwed ;; up formatting. ;; - Added "partial" to the keywords. ;; 0.5.0 - Found bugs with compiled cc-mode and loading from init files. ;; - Updated the eval-when-compile to code to let the mode be ;; compiled. ;; 0.6.0 - Added the c-filter-ops patch for 5.31.1 which made that ;; function in cc-langs.el unavailable. ;; - Added a csharp-lineup-region for indention #region and ;; #endregion block differently. ;; 0.7.0 - Added autoload so update-directory-autoloads works ;; (Thank you, Nikolaj Schumacher) ;; - Fontified the entire #region and #endregion lines. ;; - Initial work to get get, set, add, remove font-locked. ;; 0.7.1 - Added option to indent #if/endif with code ;; - Fixed c-opt-cpp-prefix defn (it must not include the BOL ;; char (^). ;; - proper fontification and indent of classes that inherit ;; (previously the colon was confusing the parser) ;; - reclassified namespace as a block beginner ;; - removed $ as a legal symbol char - not legal in C#. ;; - added struct to c-class-decl-kwds so indent is correct ;; within a struct. ;; 0.7.2 - Added automatic codedoc insertion. ;; 0.7.3 - Instance initializers (new Type { ... } ) and ;; (new Type() { ...} ) are now indented properly. ;; - proper fontification and indent of enums as brace-list-*, ;; including special treatment for enums that explicitly ;; inherit from an int type. Previously the colon was ;; confusing the parser. ;; - proper fontification of verbatim literal strings, ;; including those that end in slash. This edge case was not ;; handled at all before; it is now handled correctly. ;; - code cleanup and organization; removed the linefeed. ;; - intelligent curly-brace insertion ;; 0.7.4 - added a C# style ;; - using is now a keyword and gets fontified ;; - fixed a bug that had crept into the codedoc insertion ;; (require 'cc-mode) (message (concat "Loading " load-file-name)) ;; ================================================================== ;; c# upfront stuff ;; ================================================================== ;; This is a copy of the function in cc-mode which is used to handle ;; the eval-when-compile which is needed during other times. (defun c-filter-ops (ops opgroup-filter op-filter &optional xlate) ;; See cc-langs.el, a direct copy. (unless (listp (car-safe ops)) (setq ops (list ops))) (cond ((eq opgroup-filter t) (setq opgroup-filter (lambda (opgroup) t))) ((not (functionp opgroup-filter)) (setq opgroup-filter `(lambda (opgroup) (memq opgroup ',opgroup-filter))))) (cond ((eq op-filter t) (setq op-filter (lambda (op) t))) ((stringp op-filter) (setq op-filter `(lambda (op) (string-match ,op-filter op))))) (unless xlate (setq xlate 'identity)) (c-with-syntax-table (c-lang-const c-mode-syntax-table) (delete-duplicates (mapcan (lambda (opgroup) (when (if (symbolp (car opgroup)) (when (funcall opgroup-filter (car opgroup)) (setq opgroup (cdr opgroup)) t) t) (mapcan (lambda (op) (when (funcall op-filter op) (let ((res (funcall xlate op))) (if (listp res) res (list res))))) opgroup))) ops) :test 'equal))) ;; These are only required at compile time to get the sources for the ;; language constants. (The cc-fonts require and the font-lock ;; related constants could additionally be put inside an ;; (eval-after-load "font-lock" ...) but then some trickery is ;; necessary to get them compiled.) (eval-when-compile (let ((load-path (if (and (boundp 'byte-compile-dest-file) (stringp byte-compile-dest-file)) (cons (file-name-directory byte-compile-dest-file) load-path) load-path))) (load "cc-mode" nil t) (load "cc-fonts" nil t) (load "cc-langs" nil t))) (eval-and-compile ;; Make our mode known to the language constant system. Use Java ;; mode as the fallback for the constants we don't change here. ;; This needs to be done also at compile time since the language ;; constants are evaluated then. (c-add-language 'csharp-mode 'java-mode)) ;; ================================================================== ;; end of c# upfront stuff ;; ================================================================== ;; ================================================================== ;; csharp-mode utility and feature defuns ;; ================================================================== ;; Indention: csharp-mode follows normal indention rules except for ;; when indenting the #region and #endregion blocks. This function ;; defines a custom indention to indent the #region blocks properly ;; (defun csharp-lineup-region (langelem) "Indent all #region and #endregion blocks inline with code while retaining normal column-zero indention for #if and the other processing blocks. To use this indenting just put the following in your emacs file: (c-set-offset 'cpp-macro 'csharp-lineup-region) An alternative is to use `csharp-lineup-if-and-region'. " (save-excursion (back-to-indentation) (if (re-search-forward "#\\(end\\)?region" (c-point 'eol) [0]) 0 [0]))) (defun csharp-lineup-if-and-region (langelem) "Indent all #region/endregion blocks and #if/endif blocks inline with code while retaining normal column-zero indention for any other processing blocks. To use this indenting just put the following in your emacs file: (c-set-offset 'cpp-macro 'csharp-lineup-if-and-region) Another option is to use `csharp-lineup-region'. " (save-excursion (back-to-indentation) (if (re-search-forward "#\\(\\(end\\)?\\(if\\|region\\)\\|else\\)" (c-point 'eol) [0]) 0 [0]))) (defun csharp-insert-open-brace () "Intelligently insert a pair of curly braces. This fn is most often bound to the open-curly brace, with (local-set-key (kbd \"{\") 'csharp-insert-open-brace) The default binding for an open curly brace in cc-modes is often `c-electric-brace' or `skeleton-pair-insert-maybe'. The former can be configured to insert newlines around braces in various syntactic positions. The latter inserts a pair of braces and then does not insert a newline, and does not indent. This fn provides another option, with some additional intelligence for csharp-mode. When you type an open curly, the appropriate pair of braces appears, with spacing and indent set in a context-sensitive manner. Within a string literal, you just get a pair of braces, and point is set between them. Following an equals sign, you get a pair of braces, with a semincolon appended. Otherwise, you get the open brace on a new line, with the closing brace on the line following. There may be another way to get this to happen appropriately just within emacs, but I could not figure out how to do it. So I wrote this alternative. " (interactive) (let (tpoint (in-string (string= (csharp-in-literal) "string")) (preceding3 (save-excursion (and (skip-chars-backward " ") (> (- (point) 2) (point-min)) (buffer-substring-no-properties (point) (- (point) 3))))) (one-word-back (save-excursion (backward-word 2) (thing-at-point 'word)))) (cond ;; Case 1: inside a string literal? ;; -------------------------------------------- ;; If so, then just insert a pair of braces and put the point ;; between them. The most common case is a format string for ;; String.Format() or Console.WriteLine(). (in-string (self-insert-command 1) (insert "}") (backward-char)) ;; Case 2: the open brace starts an array initializer. ;; -------------------------------------------- ;; When the last non-space was an equals sign or square brackets, ;; then it's an initializer. ((save-excursion (backward-sexp) (looking-at "\\(\\w+\\b *=\\|[[]]+\\)")) (self-insert-command 1) (insert " };") (backward-char 3)) ;; Case 3: the open brace starts an instance initializer ;; -------------------------------------------- ;; If one-word-back was "new", then it's an object initializer. ((string= one-word-back "new") (save-excursion (message "object initializer") (setq tpoint (point)) ;; prepare to indent-region later (newline) (self-insert-command 1) (newline-and-indent) (newline) (insert "};") (c-indent-region tpoint (point)) (previous-line) (indent-according-to-mode) (end-of-line) (setq tpoint (point))) (goto-char tpoint)) ;; Case 4: a lambda initialier. ;; -------------------------------------------- ;; If the open curly follows =>, then it's a lambda initializer. ((string= (substring preceding3 -2) "=>") (message "lambda init") (self-insert-command 1) (insert " }") (backward-char 2)) ;; else, it's a new scope. (if, while, class, etc) (t (save-excursion (message "new scope") (set-mark (point)) ;; prepare to indent-region later ;; check if the prior sexp is on the same line (if (save-excursion (let ((curline (line-number-at-pos)) (aftline (progn (backward-sexp) (line-number-at-pos)))) (= curline aftline))) (newline-and-indent)) (self-insert-command 1) (c-indent-line-or-region) (end-of-line) (newline) (insert "}") ;;(c-indent-command) ;; not sure of the difference here (c-indent-line-or-region) (previous-line) (end-of-line) (newline-and-indent) ;; point ends up on an empty line, within the braces, properly indented (setq tpoint (point))) (goto-char tpoint))))) ;; ================================================================== ;; end of csharp-mode utility and feature defuns ;; ================================================================== ;; ================================================================== ;; c# values for "language constants" defined in cc-langs.el ;; ================================================================== ;; Java uses a series of regexes to change the font-lock for class ;; references. The problem comes in because Java uses Pascal (leading ;; space in names, SomeClass) for class and package names, but ;; Camel-casing (initial lowercase, upper case in words, ;; i.e. someVariable) for variables. The notation suggested by EMCA for C# is ;; to use Pascal notation for everything, except inner variables. So, ;; the Java regex and formatting produces very wrong results in C#. ;;(error (byte-compile-dest-file)) ;;(error (c-get-current-file)) (c-lang-defconst c-opt-after-id-concat-key csharp (if (c-lang-const c-opt-identifier-concat-key) (c-lang-const c-symbol-start))) (c-lang-defconst c-basic-matchers-before csharp `( ;;;; Font-lock the attributes by searching for the ;;;; appropriate regex and marking it as TODO. ;;,`(,(concat "\\(" csharp-attribute-regex "\\)") ;; 0 font-lock-function-name-face) ;; Put a warning face on the opener of unclosed strings that ;; can't span lines. Later font ;; lock packages have a `font-lock-syntactic-face-function' for ;; this, but it doesn't give the control we want since any ;; fontification done inside the function will be ;; unconditionally overridden. ,(c-make-font-lock-search-function ;; Match a char before the string starter to make ;; `c-skip-comments-and-strings' work correctly. (concat ".\\(" c-string-limit-regexp "\\)") '((c-font-lock-invalid-string))) ;; Fontify keyword constants. ,@(when (c-lang-const c-constant-kwds) (let ((re (c-make-keywords-re nil (c-lang-const c-constant-kwds)))) `((eval . (list ,(concat "\\<\\(" re "\\)\\>") 1 c-constant-face-name))))) ;; Fontify all keywords except the primitive types. ,`(,(concat "\\<" (c-lang-const c-regular-keywords-regexp)) 1 font-lock-keyword-face) ;; Fontify leading identifiers in fully qualified names like ;; "Foo.Bar". ,@(when (c-lang-const c-opt-identifier-concat-key) `((,(byte-compile `(lambda (limit) (while (re-search-forward ,(concat "\\(\\<" ; 1 "\\(" (c-lang-const c-symbol-key) "\\)" ; 2 "[ \t\n\r\f\v]*" (c-lang-const c-opt-identifier-concat-key) "[ \t\n\r\f\v]*" "\\)" "\\(" (c-lang-const c-opt-after-id-concat-key) "\\)") limit t) (unless (progn (goto-char (match-beginning 0)) (c-skip-comments-and-strings limit)) (or (get-text-property (match-beginning 2) 'face) (c-put-font-lock-face (match-beginning 2) (match-end 2) c-reference-face-name)) (goto-char (match-end 1))))))))) )) ;; C# does not allow a leading qualifier operator. It also doesn't ;; allow the ".*" construct of Java. So, we redo this regex without ;; the "\\|\\*" regex. (c-lang-defconst c-identifier-key csharp (concat "\\(" (c-lang-const c-symbol-key) "\\)" ; 1 (concat "\\(" "[ \t\n\r\f\v]*" (c-lang-const c-opt-identifier-concat-key) "[ \t\n\r\f\v]*" (concat "\\(" "\\(" (c-lang-const c-symbol-key) "\\)" "\\)") "\\)*"))) ;; C# has a few rules that are slightly different than Java for ;; operators. This also removed the Java's "super" and replaces it ;; with the C#'s "base". (c-lang-defconst c-operators csharp `((prefix "base"))) ;; C# uses CPP-like prefixes to mark #define, #region/endregion, ;; #if/else/endif, and #pragma. This regexp matches the prefix, ;; not including the beginning-of-line (BOL), and not including ;; the term after the prefix (define, pragma, etc). This regexp says ;; whitespace, followed by the prefix, followed by maybe more whitespace. (c-lang-defconst c-opt-cpp-prefix csharp "\\s *#\\s *") ;; there are no message directives in C# (c-lang-defconst c-cpp-message-directives csharp nil) (c-lang-defconst c-cpp-expr-directives csharp '("if")) (c-lang-defconst c-opt-cpp-macro-define csharp "define") ;; $ is not a legal char in an identifier in C#. So we need to ;; create a csharp-specific definition of this constant. (c-lang-defconst c-symbol-chars csharp (concat c-alnum "_")) (c-lang-defconst c-colon-type-list-kwds csharp '("class")) (c-lang-defconst c-block-prefix-disallowed-chars ;; Allow ':' for inherit list starters. csharp (set-difference (c-lang-const c-block-prefix-disallowed-chars) '(?: ?,))) (c-lang-defconst c-assignment-operators csharp '("=" "*=" "/=" "%=" "+=" "-=" ">>=" "<<=" "&=" "^=" "|=")) (c-lang-defconst c-primitive-type-kwds ;; ECMA-344, S8 csharp '("object" "string" "sbyte" "short" "int" "long" "byte" "ushort" "uint" "ulong" "float" "double" "bool" "char" "decimal" "void")) ;; The keywords that define that the following is a type, such as a ;; class definition. (c-lang-defconst c-type-prefix-kwds ;; ECMA-344, S? csharp '("class" "interface" "struct")) ;; no enum here. ;; we want enum to be a brace list. ;; Type modifier keywords. They appear anywhere in types, but modify ;; instead of create one. (c-lang-defconst c-type-modifier-kwds ;; EMCA-344, S? csharp '("readonly" "const")) ;; Tue, 20 Apr 2010 16:02 ;; need to vverify that this works for lambdas... (c-lang-defconst c-special-brace-lists csharp '((?{ . ?}) )) ;; dinoch ;; Thu, 22 Apr 2010 18:54 ;; ;; No idea why this isn't getting set properly in the first place. ;; In cc-langs.el, it is set to the union of a bunch of things, none ;; of which include "new", or "enum". ;; ;; But somehow both of those show up in the resulting derived regexp. ;; This breaks indentation of instance initializers, such as ;; ;; var x = new Foo { ... }; ;; ;; Based on my inspection, the existing c-lang-defconst should work! ;; I don't know how to fix this c-lang-defconst, so I am re-setting this ;; variable here, to provide the regex explicitly. ;; (c-lang-defconst c-decl-block-key csharp '"\\(namespace\\)\\([^[:alnum:]_]\\|$\\)\\|\\(class\\|interface\\|struct\\)\\([^[:alnum:]_]\\|$\\)" ) ;; Thu, 22 Apr 2010 14:29 ;; I want this to handle var x = new Foo[] { ... }; ;; not sure if necessary. (c-lang-defconst c-inexpr-brace-list-kwds csharp '("new")) ;; ;;(c-lang-defconst c-inexpr-class-kwds ;; ;; csharp '("new")) (c-lang-defconst c-class-decl-kwds ;; EMCA-344, S? csharp '("class" "interface" "struct" )) ;; no "enum"!! ;; The various modifiers used for class and method descriptions. (c-lang-defconst c-modifier-kwds csharp '("public" "partial" "private" "const" "abstract" "protected" "ref" "out" "static" "virtual" "override" "params" "internal")) ;; Thu, 22 Apr 2010 23:02 ;; Based on inspection of the cc-mode code, the c-protection-kwds ;; c-lang-const is used only for objective-c. So the value is ;; irrelevant for csharp. (c-lang-defconst c-protection-kwds csharp nil ;; csharp '("private" "protected" "public" "internal") ) ;; Define the keywords that can have something following after them. (c-lang-defconst c-type-list-kwds csharp '("struct" "class" "interface" "is" "as" "delegate" "event" "set" "get" "add" "remove")) ;; This allows the classes after the : in the class declartion to be ;; fontified. (c-lang-defconst c-typeless-decl-kwds csharp '(":")) ;; Sets up the enum to handle the list properly, and also the new ;; keyword to handle object initializers. This requires a modified ;; c-basic-matchers-after (see above) in order to correctly fontify C# ;; 3.0 object initializers. (c-lang-defconst c-brace-list-decl-kwds csharp '("enum" "new")) ;; Statement keywords followed directly by a substatement. ;; catch is not one of them. (c-lang-defconst c-block-stmt-1-kwds csharp '("do" "try" "finally")) ;; Statement keywords followed by a paren sexp and then by a substatement. (c-lang-defconst c-block-stmt-2-kwds csharp '("for" "if" "switch" "while" "catch" "foreach" "using" "checked" "unchecked" "lock")) ;; Statements that break out of braces (c-lang-defconst c-simple-stmt-kwds csharp '("return" "continue" "break" "throw" "goto" )) ;; Statements that allow a label ;; TODO? (c-lang-defconst c-before-label-kwds csharp nil) ;; Constant keywords (c-lang-defconst c-constant-kwds csharp '("true" "false" "null")) ;; Keywords that start "primary expressions." (c-lang-defconst c-primary-expr-kwds csharp '("this" "base")) ;; Treat namespace as an outer block so class indenting ;; works properly. (c-lang-defconst c-other-block-decl-kwds csharp '("namespace")) (c-lang-defconst c-other-kwds csharp '("in" "sizeof" "typeof" "is" "as" "yield" "where" "select" "from")) (c-lang-defconst c-overloadable-operators ;; EMCA-344, S14.2.1 csharp '("+" "-" "*" "/" "%" "&" "|" "^" "<<" ">>" "==" "!=" ">" "<" ">=" "<=")) ;; This c-cpp-matchers stuff is used for fontification. ;; see cc-font.el ;; ;; There's no preprocessor in C#, but there are still compiler ;; directives to fontify: "#pragma", #region/endregion, #define, #undef, ;; #if/else/endif. (The definitions for the extra keywords above are ;; enough to incorporate them into the fontification regexps for types ;; and keywords, so no additional font-lock patterns are required for ;; keywords.) (c-lang-defconst c-cpp-matchers csharp (cons ;; Use the eval form for `font-lock-keywords' to be able to use ;; the `c-preprocessor-face-name' variable that maps to a ;; suitable face depending on the (X)Emacs version. '(eval . (list "^\\s *\\(#pragma\\|undef\\|define\\)\\>\\(.*\\)" (list 1 c-preprocessor-face-name) '(2 font-lock-string-face))) ;; There are some other things in `c-cpp-matchers' besides the ;; preprocessor support, so include it. (c-lang-const c-cpp-matchers))) (defcustom csharp-font-lock-extra-types nil "*List of extra types (aside from the type keywords) to recognize in C# mode. Each list item should be a regexp matching a single identifier." :type 'list :group 'csharp) (defconst csharp-font-lock-keywords-1 (c-lang-const c-matchers-1 csharp) "Minimal highlighting for C# mode.") (defconst csharp-font-lock-keywords-2 (c-lang-const c-matchers-2 csharp) "Fast normal highlighting for C# mode.") (defconst csharp-font-lock-keywords-3 (c-lang-const c-matchers-3 csharp) "Accurate normal highlighting for C# mode.") (defvar csharp-font-lock-keywords csharp-font-lock-keywords-3 "Default expressions to highlight in C# mode.") (defvar csharp-mode-syntax-table nil "Syntax table used in csharp-mode buffers.") (or csharp-mode-syntax-table (setq csharp-mode-syntax-table (funcall (c-lang-const c-make-mode-syntax-table csharp)))) (defvar csharp-mode-abbrev-table nil "Abbreviation table used in csharp-mode buffers.") (c-define-abbrev-table 'csharp-mode-abbrev-table ;; Keywords that if they occur first on a line might alter the ;; syntactic context, and which therefore should trig reindentation ;; when they are completed. '(("else" "else" c-electric-continued-statement 0) ("while" "while" c-electric-continued-statement 0) ("catch" "catch" c-electric-continued-statement 0) ("finally" "finally" c-electric-continued-statement 0))) (defvar csharp-mode-map (let ((map (c-make-inherited-keymap))) ;; Add bindings which are only useful for C# map) "Keymap used in csharp-mode buffers.") ;; TODO ;; Defines our constant for finding attributes. ;;(defconst csharp-attribute-regex "\\[\\([XmlType]+\\)(") ;;(defconst csharp-attribute-regex "\\[\\(.\\)") ;; This doesn't work because the string regex happens before this point ;; and getting the font-locking to work before and after is fairly difficult ;;(defconst csharp-attribute-regex ;; (concat ;; "\\[[a-zA-Z][ \ta-zA-Z0-9.]+" ;; "\\((.*\\)?" ;;)) ;; ================================================================== ;; end of c# values for "language constants" defined in cc-langs.el ;; ================================================================== ;; ================================================================== ;; C# code-doc insertion magic ;; ================================================================== ;; ;; In Visual Studio, if you type three slashes, it immediately expands into ;; an inline code-documentation fragment. The following method does the ;; same thing. ;; ;; This is the kind of thing that could be handled by YASnippet or ;; another similarly flexible snippet framework. But I don't want to ;; introduce a dependency on yasnippet to csharp-mode. So the capability ;; must live within csharp-mode itself. (defun csharp-maybe-insert-codedoc (arg) "Insert an xml code documentation template as appropriate, when typing slashes. This fn gets bound to / (the slash key), in csharp-mode. If the slash being inserted is not the third consecutive slash, the slash is inserted as normal. If it is the third consecutive slash, then a xml code documentation template may be inserted in some cases. For example, a template is inserted if the prior line is empty, or contains only an open curly brace; a template is inserted if the prior word closes the element; a template is inserted if the prior word closes the element; an template is inserted if the prior word closes the element; a template is inserted if the prior word closes a element. In all other cases the slash is inserted as normal. If you want the default cc-mode behavior, which implies no automatic insertion of xml code documentation templates, then use this in your `csharp-mode-hook' function: (local-set-key (kbd \"/\") 'c-electric-slash) " (interactive "*p") ;;(message "csharp-maybe-insert-codedoc") (let ( (cur-point (point)) (char last-command-char) (cb0 (char-before (- (point) 0))) (cb1 (char-before (- (point) 1))) is-first-non-whitespace did-auto-insert ) ;; check if two prior chars were slash (if (and (= char ?/) cb0 (= ?/ cb0) cb1 (= ?/ cb1) ) (progn ;;(message "yes - this is the third consecutive slash") (setq is-first-non-whitespace (save-excursion (back-to-indentation) (= cur-point (+ (point) 2)))) (if is-first-non-whitespace ;; This is a 3-slash sequence. It is the first non-whitespace text ;; on the line. Now we need to examine the surrounding context ;; in order to determine which xml cod doc template to insert. (let (word-back char0 char1 word-fore char-0 char-1 text-to-insert ;; text to insert in lieu of slash fn-to-call ;; func to call after inserting text (preceding-line-is-empty (or (= (line-number-at-pos) 1) (save-excursion (previous-line) (beginning-of-line) (looking-at "[ \t]*$\\|[ \t]*{[ \t]*$")))) (flavor 0) ;; used only for diagnostic purposes ) ;;(message "starting a 3-slash comment") ;; get the prior word, and the 2 chars preceding it. (backward-word) (setq word-back (thing-at-point 'word) char0 (char-before (- (point) 0)) char1 (char-before (- (point) 1))) ;; restore prior position (goto-char cur-point) ;; get the following word, and the 2 chars preceding it. (forward-word) (backward-word) (setq word-fore (thing-at-point 'word) char-0 (char-before (- (point) 0)) char-1 (char-before (- (point) 1))) ;; restore prior position again (goto-char cur-point) (cond ;; The preceding line is empty, or all whitespace, or ;; contains only an open-curly. In this case, insert a ;; summary element pair. (preceding-line-is-empty (setq text-to-insert "/ \n/// \n/// " flavor 1) ) ;; The preceding word closed a summary element. In this case, ;; if the forward word does not open a remarks element, then ;; insert a remarks element. ((and (string-equal word-back "summary") (eq char0 ?/) (eq char1 ?<)) (if (not (and (string-equal word-fore "remarks") (eq char-0 ?<))) (setq text-to-insert "/ \n/// \n/// \n/// \n/// " flavor 2))) ;; The preceding word closed the remarks section. In this case, ;; insert an example element. ((and (string-equal word-back "remarks") (eq char0 ?/) (eq char1 ?<)) (setq text-to-insert "/ \n/// \n/// " flavor 3)) ;; The preceding word closed the example section. In this ;; case, insert an returns element. This isn't always ;; correct, because sometimes the xml code doc is attached to ;; a class or a property, neither of which has a return ;; value. A more intelligent implementation would inspect the ;; syntax state and only inject a returns element if ;; appropriate. ((and (string-equal word-back "example") (eq char0 ?/) (eq char1 ?<)) (setq text-to-insert "/ " fn-to-call (lambda () (backward-word) (backward-char) (backward-char) (c-indent-line-or-region) ) flavor 4)) ;; The preceding word opened the remarks section, or it ;; closed a para section. In this case, insert a para ;; element, using appropriate indentation with respect to the ;; prior tag. ((or (and (string-equal word-back "remarks") (eq char0 ?<) (or (eq char1 32) (eq char1 9))) (and (string-equal word-back "para") (eq char0 ?/) (eq char1 ?<))) (let (prior-point spacer) (save-excursion (backward-word) (backward-char) (backward-char) (setq prior-point (point)) (skip-chars-backward "\t ") (setq spacer (buffer-substring (point) prior-point)) ;;(message (format "pt(%d) prior(%d) spacer(%s)" (point) prior-point spacer)) ) (if (string-equal word-back "remarks") (setq spacer (concat spacer " "))) (setq text-to-insert (format "/%s\n///%s \n///%s" spacer spacer spacer) flavor 6))) ;; The preceding word opened a para element. In this case, if ;; the forward word does not close the para element, then ;; close the para element. ;; -- ;; This is a nice idea but flawed. Suppose I have a para element with some ;; text in it. If I position the cursor at the first line, then type 3 slashes, ;; I get a close-element, and that would be inappropriate. Not sure I can ;; easily solve that problem, so the best thing might be to simply punt, and ;; require people to close their own elements. ;; ;; ( (and (string-equal word-back "para") (eq char0 60) (or (eq char1 32) (eq char1 9))) ;; (if (not (and (string-equal word-fore "para") (eq char-0 47) (eq char-1 60) )) ;; (setq text-to-insert "/ \n/// \n///" ;; fn-to-call (lambda () ;; (previous-line) ;; (end-of-line) ;; ) ;; flavor 7) ) ;; ) ;; the default case - do nothing (t nil)) (if text-to-insert (progn ;;(message (format "inserting special text (f(%d))" flavor)) ;; set the flag, that we actually inserted text (setq did-auto-insert t) ;; save point of beginning of insertion (setq cur-point (point)) ;; actually insert the text (insert text-to-insert) ;; indent the inserted string, and re-position point, either through ;; the case-specific fn, or via the default progn. (if fn-to-call (funcall fn-to-call) (let ((newline-count 0) (pos 0) ix) ;; count the number of newlines in the inserted string (while (string-match "\n" text-to-insert pos) (setq pos (match-end 0) newline-count (+ newline-count 1) ) ) ;; indent what we just inserted (c-indent-region cur-point (point) t) ;; move up n/2 lines. This assumes that the ;; inserted text is ~symmetric about the halfway point. ;; The assumption holds if the xml code doc uses a ;; begin-elt and end-elt on a new line all by themselves, ;; and a blank line in between them where the point should be. ;; A more intelligent implementation would use a specific ;; marker string, like @@DOT, to note the desired point. (previous-line (/ newline-count 2)) (end-of-line))))))))) (if (not did-auto-insert) (self-insert-command (prefix-numeric-value arg))))) ;; ================================================================== ;; end of c# code-doc insertion magic ;; ================================================================== ;; ================================================================== ;; c# fontification extensions ;; ================================================================== ;; Commentary: ;; ;; The purpose of the following code is to fix font-lock for C#, ;; specifically for the verbatim-literal strings. C# is a cc-mode ;; language and strings are handled mostly like other c-based ;; languages. The one exception is the verbatim-literal string, which ;; uses the syntax @"...". ;; ;; `parse-partial-sexp' treats those strings as just regular strings, ;; with the @ a non-string character. This is fine, except when the ;; verblit string ends in a slash, in which case, font-lock breaks from ;; that point onward in the buffer. ;; ;; This is an attempt to fix that. ;; ;; The idea is to scan the buffer in full for verblit strings, and apply the ;; appropriate syntax-table text properties for verblit strings. Also setting ;; `parse-sexp-lookup-properties' to t tells `parse-partial-sexp' ;; to use the syntax-table text properties set up by the scan as it does ;; its parse. ;; ;; Also need to re-scan after any changes in the buffer, but on a more ;; limited region. ;; ;; ;; I don't remember what this is supposed to do, ;; ;; or how I figured out the value. ;; ;; ;; (defconst csharp-font-lock-syntactic-keywords ;; '(("\\(@\\)\\(\"\\)[^\"]*\\(\"\\)\\(\"\\)[^\"]*\\(\"\\)[^\"]" ;; (1 '(6)) (2 '(7)) (3 '(1)) (4 '(1)) (5 '(7)) ;; )) ;; "Highlighting of verbatim literal strings. See also the variable ;; `font-lock-keywords'.") ;; Allow this: ;; (csharp-log 3 "csharp: scan...'%s'" state) (defvar csharp-log-level 0 "The current log level for CSharp-specific operations. This is used in particular by the verbatim-literal string scanning. Most other csharp functions are not instrumented. 0 = NONE, 1 = Info, 2 = VERBOSE, 3 = DEBUG, 4 = SHUTUP ALREADY. ") (defun csharp-log (level text &rest args) "Log a message at level LEVEL. If LEVEL is higher than `csharp-log-level', the message is ignored. Otherwise, it is printed using `message'. TEXT is a format control string, and the remaining arguments ARGS are the string substitutions (see `format')." (if (<= level csharp-log-level) (let* ((msg (apply 'format text args))) (message "%s" msg) ))) (defun csharp-max-beginning-of-stmt () "Return the greater of `c-beginning-of-statement-1' and `c-beginning-of-statement' . I don't understand why both of these methods are necessary or why they differ. But they do." (let (dash nodash (curpos (point))) ;; I think this may need a save-excursion... ;; Calling c-beginning-of-statement-1 resets the point! (setq dash (progn (c-beginning-of-statement-1) (point))) (csharp-log 3 "C#: max-bostmt dash(%d)" dash) (goto-char curpos) (setq nodash (progn (c-beginning-of-statement 1) (point))) (csharp-log 3 "C#: max-bostmt nodash(%d)" nodash) (goto-char curpos) (max dash nodash))) (defun csharp-in-literal (&optional lim detect-cpp) "Return the type of literal point is in, if any. Basically this works like `c-in-literal' except it doesn't use or fill the cache (`c-in-literal-cache'). The return value is `c' if in a C-style comment, `c++' if in a C++ style comment, `string' if in a string literal, `pound' if DETECT-CPP is non-nil and in a preprocessor line, or nil if somewhere else. Optional LIM is used as the backward limit of the search. If omitted, or nil, `c-beginning-of-syntax' is used. Note that this function might do hidden buffer changes. See the comment at the start of cc-engine.el for more info." (let ((rtn (save-excursion (let* ((pos (point)) (lim (or lim (progn (c-beginning-of-syntax) (point)))) (state (parse-partial-sexp lim pos))) (csharp-log 4 "C#: parse lim(%d) state: %s" lim (prin1-to-string state)) (cond ((elt state 3) (csharp-log 4 "C#: in literal string (%d)" pos) 'string) ((elt state 4) (csharp-log 4 "C#: in literal comment (%d)" pos) (if (elt state 7) 'c++ 'c)) ((and detect-cpp (c-beginning-of-macro lim)) 'pound) (t nil)))))) rtn)) (defun csharp-set-vliteral-syntax-table-properties (beg end) "Scan the buffer text between BEG and END, a verbatim literal string, setting and clearing syntax-table text properties where necessary. We need to modify the default syntax-table text property in these cases: (backslash) - is not an escape inside a verbatim literal string. (double-quote) - can be a literal quote, when doubled. BEG is the @ delimiter. END is the 'old' position of the ending quote. see http://www.sunsite.ualberta.ca/Documentation/Gnu/emacs-lisp-ref-21-2.7/html_node/elisp_592.html for the list of syntax table numeric codes. " (csharp-log 3 "C#: set-vlit-syntax-table: beg(%d) end(%d)" beg end) (if (and (> beg 0) (> end 0)) (let ((curpos beg) (state 0)) (c-clear-char-properties beg end 'syntax-table) (while (<= curpos end) (cond ((= state 0) (if (= (char-after curpos) ?@) (progn (c-put-char-property curpos 'syntax-table '(3)) ; (6) = expression prefix, (3) = symbol ;;(message (format "C#: set-s-t: prefix pos(%d) chr(%c)" beg (char-after beg))) ) ) (setq state (+ 1 state))) ((= state 1) (if (= (char-after curpos) ?\") (progn (c-put-char-property curpos 'syntax-table '(7)) ; (7) = string quote ;;(message (format "C#: set-s-t: open quote pos(%d) chr(%c)" ;; curpos (char-after curpos))) )) (setq state (+ 1 state))) ((= state 2) (cond ;; handle backslash ((= (char-after curpos) ?\\) (c-put-char-property curpos 'syntax-table '(2)) ; (1) = punctuation, (2) = word ;;(message (format "C#: set-s-t: backslash word pos(%d) chr(%c)" curpos (char-after curpos))) ) ;; doubled double-quote ((and (= (char-after curpos) ?\") (= (char-after (+ 1 curpos)) ?\")) (c-put-char-property curpos 'syntax-table '(2)) ; (1) = punctuation, (2) = word (c-put-char-property (+ 1 curpos) 'syntax-table '(2)) ; (1) = punctuation ;;(message (format "C#: set-s-t: double doublequote pos(%d) chr(%c)" curpos (char-after curpos))) (setq curpos (+ curpos 1)) ) ;; a single double-quote, which should be a string terminator ((= (char-after curpos) ?\") (c-put-char-property curpos 'syntax-table '(7)) ; (7) = string quote ;;(message (format "C#: set-s-t: close quote pos(%d) chr(%c)" curpos (char-after curpos))) ;;go no further (setq state (+ 1 state))) ;; everything else (t ;;(message (format "C#: set-s-t: none pos(%d) chr(%c)" curpos (char-after curpos))) nil)))) ;; next char (setq curpos (+ curpos 1)))))) (defun csharp-end-of-verbatim-literal-string (&optional lim) "Moves to and returns the position of the end quote of the verbatim literal string. When calling, point should be on the @ of the verblit string. If it is not, then no movement is performed and `point' is returned. This function ignores text properties. In fact it is the underlying scanner used to set the text properties in a C# buffer. " (csharp-log 3 "C#: end-of-vlit-string: point(%d) c(%c)" (point) (char-after)) (let (curpos (max (or lim (point-max)))) (if (not (looking-at "@\"")) (point) (forward-char 2) ;; pass up the @ sign and first quote (setq curpos (point)) ;; Within a verbatim literal string, a doubled double-quote ;; escapes the double-quote." (while (and ;; process characters... (or ;; while... (not (eq (char-after curpos) ?\")) ;; it's not a quote (eq (char-after (+ curpos 1)) ?\")) ;; or, its a double (double) quote (< curpos max)) ;; and we're not done yet (cond ((and (eq (char-after curpos) ?\") ;; it's a double-quote. (eq (char-after (+ curpos 1)) ?\")) (setq curpos (+ 2 curpos))) ;; Skip 2 (t ;; anything else (setq curpos (+ 1 curpos))))) ;; skip fwd 1 curpos))) (defun csharp-scan-for-verbatim-literals-and-set-props (&optional beg end) "Scans the buffer, between BEG and END, for verbatim literal strings, and sets override text properties on each string to allow proper syntax highlighting, indenting, and cursor movement. BEG and END define the limits of the scan. When nil, they default to `point-min' and `point-max' respectively. Setting text properties generally causes the buffer to be marked as modified, but this fn suppresses that via the `c-buffer-save-state' macro, for any changes in text properties that it makes. This fn also ignores the read-only setting on a buffer, using the same macro. This fn is called when a csharp-mode buffer is loaded, with BEG and END set to nil, to do a full scan. It is also called on every buffer change, with the BEG and END set to the values for the change. The return value is nil if the buffer was not a csharp-mode buffer. Otherwise it is the last cursor position examined by the scan. " (if (not (c-major-mode-is 'csharp-mode)) ;; don't scan if not csharp mode nil (save-excursion (c-save-buffer-state ((curpos (or beg (point-min))) (lastpos (or end (point-max))) (state 0) (start 0) (cycle 0) literal eos limits) (csharp-log 3 "C#: scan") (goto-char curpos) (while (and (< curpos lastpos) (< cycle 10000)) (cond ;; Case 1: current char is a @ sign ;; -------------------------------------------- ;; Check to see if it demarks the beginning of a verblit ;; string. ((= ?@ (char-after curpos)) ;; are we in a comment? a string? Maybe the @ is a prefix ;; to allow the use of a reserved word as a symbol. Let's find out. ;; not sure why I need both of the following. (syntax-ppss-flush-cache 1) (parse-partial-sexp 1 curpos) (goto-char curpos) (setq literal (csharp-in-literal)) (cond ;; Case 1.A: it's a @ within a string. ;; -------------------------------------------- ;; This should never happen, because this scanner hops over strings. ;; But it might happen if the scan starts at an odd place. ((eq literal 'string) nil) ;; Case 1.B: The @ is within a comment. Hop over it. ((and (memq literal '(c c++)) ;; This is a kludge for XEmacs where we use ;; `buffer-syntactic-context', which doesn't correctly ;; recognize "\*/" to end a block comment. ;; `parse-partial-sexp' which is used by ;; `c-literal-limits' will however do that in most ;; versions, which results in that we get nil from ;; `c-literal-limits' even when `c-in-literal' claims ;; we're inside a comment. ;;(setq limits (c-literal-limits start))) (setq limits (c-literal-limits))) ;; advance to the end of the comment (if limits (progn (csharp-log 4 "C#: scan: jump end comment A (%d)" (cdr limits)) (setq curpos (cdr limits))))) ;; Case 1.B: curpos is at least 2 chars before the last ;; position to examine, and, the following char is a ;; double-quote (ASCII 34). ;; -------------------------------------------- ;; This looks like the beginning of a verbatim string ;; literal. ((and (< (+ 2 curpos) lastpos) (= ?\" (char-after (+ 1 curpos)))) (setq eos (csharp-end-of-verbatim-literal-string)) ;; set override syntax properties on the verblit string (csharp-set-vliteral-syntax-table-properties curpos eos) (csharp-log 4 "C#: scan: jump end verblit string (%d)" eos) (setq curpos eos)))) ;; Case 2: current char is a double-quote. ;; -------------------------------------------- ;; If this is a string, we hop over it, on the assumption that ;; this scanner need not bother with regular literal strings, which ;; get the proper syntax with the generic approach. ;; If in a comment, hop over the comment. ((= ?\" (char-after curpos)) (goto-char curpos) (setq literal (c-in-literal)) (cond ;; Case 2.A: a quote within a string ;; -------------------------------------------- ;; This shouldn't happen, because we hop over strings. ;; But it might. ((eq literal 'string) nil) ;; Case 2.B: a quote within a comment ;; -------------------------------------------- ((and (memq literal '(c c++)) ;; This is a kludge for XEmacs where we use ;; `buffer-syntactic-context', which doesn't correctly ;; recognize "\*/" to end a block comment. ;; `parse-partial-sexp' which is used by ;; `c-literal-limits' will however do that in most ;; versions, which results in that we get nil from ;; `c-literal-limits' even when `c-in-literal' claims ;; we're inside a comment. ;;(setq limits (c-literal-limits start))) (setq limits (c-literal-limits))) ;; advance to the end of the comment (if limits (progn (setq curpos (cdr limits)) (csharp-log 3 "C#: scan: jump end comment B (%s)" curpos)))) ;; Case 2.C: Not in a comment, and not in a string. ;; -------------------------------------------- ;; This is the beginning of a literal (but not verbatim) string. (t (forward-char 1) ;; pass up the quote (if (consp (setq limits (c-literal-limits))) (progn (csharp-log 4 "C#: scan: jump end literal (%d)" (cdr limits)) (setq curpos (cdr limits)))))))) (setq cycle (+ 1 cycle)) (setq curpos (+ 1 curpos)) (c-safe (goto-char curpos))))))) (defun csharp-before-font-lock (beg end old-len) "Adjust`syntax-table' properties on the region affected by the change in a csharp-mode buffer. This function is the C# value for `c-before-font-lock-function'. It intended to be called only by the cc-mode runtime. It prepares the buffer for font locking, hence must get called before `font-lock-after-change-function'. It does hidden buffer changes. BEG, END and OLD-LEN have the same meaning here as for any after-change function. Point is undefined both before and after this function call. The return value is meaningless, and is ignored by cc-mode. " (let ((start-scan (progn (c-beginning-of-statement 1) (point)))) (csharp-scan-for-verbatim-literals-and-set-props start-scan end))) (c-lang-defconst c-before-font-lock-function csharp 'csharp-before-font-lock) ;; ================================================================== ;; end of c# fontification extensions ;; ================================================================== ;; ================================================================== ;; C#-specific optimizations of cc-mode funcs ;; ================================================================== ;; There's never a need to check for C-style macro definitions in ;; a C# buffer. (defadvice c-beginning-of-macro (around csharp-mode-advice-1 compile activate) (if (c-major-mode-is 'csharp-mode) nil ad-do-it) ) ;; There's never a need to move over an Obj-C directive in csharp mode (defadvice c-forward-objc-directive (around csharp-mode-advice-2 compile activate) (if (c-major-mode-is 'csharp-mode) nil ad-do-it) ) ;; ================================================================== ;; end of C#-specific optimizations of cc-mode funcs ;; ================================================================== ;; ================================================================== ;; c# - monkey-patching of basic parsing logic ;; ================================================================== ;; ;; Here, the model redefines two defuns to add special cases for csharp ;; mode. These primarily deal with indentation of instance ;; initializers, which are somewhat unique to C#. I couldn't figure out ;; how to get cc-mode to do what C# needs, without modifying these ;; defuns. ;; (defun c-looking-at-inexpr-block (lim containing-sexp &optional check-at-end) ;; Return non-nil if we're looking at the beginning of a block ;; inside an expression. The value returned is actually a cons of ;; either 'inlambda, 'inexpr-statement or 'inexpr-class and the ;; position of the beginning of the construct. ;; ;; LIM limits the backward search. CONTAINING-SEXP is the start ;; position of the closest containing list. If it's nil, the ;; containing paren isn't used to decide whether we're inside an ;; expression or not. If both LIM and CONTAINING-SEXP are used, LIM ;; needs to be farther back. ;; ;; If CHECK-AT-END is non-nil then extra checks at the end of the ;; brace block might be done. It should only be used when the ;; construct can be assumed to be complete, i.e. when the original ;; starting position was further down than that. ;; ;; This function might do hidden buffer changes. (save-excursion (let ((res 'maybe) passed-paren (closest-lim (or containing-sexp lim (point-min))) ;; Look at the character after point only as a last resort ;; when we can't disambiguate. (block-follows (and (eq (char-after) ?{) (point)))) (while (and (eq res 'maybe) (progn (c-backward-syntactic-ws) (> (point) closest-lim)) (not (bobp)) (progn (backward-char) (looking-at "[\]\).]\\|\\w\\|\\s_")) (c-safe (forward-char) (goto-char (scan-sexps (point) -1)))) (setq res (if (looking-at c-keywords-regexp) (let ((kw-sym (c-keyword-sym (match-string 1)))) (cond ((and block-follows (c-keyword-member kw-sym 'c-inexpr-class-kwds)) (and (not (eq passed-paren ?\[)) ;; dinoch Thu, 22 Apr 2010 18:20 ;; ============================================ ;; looking at new MyType() { ... } ;; means this is a brace list, so, return nil, ;; implying NOT looking-at-inexpr-block (not (and (c-major-mode-is 'csharp-mode) (looking-at "new\s+\\([[:alnum:]_]+\\)\\b"))) (or (not (looking-at c-class-key)) ;; If the class instantiation is at the start of ;; a statement, we don't consider it an ;; in-expression class. (let ((prev (point))) (while (and (= (c-backward-token-2 1 nil closest-lim) 0) (eq (char-syntax (char-after)) ?w)) (setq prev (point))) (goto-char prev) (not (c-at-statement-start-p))) ;; Also, in Pike we treat it as an ;; in-expression class if it's used in an ;; object clone expression. (save-excursion (and check-at-end (c-major-mode-is 'pike-mode) (progn (goto-char block-follows) (zerop (c-forward-token-2 1 t))) (eq (char-after) ?\()))) (cons 'inexpr-class (point)))) ((c-keyword-member kw-sym 'c-inexpr-block-kwds) (when (not passed-paren) (cons 'inexpr-statement (point)))) ((c-keyword-member kw-sym 'c-lambda-kwds) (when (or (not passed-paren) (eq passed-paren ?\()) (cons 'inlambda (point)))) ((c-keyword-member kw-sym 'c-block-stmt-kwds) nil) (t 'maybe))) (if (looking-at "\\s(") (if passed-paren (if (and (eq passed-paren ?\[) (eq (char-after) ?\[)) ;; Accept several square bracket sexps for ;; Java array initializations. 'maybe) (setq passed-paren (char-after)) 'maybe) 'maybe)))) (if (eq res 'maybe) (when (and c-recognize-paren-inexpr-blocks block-follows containing-sexp (eq (char-after containing-sexp) ?\()) (goto-char containing-sexp) (if (or (save-excursion (c-backward-syntactic-ws lim) (and (> (point) (or lim (point-min))) (c-on-identifier))) (and c-special-brace-lists (c-looking-at-special-brace-list))) nil (cons 'inexpr-statement (point)))) res)))) (defconst csharp-enum-decl-re (concat "\\\s+\\([[:alnum:]_]+\\)\s*:\s*" "\\(" (c-make-keywords-re nil (list "sbyte" "byte" "short" "ushort" "int" "uint" "long" "ulong")) "\\)") "Regex that captures an enum declaration in C#" ) (defun c-inside-bracelist-p (containing-sexp paren-state) ;; return the buffer position of the beginning of the brace list ;; statement if we're inside a brace list, otherwise return nil. ;; CONTAINING-SEXP is the buffer pos of the innermost containing ;; paren. PAREN-STATE is the remainder of the state of enclosing ;; braces ;; ;; N.B.: This algorithm can potentially get confused by cpp macros ;; placed in inconvenient locations. It's a trade-off we make for ;; speed. ;; ;; This function might do hidden buffer changes. (or ;; This will pick up brace list declarations. (c-safe (save-excursion (goto-char containing-sexp) (c-forward-sexp -1) (let (bracepos) (if (and (or (looking-at c-brace-list-key) (progn (c-forward-sexp -1) (looking-at c-brace-list-key)) ;; dinoch Thu, 22 Apr 2010 18:20 ;; ============================================ ;; looking enum Foo : int ;; means this is a brace list, so, return nil, ;; implying NOT looking-at-inexpr-block (and (c-major-mode-is 'csharp-mode) (progn (c-forward-sexp -1) (looking-at csharp-enum-decl-re)))) (setq bracepos (c-down-list-forward (point))) (not (c-crosses-statement-barrier-p (point) (- bracepos 2)))) (point))))) ;; this will pick up array/aggregate init lists, even if they are nested. (save-excursion (let ((class-key ;; Pike can have class definitions anywhere, so we must ;; check for the class key here. (and (c-major-mode-is 'pike-mode) c-decl-block-key)) bufpos braceassignp lim next-containing) (while (and (not bufpos) containing-sexp) (when paren-state (if (consp (car paren-state)) (setq lim (cdr (car paren-state)) paren-state (cdr paren-state)) (setq lim (car paren-state))) (when paren-state (setq next-containing (car paren-state) paren-state (cdr paren-state)))) (goto-char containing-sexp) (if (c-looking-at-inexpr-block next-containing next-containing) ;; We're in an in-expression block of some kind. Do not ;; check nesting. We deliberately set the limit to the ;; containing sexp, so that c-looking-at-inexpr-block ;; doesn't check for an identifier before it. (setq containing-sexp nil) ;; see if the open brace is preceded by = or [...] in ;; this statement, but watch out for operator= (setq braceassignp 'dontknow) (c-backward-token-2 1 t lim) ;; Checks to do only on the first sexp before the brace. (when (and c-opt-inexpr-brace-list-key (eq (char-after) ?\[)) ;; In Java, an initialization brace list may follow ;; directly after "new Foo[]", so check for a "new" ;; earlier. (while (eq braceassignp 'dontknow) (setq braceassignp (cond ((/= (c-backward-token-2 1 t lim) 0) nil) ((looking-at c-opt-inexpr-brace-list-key) t) ((looking-at "\\sw\\|\\s_\\|[.[]") ;; Carry on looking if this is an ;; identifier (may contain "." in Java) ;; or another "[]" sexp. 'dontknow) (t nil))))) ;; Checks to do on all sexps before the brace, up to the ;; beginning of the statement. (while (eq braceassignp 'dontknow) (cond ((eq (char-after) ?\;) (setq braceassignp nil)) ((and class-key (looking-at class-key)) (setq braceassignp nil)) ((eq (char-after) ?=) ;; We've seen a =, but must check earlier tokens so ;; that it isn't something that should be ignored. (setq braceassignp 'maybe) (while (and (eq braceassignp 'maybe) (zerop (c-backward-token-2 1 t lim))) (setq braceassignp (cond ;; Check for operator = ((and c-opt-op-identifier-prefix (looking-at c-opt-op-identifier-prefix)) nil) ;; Check for `= in Pike. ((and (c-major-mode-is 'pike-mode) (or (eq (char-after) ?`) ;; Special case for Pikes ;; `[]=, since '[' is not in ;; the punctuation class. (and (eq (char-after) ?\[) (eq (char-before) ?`)))) nil) ((looking-at "\\s.") 'maybe) ;; make sure we're not in a C++ template ;; argument assignment ((and (c-major-mode-is 'c++-mode) (save-excursion (let ((here (point)) (pos< (progn (skip-chars-backward "^<>") (point)))) (and (eq (char-before) ?<) (not (c-crosses-statement-barrier-p pos< here)) (not (c-in-literal)) )))) nil) (t t)))))) (if (and (eq braceassignp 'dontknow) (/= (c-backward-token-2 1 t lim) 0)) (setq braceassignp nil))) (if (not braceassignp) (if (eq (char-after) ?\;) ;; Brace lists can't contain a semicolon, so we're done. (setq containing-sexp nil) ;; Go up one level. (setq containing-sexp next-containing lim nil next-containing nil)) ;; we've hit the beginning of the aggregate list (c-beginning-of-statement-1 (c-most-enclosing-brace paren-state)) (setq bufpos (point)))) ) bufpos)) )) ;; ================================================================== ;; end of monkey-patching of basic parsing logic ;; ================================================================== ;;(easy-menu-define csharp-menu csharp-mode-map "C# Mode Commands" ;; ;; Can use `csharp' as the language for `c-mode-menu' ;; ;; since its definition covers any language. In ;; ;; this case the language is used to adapt to the ;; ;; nonexistence of a cpp pass and thus removing some ;; ;; irrelevant menu alternatives. ;; (cons "C#" (c-lang-const c-mode-menu csharp))) ;;; Autoload mode trigger ;;;###autoload (add-to-list 'auto-mode-alist '("\\.cs$" . csharp-mode)) (c-add-style "C#" '("Java" (c-basic-offset . 4) (c-comment-only-line-offset . (0 . 0)) (c-offsets-alist . ( (access-label . -) (arglist-close . c-lineup-arglist) (arglist-cont . 0) (arglist-cont-nonempty . c-lineup-arglist) (arglist-intro . c-lineup-arglist-intro-after-paren) (block-close . 0) (block-open . 0) (brace-entry-open . 0) (brace-list-close . 0) (brace-list-entry . 0) (brace-list-intro . +) (brace-list-open . +) (c . c-lineup-C-comments) (case-label . +) (catch-clause . 0) (class-close . 0) (class-open . 0) (comment-intro . c-lineup-comment) (cpp-macro . 0) (cpp-macro-cont . c-lineup-dont-change) (defun-block-intro . +) (defun-close . 0) (defun-open . 0) (do-while-closure . 0) (else-clause . 0) (extern-lang-close . 0) (extern-lang-open . 0) (friend . 0) (func-decl-cont . +) (inclass . +) (inexpr-class . +) (inexpr-statement . 0) (inextern-lang . +) (inher-cont . c-lineup-multi-inher) (inher-intro . +) (inlambda . c-lineup-inexpr-block) (inline-close . 0) (inline-open . 0) (innamespace . +) (knr-argdecl . 0) (knr-argdecl-intro . 5) (label . 0) (lambda-intro-cont . +) (member-init-cont . c-lineup-multi-inher) (member-init-intro . +) (namespace-close . 0) (namespace-open . 0) (statement . 0) (statement-block-intro . +) (statement-case-intro . +) (statement-case-open . +) (statement-cont . +) (stream-op . c-lineup-streamop) (string . c-lineup-dont-change) (substatement . +) (substatement-open . 0) (template-args-cont c-lineup-template-args +) (topmost-intro . 0) (topmost-intro-cont . 0) )) )) ;; Custom variables ;;;###autoload (defcustom csharp-mode-hook nil "*Hook called by `csharp-mode'." :type 'hook :group 'c) ;;; The entry point into the mode ;;;###autoload (defun csharp-mode () "Major mode for editing C# code. This mode is derived from CC Mode to support C#. The hook `c-mode-common-hook' is run with no args at mode initialization, then `csharp-mode-hook'. This mode will automatically add a regexp for Csc.exe error and warning messages to the `compilation-error-regexp-alist'. Key bindings: \\{csharp-mode-map}" (interactive) (kill-all-local-variables) (make-local-variable 'beginning-of-defun-function) (make-local-variable 'end-of-defun-function) (c-initialize-cc-mode t) (set-syntax-table csharp-mode-syntax-table) ;; define underscore as part of a word in the Csharp syntax table (modify-syntax-entry ?_ "w" csharp-mode-syntax-table) ;; define @ as an expression prefix in Csharp syntax table (modify-syntax-entry ?@ "'" csharp-mode-syntax-table) (setq major-mode 'csharp-mode mode-name "C#" local-abbrev-table csharp-mode-abbrev-table abbrev-mode t) (use-local-map csharp-mode-map) ;; `c-init-language-vars' is a macro that is expanded at compile ;; time to a large `setq' with all the language variables and their ;; customized values for our language. (c-init-language-vars csharp-mode) ;; `c-common-init' initializes most of the components of a CC Mode ;; buffer, including setup of the mode menu, font-lock, etc. ;; There's also a lower level routine `c-basic-common-init' that ;; only makes the necessary initialization to get the syntactic ;; analysis and similar things working. (c-common-init 'csharp-mode) ;; csc.exe, the C# Compiler, produces errors like this: ;; file.cs(6,18): error SC1006: Name of constructor must match name of class (add-hook 'compilation-mode-hook (lambda () (setq compilation-error-regexp-alist (cons ' ("^[ \t]*\\([A-Za-z0-9][^(]+\\.cs\\)(\\([0-9]+\\)[,]\\([0-9]+\\)) ?: \\(error\\|warning\\) CS[0-9]+:" 1 2 3) compilation-error-regexp-alist)))) ;; to allow next-error to work with csc.exe: (setq compilation-scroll-output t) ;; allow fill-paragraph to work on xml code doc (set (make-local-variable 'paragraph-separate) "[ \t]*\\(//+\\|\\**\\)\\([ \t]+\\|[ \t]+<.+?>\\)$\\|^\f") (c-run-mode-hooks 'c-mode-common-hook 'csharp-mode-hook) ;; Need the following for parse-partial-sexp to work properly with ;; verbatim literal strings Setting this var to non-nil tells ;; `parse-partial-sexp' to pay attention to the syntax text ;; properties on the text in the buffer. If csharp-mode attaches ;; text syntax to @"..." then, `parse-partial-sexp' will treat those ;; strings accordingly. (set (make-local-variable 'parse-sexp-lookup-properties) t) ;; scan the entire buffer for verblit strings (csharp-scan-for-verbatim-literals-and-set-props nil nil) (local-set-key (kbd "/") 'csharp-maybe-insert-codedoc) (local-set-key (kbd "{") 'csharp-insert-open-brace) (c-update-modeline)) (message (concat "Done loading " load-file-name)) (provide 'csharp-mode) ;;; csharp-mode.el ends here ;;MD5: 4EDCB2ECE38841F407C7ED3DA8354E15