diff options
author | 2010-12-05 12:41:38 +0100 | |
---|---|---|
committer | 2010-12-05 12:41:38 +0100 | |
commit | 15d8264356d0e02a98f388b66020fd1fdae93a16 (patch) | |
tree | f339e2e8dab3bd5ddff665210c86722d74379927 /emacs.d/csharp-mode.el | |
parent | 608117225178a49638e3ec663b1940b0dc2d564e (diff) | |
download | dotfiles-15d8264356d0e02a98f388b66020fd1fdae93a16.tar.gz dotfiles-15d8264356d0e02a98f388b66020fd1fdae93a16.zip |
Added emacs config
Diffstat (limited to 'emacs.d/csharp-mode.el')
-rw-r--r-- | emacs.d/csharp-mode.el | 1977 |
1 files changed, 1977 insertions, 0 deletions
diff --git a/emacs.d/csharp-mode.el b/emacs.d/csharp-mode.el new file mode 100644 index 0000000..9cd7914 --- /dev/null +++ b/emacs.d/csharp-mode.el @@ -0,0 +1,1977 @@ +;;; csharp-mode.el --- C# mode derived mode + +;; Author: Dylan R. E. Moonfire +;; Maintainer: Dylan R. E. Moonfire <contact@mfgames.com> +;; Created: Feburary 2005 +;; Modified: February 2010 +;; Version: 0.7.4 - Dino Chiesa <dpchiesa@hotmail.com> +;; 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 <summary> template is inserted if the prior line is empty, + or contains only an open curly brace; + a <remarks> template is inserted if the prior word + closes the <summary> element; + a <returns> template is inserted if the prior word + closes the <remarks> element; + an <example> template is inserted if the prior word closes + the <returns> element; + a <para> template is inserted if the prior word closes + a <para> 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 "/ <summary>\n/// \n/// </summary>" + 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 "/ <remarks>\n/// <para>\n/// \n/// </para>\n/// </remarks>" + 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 "/ <example>\n/// \n/// </example>" + 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 "/ <returns></returns>" + 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<para>\n///%s \n///%s</para>" + 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/// </para>\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 + "\\<enum\\>\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 `<opchar>= 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 |