Init-Files/site-lisp/cc-mode/csharp-only/csharp-mode-0.8.6.el

5911 lines
220 KiB
EmacsLisp
Raw Blame History

;;; csharp-mode.el --- C# mode derived mode
;; Author : Dylan R. E. Moonfire (original)
;; Maintainer : Dino Chiesa <dpchiesa@hotmail.com>
;; Created : Feburary 2005
;; Modified : May 2011
;; Version : 0.8.6
;; Keywords : c# languages oop mode
;; X-URL : http://code.google.com/p/csharpmode/
;; Last-saved : <2011-May-21 20:28:30>
;;
;; 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 major mode for editing C# code. It performs automatic
;; indentation of C# syntax; font locking; and integration with compile.el;
;; flymake.el; yasnippet.el; and imenu.el.
;;
;; 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 insertion of matched pairs of curly braces.
;;
;; - compile tweaks. Infers the compile command from special comments
;; in the file header. Also, sets the regex for next-error, so that
;; compile.el can handle csc.exe output.
;;
;; - flymake integration
;; - select flymake command from code comments
;; - infer flymake command otherwise (presence of makefile, etc)
;; - Turn off query-on-exit-flag for the flymake process.
;; - define advice to flymake-goto-line , to allow it to goto the
;; appropriate column for the error on a given line. This works
;; with `flymake-goto-next-error' etc.
;;
;; - yasnippet integration
;; - preloaded snippets
;;
;; - imenu integration - generates an index of namespaces, classes,
;; interfaces, methods, and properties for easy navigation within
;; the buffer.
;;
;; Installation instructions
;; --------------------------------
;;
;; Put csharp-mode.el somewhere in your load path, optionally byte-compile
;; it, and add the following to your .emacs file:
;;
;; (autoload 'csharp-mode "csharp-mode" "Major mode for editing C# code." t)
;; (setq auto-mode-alist
;; (append '(("\\.cs$" . csharp-mode)) auto-mode-alist))
;;
;;
;; Optionally, define and register a mode-hook function. To do so, use
;; something like this in your .emacs file:
;;
;; (defun my-csharp-mode-fn ()
;; "function that runs when csharp-mode is initialized for a buffer."
;; (turn-on-auto-revert-mode)
;; (setq indent-tabs-mode nil)
;; (require 'flymake)
;; (flymake-mode 1)
;; (require 'yasnippet)
;; (yas/minor-mode-on)
;; (require 'rfringe)
;; ...insert more code here...
;; ...including any custom key bindings you might want ...
;; )
;; (add-hook 'csharp-mode-hook 'my-csharp-mode-fn t)
;;
;;
;; General
;; ----------------------------
;;
;; Mostly C# mode will "just work." Use `describe-mode' to see the
;; default keybindings and the highlights of the mode.
;;
;;
;; Flymake Integration
;; ----------------------------
;;
;; You can use flymake with csharp mode to automatically check the
;; syntax of your csharp code, and highlight errors. To do so, add a
;; comment line like this to each .cs file that you use flymake with:
;;
;; // flymake: c:\.net3.5\csc.exe /t:module /nologo /R:Foo.dll @@FILE@@
;;
;; That lines specifies a command "stub". Flymake appends the name of
;; the file to compile, and then runs the command to check
;; syntax. Flymake assumes that syntax errors will be noted in the
;; output of the command in a form that fits one of the regexs in the
;; `compilation-error-regexp-alist-alist'. Check the flymake module for
;; more information on that.
;;
;; Some rules for the command:
;;
;; 1. it must appear all on a single line.
;;
;; 2. csharp-mode generally looks for the marker line in the first N
;; lines of the file, where N is set in
;; `csharp-cmd-line-limit'. See the documentation on that
;; variable for more information.
;;
;; 3. the command SHOULD use @@FILE@@ in place of the name of the
;; source file to be compiled, normally the file being edited.
;; This is because normally flymake saves a copy of the buffer
;; into a temporary file with a unique name, and then compiles
;; that temporary file. The token @@FILE@@ is replaced by
;; csharp-mode with the name of the temporary file created by
;; flymake, before invoking the command.
;;
;; 4. The command should include /R options specifying external
;; libraries that the code depends on.
;;
;; If you have no external dependencies, then you need not specify any
;; flymake command at all. csharp-mode will implicitly act as if you had
;; specified the command:
;;
;; // flymake: c:\.net3.5\csc.exe /t:module /nologo @@FILE@@
;;
;;
;; If you use csc.exe as the syntax check tool (as almost everyone
;; will), the /t:module is important. csharp-mode assumes that the
;; syntax-check compile command will produce a file named
;; NAME.netmodule, which is the default when using /t:module. (Remember
;; than NAME is dynamically generated). csharp-mode will remove the
;; generated netmodule file after the syntax check is complete. If you
;; don't specify /t:module, then csharp-mode won't know what file to
;; delete.
;;
;; csharp-mode also fiddles with some other flymake things. In
;; particular it: adds .cs to the flymake "allowed filename masks";
;; adds parsing for csc error messages; and adds advice to the error
;; parsing logic. This all should be pretty benign for all other
;; flymake buffers. But it might not be.
;;
;; You can explicitly turn the flymake integration for C# off by
;; setting `csharp-want-flymake-fixup' to nil.
;;
;;
;; Compile Integration
;; ----------------------------
;;
;; csharp-mode binds the function `csharp-invoke-compile-interactively'
;; to "\C-x\C-e" . This function attempts to intellgently guess the
;; format of the compile command to use for a buffer. It looks in the
;; comments at the head of the buffer for a line that begins with
;; compile: . If found, csharp-mode suggests the text that follows as
;; the compilation command when running `compile' . If such a line is
;; not found, csharp-mode falls back to a msbuild or nmake command.
;; See the documentation on `csharp-cmd-line-limit' for further
;; information.
;;
;; Also, csharp-mode installs an error regexp for csc.exe into
;; `compilation-error-regexp-alist-alist', which allows `next-error'
;; and `previous-error' (defined in compile.el) to navigate to the next
;; and previous compile errors in the cs buffer, after you've run `compile'.
;;
;;
;; YASnippet integration
;; -----------------------------
;;
;; csharp-mode defines some built-in snippets for
;; convenience. For example, if statements, for, foreach, and
;; so on. You can see them on the YASnippet menu that is displayed
;; when a csharp-mode buffer is opened. csharp-mode defines this
;; snippets happens only if ya-snippet is available. (It is done in an
;; `eval-after-load' clause.) The builtin snippets will not overwrite
;; snippets that use the same name, if they are defined in the normal
;; way (in a compiled bundle) with ya-snippet.
;;
;; You can explicitly turn off ya-snippet integration. See the var,
;; `csharp-want-yasnippet-fixup'.
;;
;;
;; imenu integration
;; -----------------------------
;;
;; This should just work. For those who don't know what imenu is, it
;; allows navigation to different points within the file from an
;; "Index" menu, in the window's menubar. csharp-mode computes the
;; menu containing the namespaces, classes, methods, and so on, in the
;; buffer. This happens at the time the file is loaded; for large
;; files it takes a bit of time to complete the scan. If you don't
;; want this capability, set `csharp-want-imenu' to nil.
;;
;;
;;; Known Bugs:
;;
;; The imenu scan is text-based and naive. For example, if you
;; intersperse comments between the name of a class/method/namespace,
;; and the curly brace, the scan will not recognize the thing being
;; declared. This is fixable - would need to extract the buffer
;; substring then remove comments before doing the regexp checks - but
;; it would make the scan much slower. Also, the scan doesn't deal
;; with preproc symbol definitions and #if/#else. Those things are
;; invisible to the scanner csharp-mode uses to build the imenu menu.
;;
;; Leading identifiers are no longer being fontified, for some reason.
;; See matchers-before. (Not sure this is still a problem - 19 may
;; 2011 DPC)
;;
;; Method names with a preceding attribute are not fontified.
;;
;; The symbol followng #if is not fontified. It should be treated like
;; define and get font-lock-variable-name-face .
;;
;; 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:
;;
;; imenu should scan for and find delegates and events, in addition
;; to the classes, structs, properties and methods it does currently.
;;
;; Get csharp-mode.el accepted as part of the emacs standard distribution.
;; Must contact monnier at iro.umontreal.ca to make this happen.
;;
;; Add refactoring capabilities?
;; - extract as method - extract a block of code into a method
;; - extract as Func<> - extract a block of code into an Action<T>
;;
;; More code-gen power:
;; - interface implementation - I think would require csharp-shell
;;
;;
;; Acknowledgements:
;;
;; Thanks to Alan Mackenzie and Stefan Monnier for answering questions
;; and making suggestions. And to Trey Jackson for sharing his
;; knowledge of emacs lisp.
;;
;;
;;; 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 formfeed.
;; - intelligent curly-brace insertion with
;; `csharp-insert-open-brace'
;; 0.7.4 - added a C# style
;; - using is now a keyword and gets fontified correctly
;; - fixed a bug that had crept into the codedoc insertion.
;; 0.7.5 - now fontify namespaces in the using statements. This is
;; done in the csharp value for c-basic-matchers-before .
;; - also fontify the name following namespace decl.
;; This is done in the csharp value for c-basic-matchers-after .
;; - turn on recognition of generic types. They are now
;; fontified correctly.
;; - <> are now treated as syntactic parens and can be jumped
;; over with c-forward-sexp.
;; - Constructors are now fontified.
;; - Field/Prop names inside object initializers are now fontified.
;;
;; 0.7.7 - relocate running c-run-mode-hooks to the end of
;; csharp-mode, to allow user to modify key bindings in a
;; hook if he doesn't like the defaults.
;;
;; 0.7.8 - redefine csharp-log to insert timestamp.
;; - Fix byte-compile errors on emacs 23.2 ? Why was
;; c-filter-ops duplicated here? What was the purpose of its
;; presence here, I am not clear.
;;
;; 0.8.0 - include flymake magic into this module.
;; - include yasnippet integration
;;
;; 0.8.2 2011 April DPC
;; - small tweaks; now set a one-time bool for flymake installation
;; - some doc updates on flymake
;;
;; 0.8.3 2011 May 17 DPC
;; - better help on csharp-mode
;; - csharp-move-* functions for manual navigation.
;; - imenu integration for menu-driven navigation - navigate to
;; named methods, classes, etc.
;; - adjusted the flymake regexp to handle output from fxcopcmd,
;; and extended the help to provide examples how to use this.
;;
;; 0.8.4 DPC 2011 May 18
;; - fix a basic bug in the `csharp-yasnippet-fixup' fn.
;;
;; 0.8.5 DPC 2011 May 21
;; - imenu: correctly parse Properties that are part of an
;; explicitly specified interface. Probably need to do this
;; for methods, too.
;; - fontify the optional alias before namespace in a using (import).
;; - Tweak open-curly magic insertion for object initializers.
;; - better fontification of variables and references
;; - "sealed" is now fontified as a keyword
;; - imenu: correctly index ctors that call this or base.
;; - imenu: correctly index Extension methods (this System.Enum e)
;; - imenu: correctly scan method params tagged with out, ref, params
;; - imenu scan: now handle curlies within strings.
;; - imenu: split menus now have better labels, are sorted correctly.
;;
;; 0.8.6 DPC 2011 May ??
;; -
(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.
;;
;; NB: I think this is needed to satisfy requirements when this module
;; calls `c-lang-defconst'. (DPC)
;; (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 load of cc-fonts 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
;; ==================================================================
;; ==================================================================
;; constants used in this module
;; ==================================================================
;;(error (byte-compile-dest-file))
;;(error (c-get-current-file))
(defconst csharp-aspnet-directive-re
"<%@.+?%>"
"Regex for matching directive blocks in ASP.NET files (.aspx, .ashx, .ascx)")
(defconst csharp-enum-decl-re
(concat
"\\<enum[ \t\n\r\f\v]+"
"\\([[:alpha:]_][[:alnum:]_]*\\)"
"[ \t\n\r\f\v]*"
"\\(:[ \t\n\r\f\v]*"
"\\("
(c-make-keywords-re nil
(list "sbyte" "byte" "short" "ushort" "int" "uint" "long" "ulong"))
"\\)"
"\\)?")
"Regex that captures an enum declaration in C#"
)
;; ==================================================================
;; ==================================================================
;; csharp-mode utility and feature defuns
;; ==================================================================
(defun csharp--at-vsemi-p (&optional pos)
"Determines if there is a virtual semicolon at POS or point.
It returns t if at a position where a virtual-semicolon is.
Otherwise nil.
This is the C# version of the function. It gets set into
the variable `c-at-vsemi-p-fn'.
A vsemi is a cc-mode concept implying the end of a statement,
where no actual end-of-statement signifier character ( semicolon,
close-brace) appears. The concept is used to allow proper
indenting of blocks of code: Where a vsemi appears, the following
line will not indent further.
A vsemi appears in 3 cases in C#:
- after an attribute that decorates a class, method, field, or
property.
- in an object initializer, before the open-curly?
- after an ASPNET directive, that appears in a aspx/ashx/ascx file
An example of the former is [WebMethod] or [XmlElement].
An example of the latter is something like this:
<%@ WebHandler Language=\"C#\" Class=\"Handler\" %>
Providing this function allows the indenting in csharp-mode
to work properly with code that includes attributes and ASPNET
directives.
"
(save-excursion
(let ((pos-or-point (progn (if pos (goto-char pos)) (point))))
(cond
;; before open curly in object initializer. new Foo* { }
((and (looking-back
(concat "\\<new[ \t\n\f\v\r]+"
"\\(?:[A-Za-z_][[:alnum:]]*\\.\\)*"
"[A-Za-z_][[:alnum:]]*[\ t\n\f\v\r]*"))
(looking-at "[ \t\n\f\v\r]*{"))
t)
;; put a vsemi after an ASPNET directive, like
;; <%@ WebHandler Language="C#" Class="Handler" %>
((looking-back (concat csharp-aspnet-directive-re "$") nil t)
t)
;; put a vsemi after an attribute, as with
;; [XmlElement]
;; Except when the attribute is used within a line of code, as
;; specifying something for a parameter.
((c-safe (backward-sexp) t)
(cond
((re-search-forward
(concat
"\\(\\["
"[ \t\n\r\f\v]*"
"\\("
"\\(?:[A-Za-z_][[:alnum:]]*\\.\\)*"
"[A-Za-z_][[:alnum:]]*"
"\\)"
"[^]]*\\]\\)"
)
(1+ pos-or-point) t)
(c-safe (backward-sexp))
(c-backward-syntactic-ws)
(cond
((eq (char-before) 93) ;; close sq brace (a previous attribute)
(csharp--at-vsemi-p (point))) ;; recurse
((or
(eq (char-before) 59) ;; semicolon
(eq (char-before) 123) ;; open curly
(eq (char-before) 125)) ;; close curly
t)
;; attr is used within a line of code
(t nil)))
(t nil)))
(t nil))
)))
(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-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 a symbol: `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 "parse lim(%d) state: %s" lim (prin1-to-string state))
(cond
((elt state 3)
(csharp-log 4 "in literal string (%d)" pos)
'string)
((elt state 4)
(csharp-log 4 "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-insert-open-brace ()
"Intelligently insert a pair of curly braces. This fn should be
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. This works for String.Format()
purposes.
- Following = or [], as in an array assignment, you get a pair
of braces, with two intervening spaces, with a semincolon
appended. Point is left between the braces.
- Following \"new Foo\", it's an object initializer. You get:
newline, open brace, newline, newline, close, semi. Point is
left on the blank line between the braces. Unless the object
initializer is within an array initializer, in which case, no
newlines, and the semi is replaced with a comma. (Try it to
see what this means).
- Following => , implying a lambda, you get an open/close pair,
with two intervening spaces, no semicolon, and point on the
2nd space.
- Otherwise, you get a newline, the open curly, followed by
an empty line and the closing curly on the line following,
with point on the empty line.
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 " \t")
(> (- (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
(and (c-safe (backward-sexp) t)
(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")
(csharp-log 2 "object initializer")
(setq tpoint (point)) ;; prepare to indent-region later
(backward-word 2)
(c-backward-syntactic-ws)
(if (or (eq (char-before) ?,) ;; comma
(and (eq (char-before) 123) ;; open curly
(progn (backward-char)
(c-backward-syntactic-ws)
(looking-back "\\[\\]"))))
(progn
;; within an array - emit no newlines
(goto-char tpoint)
(self-insert-command 1)
(insert " },")
(backward-char 3))
(progn
(goto-char tpoint)
(newline)
(self-insert-command 1)
(newline-and-indent)
(newline)
(insert "};")
(c-indent-region tpoint (point))
(forward-line -1)
(indent-according-to-mode)
(end-of-line))))
;; Case 4: a lambda initialier.
;; --------------------------------------------
;; If the open curly follows =>, then it's a lambda initializer.
((string= (substring preceding3 -2) "=>")
(csharp-log 2 "lambda init")
(self-insert-command 1)
(insert " }")
(backward-char 2))
;; else, it's a new scope. (if, while, class, etc)
(t
(save-excursion
(csharp-log 2 "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
(if (c-safe (backward-sexp) t)
(line-number-at-pos)
-1))))
(= 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)
(forward-line -1)
(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
;; ==================================================================
(c-lang-defconst c-at-vsemi-p-fn
csharp 'csharp--at-vsemi-p)
;; This c-opt-after-id-concat-key is a regexp that matches
;; dot. In other words: "\\(\\.\\)"
;; Not sure why this needs to be so complicated.
;; This const is now internal (obsolete); need to move to
;; c-after-id-concat-ops. I don't yet understand the meaning
;; of that variable, so for now. . . .
;; (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-opt-after-id-concat-key
csharp "[[:alpha:]_]" )
;; The matchers elements can be of many forms. It gets pretty
;; complicated. Do a describe-variable on font-lock-keywords to get a
;; description. (Why on font-lock-keywords? I don't know, but that's
;; where you get the help.)
;;
;; Aside from the provided documentation, the other option of course, is
;; to look in the source code as an example for what to do. The source
;; in cc-fonts uses a defun c-make-font-lock-search-function to produce
;; most of the matchers. Called this way:
;;
;; (c-make-font-lock-search-function regexp '(A B c))
;;
;; The REGEXP is used in re-search-forward, and if there's a match, then
;; A is called within a save-match-data. If B and C are non-nil, they
;; are called as pre and post blocks, respecitvely.
;;
;; Anyway the c-make-font-lock-search-function works for a single regex,
;; but more complicated scenarios such as those intended to match and
;; fontify object initializers, call for a hand-crafted lambda.
;;
;; The object initializer is special because matching on it must
;; allow nesting.
;;
;; In c#, the object initializer block is used directly after a
;; constructor, like this:
;;
;; new MyType
;; {
;; Prop1 = "foo"
;; }
;;
;; csharp-mode needs to fontify the properties in the
;; initializer block in font-lock-variable-name-face. The key thing is
;; to set the text property on the open curly, using type c-type and
;; value c-decl-id-start. This apparently allows `parse-partial-sexp' to
;; do the right thing, later.
;;
;; This simple case is easy to handle in a regex, using the basic
;; `c-make-font-lock-search-function' form. But the general syntax for a
;; constructor + object initializer in C# is more complex:
;;
;; new MyType(..arglist..) {
;; Prop1 = "foo"
;; }
;;
;; A simple regex match won't satisfy here, because the ..arglist.. can
;; be anything, including calls to other constructors, potentially with
;; object initializer blocks. This may nest arbitrarily deeply, and the
;; regex in emacs doesn't support balanced matching. Therefore there's
;; no way to match on the "outside" pair of parens, to find the relevant
;; open curly. What's necessary is to do the match on "new MyType" then
;; skip over the sexp defined by the parens, then set the text property on
;; the appropriate open-curly.
;;
;; To make that happen, it's good to have insight into what the matcher
;; really does. The output of `c-make-font-lock-search-function' before
;; byte-compiling, is:
;;
;; (lambda (limit)
;; (let ((parse-sexp-lookup-properties
;; (cc-eval-when-compile
;; (boundp 'parse-sexp-lookup-properties))))
;; (while (re-search-forward REGEX limit t)
;; (unless
;; (progn
;; (goto-char (match-beginning 0))
;; (c-skip-comments-and-strings limit))
;; (goto-char (match-end 0))
;; (progn
;; B
;; (save-match-data A)
;; C ))))
;; nil)
;;
;; csharp-mode uses this hand-crafted form of a matcher to handle the
;; general case for constructor + object initializer, within
;; `c-basic-matchers-after' .
;;
;; (defun c-make-font-lock-search-function (regexp &rest highlights)
;; ;; This function makes a byte compiled function that works much like
;; ;; a matcher element in `font-lock-keywords'. It cuts out a little
;; ;; bit of the overhead compared to a real matcher. The main reason
;; ;; is however to pass the real search limit to the anchored
;; ;; matcher(s), since most (if not all) font-lock implementations
;; ;; arbitrarily limits anchored matchers to the same line, and also
;; ;; to insulate against various other irritating differences between
;; ;; the different (X)Emacs font-lock packages.
;; ;;
;; ;; REGEXP is the matcher, which must be a regexp. Only matches
;; ;; where the beginning is outside any comment or string literal are
;; ;; significant.
;; ;;
;; ;; HIGHLIGHTS is a list of highlight specs, just like in
;; ;; `font-lock-keywords', with these limitations: The face is always
;; ;; overridden (no big disadvantage, since hits in comments etc are
;; ;; filtered anyway), there is no "laxmatch", and an anchored matcher
;; ;; is always a form which must do all the fontification directly.
;; ;; `limit' is a variable bound to the real limit in the context of
;; ;; the anchored matcher forms.
;; ;;
;; ;; This function does not do any hidden buffer changes, but the
;; ;; generated functions will. (They are however used in places
;; ;; covered by the font-lock context.)
;;
;; ;; Note: Replace `byte-compile' with `eval' to debug the generated
;; ;; lambda easier.
;; (byte-compile
;; `(lambda (limit)
;; (let (;; The font-lock package in Emacs is known to clobber
;; ;; `parse-sexp-lookup-properties' (when it exists).
;; (parse-sexp-lookup-properties
;; (cc-eval-when-compile
;; (boundp 'parse-sexp-lookup-properties))))
;; (while (re-search-forward ,regexp limit t)
;; (unless (progn
;; (goto-char (match-beginning 0))
;; (c-skip-comments-and-strings limit))
;; (goto-char (match-end 0))
;; ,@(mapcar
;; (lambda (highlight)
;; (if (integerp (car highlight))
;; (progn
;; (unless (eq (nth 2 highlight) t)
;; (error
;; "The override flag must currently be t in %s"
;; highlight))
;; (when (nth 3 highlight)
;; (error
;; "The laxmatch flag may currently not be set in %s"
;; highlight))
;; `(save-match-data
;; (c-put-font-lock-face
;; (match-beginning ,(car highlight))
;; (match-end ,(car highlight))
;; ,(elt highlight 1))))
;; (when (nth 3 highlight)
;; (error "Match highlights currently not supported in %s"
;; highlight))
;; `(progn
;; ,(nth 1 highlight)
;; (save-match-data ,(car highlight))
;; ,(nth 2 highlight))))
;; highlights))))
;; nil))
;; )
(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 the namespaces that follow using statements.
;; This regex handles the optional alias, as well.
,`(,(concat
"\\<\\(using\\)[ \t\n\f\v\r]+"
"\\(?:"
"\\([A-Za-z_][[:alnum:]]*\\)"
"[ \t\n\f\v\r]*="
"[ \t\n\f\v\r]*"
"\\)?"
"\\(\\(?:[A-Za-z_][[:alnum:]]*\\.\\)*[A-Za-z_][[:alnum:]]*\\)"
"[ \t\n\f\v\r]*;")
(2 font-lock-constant-face t t)
(3 font-lock-constant-face))
;; Fontify all keywords except the primitive types.
,`(,(concat "\\<" (c-lang-const c-regular-keywords-regexp))
1 font-lock-keyword-face)
;; Fontify leading identifiers as a reference? in fully
;; qualified names like "Foo.Bar".
,@(when (c-lang-const c-opt-identifier-concat-key)
`((,(byte-compile
`(lambda (limit)
(csharp-log 3 "bmb reference? p(%d) L(%d)" (point) limit)
(while (re-search-forward
,(concat "\\(\\<" ;; 1
"\\(" ;; 2
;;"[A-Z]";; uppercase - assume upper = classname
"[A-Za-z_]" ;; any old
"[A-Za-z0-9_]*" ;; old: (c-lang-const c-symbol-key)
"\\)"
"[ \t\n\r\f\v]*"
"\\." ;;(c-lang-const c-opt-identifier-concat-key)
"[ \t\n\r\f\v]*"
"\\)" ;; 1 ends
"\\("
"[[:alpha:]_][A-Za-z0-9_]*" ;; start of another symbolname
"\\)" ;; 3 ends
)
limit t)
(csharp-log 3 "bmb ref? B(%d)" (match-beginning 0))
(unless (progn
(goto-char (match-beginning 0))
(c-skip-comments-and-strings limit))
(let* ((prefix (match-string 2))
(me1 (match-end 1))
(first-char (string-to-char prefix))
(is-upper (and (>= first-char 65)
(<= first-char 90))))
(csharp-log 3 " - class/intf ref (%s)" prefix)
;; only put face if not there already
(or (get-text-property (match-beginning 2) 'face)
(c-put-font-lock-face (match-beginning 2)
(match-end 2)
(if is-upper
font-lock-type-face ;; it's a type!
font-lock-variable-name-face)))
(goto-char (match-end 3))
(c-forward-syntactic-ws limit)
;; now, maybe fontify the thing afterwards, too
(let ((c (char-after)))
(csharp-log 3 " - now lkg at c(%c)" c)
(cond
((= c 40) ;; open paren
(or (get-text-property (match-beginning 3) 'face)
(c-put-font-lock-face (match-beginning 3)
(match-end 3)
font-lock-function-name-face))
(goto-char (match-end 3)))
;; these all look like variables or properties
((or (= c 59) ;; semicolon
(= c 91) ;; open sq brack
(= c 41) ;; close paren
(= c 44) ;; ,
(= c 33) ;; !
(= c 124) ;; |
(= c 61) ;; =
(= c 43) ;; +
(= c 45) ;; -
(= c 42) ;; *
(= c 47)) ;; /
(or (get-text-property (match-beginning 3) 'face)
(c-put-font-lock-face (match-beginning 3)
(match-end 3)
font-lock-variable-name-face))
(goto-char (match-end 3)))
(t
(goto-char (match-end 1)))))))))))))
))
(c-lang-defconst c-basic-matchers-after
csharp `(
;; option 1:
;; ,@(when condition
;; `((,(byte-compile
;; `(lambda (limit) ...
;;
;; option 2:
;; ,`((lambda (limit) ...
;;
;; I don't know how to avoid the (when condition ...) in the
;; byte-compiled version.
;;
;; X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+X+
;; Case 1: invocation of constructor + maybe an object
;; initializer. Some possible examples that satisfy:
;;
;; new Foo ();
;;
;; new Foo () { };
;;
;; new Foo { };
;;
;; new Foo { Prop1= 7 };
;;
;; new Foo {
;; Prop1= 7
;; };
;;
;; new Foo {
;; Prop1= 7,
;; Prop2= "Fred"
;; };
;;
;; new Foo {
;; Prop1= new Bar()
;; };
;;
;; new Foo {
;; Prop1= new Bar { PropA = 5.6F }
;; };
;;
,@(when t
`((,(byte-compile
`(lambda (limit)
(let ((parse-sexp-lookup-properties
(cc-eval-when-compile
(boundp 'parse-sexp-lookup-properties))))
(while (re-search-forward
,(concat "\\<new"
"[ \t\n\r\f\v]+"
"\\(\\(?:"
(c-lang-const c-symbol-key)
"\\.\\)*"
(c-lang-const c-symbol-key)
"\\)"
)
limit t)
(unless
(progn
(goto-char (match-beginning 0))
(c-skip-comments-and-strings limit))
(csharp-log 3 "ctor invoke? at %d" (match-beginning 1))
(save-match-data
;; next thing could be: [] () <> or {} or nothing (semicolon, comma).
;; fontify the typename
(c-put-font-lock-face (match-beginning 1)
(match-end 1)
'font-lock-type-face)
(goto-char (match-end 0))
(c-forward-syntactic-ws limit)
(if (eq (char-after) ?<) ;; ctor for generic type
(progn
(csharp-log 3 " - this is a generic type")
;; skip over <> safely
(c-safe (c-forward-sexp 1) t)
(c-forward-syntactic-ws)))
;; now, could be [] or (..) or {..} or semicolon.
(csharp-log 3 " - looking for sexp")
(if (or
(eq (char-after) ?{) ;; open curly
(and (eq (char-after) 91) ;; open square
(while (eq (char-after) 91)
(c-safe (c-forward-sexp 1)))
(eq (char-before) 93)) ;; close square
(and (eq (char-after) 40) ;; open paren
(c-safe (c-forward-sexp 1) t)))
(progn
;; at this point we've jumped over any intervening s-exp,
;; like sq brackets or parens.
(c-forward-syntactic-ws)
(csharp-log 3 " - after fwd-syn-ws point(%d)" (point))
(csharp-log 3 " - next char: %c" (char-after))
(if (eq (char-after) ?{)
(let ((start (point))
(end (if (c-safe (c-forward-sexp 1) t)
(point) 0)))
(csharp-log 3 " - open curly gets c-decl-id-start %d" start)
(c-put-char-property start
'c-type
'c-decl-id-start)
(goto-char start)
(if (> end start)
(progn
(forward-char 1) ;; step over open curly
(c-forward-syntactic-ws)
(while (> end (point))
;; now, try to fontify/assign variables to any properties inside the curlies
(csharp-log 3 " - inside open curly point(%d)" (point))
(csharp-log 3 " - next char: %c" (char-after))
;; fontify each property assignment
(if (re-search-forward
(concat "\\(" (c-lang-const c-symbol-key) "\\)\\s*=")
end t)
(progn
(csharp-log 3 " - found variable %d-%d"
(match-beginning 1)
(match-end 1))
(c-put-font-lock-face (match-beginning 1)
(match-end 1)
'font-lock-variable-name-face)
(goto-char (match-end 0))
(c-forward-syntactic-ws)
;; advance to the next assignment, if possible
(if (eq (char-after) ?@)
(forward-char 1))
(if (c-safe (c-forward-sexp 1) t)
(progn
(forward-char 1)
(c-forward-syntactic-ws))))
;; else
(csharp-log 3 " - no more assgnmts found")
(goto-char end)))))
)))))
(goto-char (match-end 0))
)))
nil))
)))
;; Case 2: declaration of enum with or without an explicit
;; base type.
;;
;; Examples:
;;
;; public enum Foo { ... }
;;
;; public enum Foo : uint { ... }
;;
,@(when t
`((,(byte-compile
`(lambda (limit)
(let ((parse-sexp-lookup-properties
(cc-eval-when-compile
(boundp 'parse-sexp-lookup-properties))))
(while (re-search-forward
,(concat csharp-enum-decl-re
"[ \t\n\r\f\v]*"
"{")
limit t)
(csharp-log 3 "enum? at %d" (match-beginning 0))
(unless
(progn
(goto-char (match-beginning 0))
(c-skip-comments-and-strings limit))
(progn
(save-match-data
(goto-char (match-end 0))
(c-put-char-property (1- (point))
'c-type
'c-decl-id-start)
(c-forward-syntactic-ws))
(save-match-data
(c-font-lock-declarators limit t nil))
(goto-char (match-end 0))
)
)))
nil))
)))
;; Case 3: declaration of constructor
;;
;; Example:
;;
;; private Foo(...) {...}
;;
,@(when t
`((,(byte-compile
`(lambda (limit)
(let ((parse-sexp-lookup-properties
(cc-eval-when-compile
(boundp 'parse-sexp-lookup-properties)))
(found-it nil))
(while (re-search-forward
,(concat
"^[ \t\n\r\f\v]*"
"\\(\\<\\(public\\|private\\|protected\\)\\)?[ \t\n\r\f\v]+"
"\\(@?[[:alpha:]_][[:alnum:]_]*\\)" ;; name of constructor
"[ \t\n\r\f\v]*"
"\\("
"("
"\\)")
limit t)
(unless
(progn
(goto-char (match-beginning 0))
(c-skip-comments-and-strings limit))
(goto-char (match-end 0))
(csharp-log 3 "ctor decl? L(%d) B(%d) E(%d)"
limit (match-beginning 0) (point))
(backward-char 1) ;; just left of the open paren
(save-match-data
;; Jump over the parens, safely.
;; If it's an unbalanced paren, no problem,
;; do nothing.
(if (c-safe (c-forward-sexp 1) t)
(progn
(c-forward-syntactic-ws)
(cond
;; invokes base or this constructor.
((re-search-forward
,(concat
"\\(:[ \t\n\r\f\v]*\\(base\\|this\\)\\)"
"[ \t\n\r\f\v]*"
"("
)
limit t)
(csharp-log 3 " - ctor with dependency?")
(goto-char (match-end 0))
(backward-char 1) ;; just left of the open paren
(csharp-log 3 " - before paren at %d" (point))
(if (c-safe (c-forward-sexp 1) t)
(progn
(c-forward-syntactic-ws)
(csharp-log 3 " - skipped over paren pair %d" (point))
(if (eq (char-after) ?{)
(setq found-it t)))))
;; open curly. no depedency on other ctor.
((eq (char-after) ?{)
(csharp-log 3 " - no dependency, curly at %d" (point))
(setq found-it t)))
)))
(if found-it
;; fontify the constructor symbol
(c-put-font-lock-face (match-beginning 3)
(match-end 3)
'font-lock-function-name-face))
(goto-char (match-end 0)))))
nil)))))
;; Case 4: using clause. Without this, using (..) gets fontified as a fn.
,@(when t
`((,(byte-compile
`(lambda (limit)
(let ((parse-sexp-lookup-properties
(cc-eval-when-compile
(boundp 'parse-sexp-lookup-properties))))
(while (re-search-forward
,(concat "\\<\\(using\\)"
"[ \t\n\r\f\v]*"
"(")
limit t)
(csharp-log 3 "using clause p(%d)" (match-beginning 0))
(unless
(progn
(goto-char (match-beginning 0))
(c-skip-comments-and-strings limit))
(save-match-data
(c-put-font-lock-face (match-beginning 1)
(match-end 1)
'font-lock-keyword-face)
(goto-char (match-end 0))))))
nil))
)))
;; Case 5: attributes
,`((lambda (limit)
(let ((parse-sexp-lookup-properties
(cc-eval-when-compile
(boundp 'parse-sexp-lookup-properties))))
(while (re-search-forward
,(concat "[ \t\n\r\f\v]+"
"\\(\\["
"[ \t\n\r\f\v]*"
"\\(?:\\(?:return\\|assembly\\)[ \t]*:[ \t]*\\)?"
"\\("
"\\(?:[A-Za-z_][[:alnum:]]*\\.\\)*"
"[A-Za-z_][[:alnum:]]*"
"\\)"
"[^]]*\\]\\)"
)
limit t)
(csharp-log 3 "attribute? - %d limit(%d)" (match-beginning 1)
limit)
(unless
(progn
(goto-char (match-beginning 1))
(c-skip-comments-and-strings limit))
(let ((b2 (match-beginning 2))
(e2 (match-end 2))
(is-attr nil))
(csharp-log 3 " - type match: %d - %d"
b2 e2)
(save-match-data
(c-backward-syntactic-ws)
(setq is-attr (or
(eq (char-before) 59) ;; semicolon
(eq (char-before) 93) ;; close square brace
(eq (char-before) 123) ;; open curly
(eq (char-before) 125) ;; close curly
(save-excursion
(c-beginning-of-statement-1)
(looking-at
"#\\(pragma\\|endregion\\|region\\|if\\|else\\|endif\\)"))
)))
(if is-attr
(progn
(if (<= 3 csharp-log-level)
(csharp-log 3 " - attribute: '%s'"
(buffer-substring-no-properties b2 e2)))
(c-put-font-lock-face b2 e2 'font-lock-type-face)))))
(goto-char (match-end 0))
))
nil))
;; Case 6: directive blocks for .aspx/.ashx/.ascx
,`((lambda (limit)
(let ((parse-sexp-lookup-properties
(cc-eval-when-compile
(boundp 'parse-sexp-lookup-properties))))
(while (re-search-forward csharp-aspnet-directive-re limit t)
(csharp-log 3 "aspnet template? - %d limit(%d)" (match-beginning 1)
limit)
(unless
(progn
(goto-char (match-beginning 0))
(c-skip-comments-and-strings limit))
(save-match-data
(let ((end-open (+ (match-beginning 0) 3))
(beg-close (- (match-end 0) 2)))
(c-put-font-lock-face (match-beginning 0)
end-open
'font-lock-preprocessor-face)
(c-put-font-lock-face beg-close
(match-end 0)
'font-lock-preprocessor-face)
;; fontify within the directive
(while (re-search-forward
,(concat
"\\("
(c-lang-const c-symbol-key)
"\\)"
"=?"
)
beg-close t)
(c-put-font-lock-face (match-beginning 1)
(match-end 1)
'font-lock-keyword-face)
(c-skip-comments-and-strings beg-close))
))
(goto-char (match-end 0)))))
nil))
;; ;; Case 5: #if
;; ,@(when t
;; `((,(byte-compile
;; `(lambda (limit)
;; (let ((parse-sexp-lookup-properties
;; (cc-eval-when-compile
;; (boundp 'parse-sexp-lookup-properties))))
;; (while (re-search-forward
;; "\\<\\(#if\\)[ \t\n\r\f\v]+\\([A-Za-z_][[:alnum:]]*\\)"
;; limit t)
;;
;; (csharp-log 3 "#if directive - %d" (match-beginning 1))
;;
;; (unless
;; (progn
;; (goto-char (match-beginning 0))
;; (c-skip-comments-and-strings limit))
;;
;; (save-match-data
;; (c-put-font-lock-face (match-beginning 2)
;; (match-end 2)
;; 'font-lock-variable-name-face)
;; (goto-char (match-end 0))))))
;; nil))
;; )))
;; ,`(,(c-make-font-lock-search-function
;; (concat "\\<new"
;; "[ \t\n\r\f\v]+"
;; "\\(\\(?:"
;; (c-lang-const c-symbol-key)
;; "\\.\\)*"
;; (c-lang-const c-symbol-key)
;; "\\)"
;; "[ \t\n\r\f\v]*"
;; "\\(?:"
;; "( *)[ \t\n\r\f\v]*" ;; optional ()
;; "\\)?"
;; "{")
;; '((c-font-lock-declarators limit t nil)
;; (save-match-data
;; (goto-char (match-end 0))
;; (c-put-char-property (1- (point)) 'c-type
;; 'c-decl-id-start)
;; (c-forward-syntactic-ws))
;; (goto-char (match-end 0)))))
;; Fontify labels after goto etc.
,@(when (c-lang-const c-before-label-kwds)
`( ;; (Got three different interpretation levels here,
;; which makes it a bit complicated: 1) The backquote
;; stuff is expanded when compiled or loaded, 2) the
;; eval form is evaluated at font-lock setup (to
;; substitute c-label-face-name correctly), and 3) the
;; resulting structure is interpreted during
;; fontification.)
(eval
. ,(let* ((c-before-label-re
(c-make-keywords-re nil
(c-lang-const c-before-label-kwds))))
`(list
,(concat "\\<\\(" c-before-label-re "\\)\\>"
"\\s *"
"\\(" ; identifier-offset
(c-lang-const c-symbol-key)
"\\)")
(list ,(+ (regexp-opt-depth c-before-label-re) 2)
c-label-face-name nil t))))))
;; Fontify the clauses after various keywords.
,@(when (or (c-lang-const c-type-list-kwds)
(c-lang-const c-ref-list-kwds)
(c-lang-const c-colon-type-list-kwds)
(c-lang-const c-paren-type-kwds))
`((,(c-make-font-lock-search-function
(concat "\\<\\("
(c-make-keywords-re nil
(append (c-lang-const c-type-list-kwds)
(c-lang-const c-ref-list-kwds)
(c-lang-const c-colon-type-list-kwds)
(c-lang-const c-paren-type-kwds)))
"\\)\\>")
'((c-fontify-types-and-refs ((c-promote-possible-types t))
(c-forward-keyword-clause 1)
(if (> (point) limit) (goto-char limit))))))))
;; Fontify the name that follows each namespace declaration
;; this needs to be done in the matchers-after because
;; otherwise the namespace names get the font-lock-type-face,
;; due to the energetic efforts of c-forward-type.
,`("\\<\\(namespace\\)[ \t\n\r\f\v]+\\(\\(?:[A-Za-z_][[:alnum:]]*\\.\\)*[A-Za-z_][[:alnum:]]*\\)"
2 font-lock-constant-face t)
))
;; C# does generics. Setting this to t tells the parser to put
;; parenthesis syntax on angle braces that surround a comma-separated
;; list.
(c-lang-defconst c-recognize-<>-arglists
csharp t)
(c-lang-defconst c-identifier-key
csharp (concat "\\([[:alpha:]_][[:alnum:]_]*\\)" ; 1
"\\("
"[ \t\n\r\f\v]*"
"\\(\\.\\)" ;;(c-lang-const c-opt-identifier-concat-key)
"[ \t\n\r\f\v]*"
"\\(\\([[:alpha:]_][[:alnum:]_]*\\)\\)"
"\\)*"))
;; 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, region, 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-identifier-syntax-modifications by default defines $ as a word
;; syntax, which is not legal in C#. So, define our own lang-specific
;; value.
(c-lang-defconst c-identifier-syntax-modifications
csharp '((?_ . "w")))
(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 verify 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?
;; don't include enum here, because we want it to be fontified as a brace
;; list, with commas delimiting the values. see c-brace-list-decl-kwds
;; below.
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" "sealed"
"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, because catch has a paren (typically).
(c-lang-defconst c-block-stmt-1-kwds
csharp '("do" "try" "finally" "unsafe"))
;; 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 '("sizeof" "typeof" "is" "as" "yield"
"where" "select" "in" "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)))
;; Custom variables
;;;###autoload
(defcustom csharp-mode-hook nil
"*Hook called by `csharp-mode'."
:type 'hook
:group 'csharp)
;; The following fn allows this:
;; (csharp-log 3 "scan result...'%s'" state)
(defcustom csharp-log-level 0
"The current log level for CSharp-mode-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. "
:type 'integer
:group 'csharp)
;;;###autoload
(defcustom csharp-want-flymake-fixup t
"*Whether to enable the builtin C# support for flymake. This is meaningful
only if flymake is loaded."
:type 'boolean :group 'csharp)
;;;###autoload
(defcustom csharp-want-yasnippet-fixup t
"*Whether to enable the builtin C# support for yasnippet. This is meaningful
only if flymake is loaded."
:type 'boolean :group 'csharp)
;;;###autoload
(defcustom csharp-want-imenu t
"*Whether to generate a buffer index via imenu for C# buffers."
:type 'boolean :group 'csharp)
;;;###autoload
(defcustom csharp-make-tool "nmake.exe"
"*The make tool to use. Defaults to nmake, found on path. Specify
a full path or alternative program name, to tell csharp-mode to use
a different make tool in compile commands.
See also, `csharp-msbuild-tool'.
"
:type 'string :group 'csharp)
;;;###autoload
(defcustom csharp-msbuild-tool "msbuild.exe"
"*The tool to use to build .csproj files. Defaults to msbuild, found on
path. Specify a full path or alternative program name, to tell csharp-mode
to use a different make tool in compile commands.
See also, `csharp-make-tool'.
"
:type 'string :group 'csharp)
;;;###autoload
(defcustom csharp-cmd-line-limit 28
"The number of lines at the top of the file to look in, to find
the command that csharp-mode will use to compile the current
buffer, or the command \"stub\" that csharp-mode will use to
check the syntax of the current buffer via flymake.
If the value of this variable is zero, then csharp-mode looks
everywhere in the file. If the value is positive, then only in
the first N lines. If negative, then only in the final N lines.
The line should appear in a comment inside the C# buffer.
Compile
--------
In the case of compile, the compile command must be prefixed with
\"compile:\". For example,
// compile: csc.exe /r:Hallo.dll Arfie.cs
This command will be suggested as the compile command when the
user invokes `compile' for the first time.
Flymake
--------
In the case of flymake, the command \"stub\" string must be
prefixed with \"flymake:\". For example,
// flymake: DOTNETDIR\csc.exe /target:netmodule /r:foo.dll @@FILE@@
In the case of flymake, the string should NOT include the name of
the file for the buffer being checked. Instead, use the token
@@FILE@@ . csharp-mode will replace this token with the name of
the source file to compile, before passing the command to flymake
to run it.
If for some reason the command is invalid or illegal, flymake
will report an error and disable itself.
It might be handy to run fxcop, for example, via flymake.
// flymake: fxcopcmd.exe /c /f:MyLibrary.dll
In all cases
------------
Be sure to specify the proper path for your csc.exe, whatever
version that might be, or no path if you want to use the system
PATH search.
If the buffer depends on external libraries, then you will want
to include /R arguments to that csc.exe command.
To be clear, this variable sets the number of lines to search for
the command. This cariable is an integer.
If the marker string (either \"compile:\" or \"flymake:\"
is present in the given set of lines, csharp-mode will take
anything after the marker string as the command to run.
"
:type 'integer :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.")
(defvar csharp--yasnippet-has-been-fixed nil
"indicates whether yasnippet has been patched for use with csharp.
Intended for internal use only.")
(defvar csharp--flymake-has-been-installed nil
"one-time use boolean, to check whether csharp tweaks for flymake (advice
etc) have been installed.")
(defvar csharp-flymake-csc-arguments
(list "/t:module" "/nologo")
"A list of arguments to use with the csc.exe
compiler, when using flymake with a
direct csc.exe build for syntax checking purposes.")
(defvar csharp-flymake-aux-error-info nil
"a list of auxiliary flymake error info items. Each item in the
list is a pair, consisting of a line number and a column number.
This info is set by advice to flymake-parse-line, and used by
advice attached to flymake-goto-line, to navigate to the proper
error column when possible. ")
(defvar csharp-flymake-csc-error-pattern
"^[ \t]*\\([_A-Za-z0-9][^(]+\\.cs\\)(\\([0-9]+\\)[,]\\([0-9]+\\)) ?: \\(\\(error\\|warning\\) +:? *C[SA][0-9]+ *:[ \t\n]*\\(.+\\)\\)"
"The regex pattern for C# compiler error messages. Follows
the same form as an entry in `flymake-err-line-patterns'. The
value is a STRING, a regex.")
;; 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
;; ==================================================================
;; ========================================================================
;; Flymake integration
(defun csharp-flymake-init ()
(csharp-flymake-init-impl
'flymake-create-temp-inplace t t 'csharp-flymake-get-cmdline))
(defun csharp-flymake-init-impl (create-temp-f use-relative-base-dir use-relative-source get-cmdline-f)
"Create syntax check command line for a directly checked source file.
Use CREATE-TEMP-F for creating temp copy."
(let* ((args nil)
(temp-source-file-name (flymake-init-create-temp-buffer-copy create-temp-f)))
(setq args (flymake-get-syntax-check-program-args
temp-source-file-name "."
use-relative-base-dir use-relative-source
get-cmdline-f))
args))
(defun csharp-flymake-cleanup ()
"Delete the temporary .netmodule file created in syntax checking
a C# buffer, then call through to flymake-simple-cleanup."
(if flymake-temp-source-file-name
(progn
(let* ((netmodule-name
(concat (file-name-sans-extension flymake-temp-source-file-name)
".netmodule"))
(expanded-netmodule-name (expand-file-name netmodule-name ".")))
(if (file-exists-p expanded-netmodule-name)
(flymake-safe-delete-file expanded-netmodule-name)))
))
(flymake-simple-cleanup))
(defun csharp-split-string-respecting-quotes (s)
"splits a string into tokens, respecting double quotes
For example, the string 'This is \"a string\"' will be split into 3 tokens.
More pertinently, the string
'csc /t:module /R:\"c:\abba dabba\dooo\Foo.dll\"'
...will be split into 3 tokens.
This fn also removes quotes from the tokens that have them. This is for
compatibility with flymake and the process-start fn.
"
(let ((local-s s)
(my-re-1 "[^ \"]*\"[^\"]+\"\\|[^ \"]+")
(my-re-2 "\\([^ \"]*\\)\"\\([^\"]+\\)\"")
(tokens))
(while (string-match my-re-1 local-s)
(let ((token (match-string 0 local-s))
(remainder (substring local-s (match-end 0))))
(if (string-match my-re-2 token)
(setq token (concat (match-string 1 token) (match-string 2 token))))
;;(message "token: %s" token)
(setq tokens (append tokens (list token)))
(setq local-s remainder)))
tokens))
(defun csharp-get-value-from-comments (marker-string line-limit)
"gets a string from the header comments in the current buffer.
This is used to extract the flymake command and the compile
command from the comments.
It looks for \"marker-string:\" and returns the string that
follows it, or returns nil if that string is not found.
eg, when marker-string is \"flymake\", and the following
string is found at the top of the buffer:
flymake: csc.exe /r:Hallo.dll
...then this command will return the string
\"csc.exe /r:Hallo.dll\"
It's ok to have whitespace between the marker and the following
colon.
"
(let (start search-limit found)
;; determine what lines to look in
(save-excursion
(save-restriction
(widen)
(cond ((> line-limit 0)
(goto-char (setq start (point-min)))
(forward-line line-limit)
(setq search-limit (point)))
((< line-limit 0)
(goto-char (setq search-limit (point-max)))
(forward-line line-limit)
(setq start (point)))
(t ;0 => no limit (use with care!)
(setq start (point-min))
(setq search-limit (point-max))))))
;; look in those lines
(save-excursion
(save-restriction
(widen)
(let ((re-string
(concat "\\b" marker-string "[ \t]*:[ \t]*\\(.+\\)$")))
(if (and start
(< (goto-char start) search-limit)
(re-search-forward re-string search-limit 'move))
(buffer-substring-no-properties
(match-beginning 1)
(match-end 1))))))))
(defun csharp-replace-command-tokens (explicitly-specified-command)
"Replace tokens in the flymake or compile command extracted from the
buffer, to allow specification of the original and modified
filenames.
@@ORIG@@ - gets replaced with the original filename
@@FILE@@ - gets replaced with the name of the temporary file
created by flymake
"
(let ((massaged-command explicitly-specified-command))
(if (string-match "@@SRC@@" massaged-command)
(setq massaged-command
(replace-match
(file-relative-name flymake-temp-source-file-name) t t massaged-command)))
(if (string-match "@@ORIG@@" massaged-command)
(setq massaged-command
(replace-match
(file-relative-name buffer-file-name) t t massaged-command)))
massaged-command))
;;(setq flymake-log-level 3)
(defun csharp-flymake-get-final-csc-arguments (initial-arglist)
"Gets the command used by csc.exe for flymake runs.
This may inject a /t:module into an arglist, where it is not
present.
This fn burps if a different /t: argument is found.
"
(interactive)
(let ((args initial-arglist)
arg
(found nil))
(while args
(setq arg (car args))
(cond
((string-equal arg "/t:module") (setq found t))
((string-match "^/t:" arg)
(setq found t)
(message "csharp-mode: WARNING /t: option present in arglist, and not /t:module; fix this.")))
(setq args (cdr args)))
(setq args
(if found
initial-arglist
(append (list "/t:module") initial-arglist)))
(if (called-interactively-p 'any)
(message "result: %s" (prin1-to-string args)))
args))
(defun csharp-flymake-get-cmdline (source base-dir)
"Gets the cmd line for running a flymake session in a C# buffer.
This gets called by flymake itself.
The fn looks in the buffer for a line that looks like:
flymake: <command goes here>
(It should be embedded into a comment)
Typically the command will be a line that runs nmake.exe,
msbuild.exe, or csc.exe, with various options. It should
eventually run the CSC.exe compiler, or something else that emits
error messages in the same form as the C# compiler, like FxCopCmd.exe
Some notes on implementation:
1. csharp-mode copies the buffer to a temporary file and
compiles *that*. This temporary file has a different name
than the actual file name for the buffer - _flymake gets
appended to the basename. Therefore, you should specify
Foo_flymake.cs for the filename, if you want to explicitly
refer to it.
If you want to refer to it implicitly, you can use the special
token \"@@SRC@@\" in the command. It will get replaced with the
name of the temporary file at runtime. If you want to refer to
the original name of the buffer, use @@ORIG@@.
2. In general, when running the compiler, you should use a
target type of \"module\" (eg, /t:module) to allow
csharp-mode to clean up the products of the build.
3. See `csharp-cmd-line-limit' for a way to restrict where
csharp-mode will search for the command.
4. If this string is not found, then this fn will fallback to
a generic, generated csc.exe command.
"
(let ((explicitly-specified-command
(csharp-get-value-from-comments "flymake" csharp-cmd-line-limit)))
(cond
(explicitly-specified-command
;; the marker string was found in the buffer
(let ((tokens (csharp-split-string-respecting-quotes
(csharp-replace-command-tokens explicitly-specified-command))))
(list (car tokens) (cdr tokens))))
;; ;; implicitly append? the name of the temporary source file
;; (list (car tokens) (append (cdr tokens) (list flymake-temp-source-file-name)))))
(t
;; fallback
(list "csc.exe"
(append (csharp-flymake-get-final-csc-arguments
csharp-flymake-csc-arguments)
(list source)))))))
;; (defun csharp-flymake-get-cmdline (source base-dir)
;; "Gets the cmd line for running a flymake session in a C# buffer.
;; This gets called by flymake itself.
;;
;; The fn looks in the buffer for a line that looks like:
;;
;; flymake: <command goes here>
;;
;; (It should be embedded into a comment)
;;
;; Typically the command will be a line that runs nmake.exe,
;; msbuild.exe, or cscc.exe, with various options. It should
;; eventually run the CSC.exe compiler, or something else that emits
;; error messages in the same form as the C# compiler.
;;
;; In general, when running the compiler, you should use a target
;; type of \"module\" (eg, /t:module) to allow csharp-mode to
;; clean up the products of the build.
;;
;; See `csharp-cmd-line-limit' for a way to restrict where
;; csharp-mode will search for the command.
;;
;; If this string is not found, then this fn will fallback to a
;; generic, generated csc.exe command.
;;
;; "
;; (let ((explicitly-specified-command
;; (let ((line-limit csharp-cmd-line-limit)
;; start search-limit found)
;; ;; determine what lines to look in
;; (save-excursion
;; (save-restriction
;; (widen)
;; (cond ((> line-limit 0)
;; (goto-char (setq start (point-min)))
;; (forward-line line-limit)
;; (setq search-limit (point)))
;; ((< line-limit 0)
;; (goto-char (setq search-limit (point-max)))
;; (forward-line line-limit)
;; (setq start (point)))
;; (t ;0 => no limit (use with care!)
;; (setq start (point-min))
;; (setq search-limit (point-max))))))
;;
;; ;; look in those lines
;; (save-excursion
;; (save-restriction
;; (widen)
;; (if (and start
;; (< (goto-char start) search-limit)
;; (re-search-forward "\\bflymake-command[ \t]*:[ \t]*\\(.+\\)$" search-limit 'move))
;;
;; (buffer-substring-no-properties
;; (match-beginning 1)
;; (match-end 1))))))))
;;
;; (cond
;; (explicitly-specified-command
;; ;; the marker string was found in the buffer
;; (let ((tokens (csharp-split-string-respecting-quotes
;; explicitly-specified-command)))
;; ;; implicitly append the name of the temporary source file
;; (list (car tokens) (append (cdr tokens) (list flymake-temp-source-file-name)))))
;;
;; (t
;; ;; fallback
;; (list "csc.exe"
;; (append (csharp-flymake-get-final-csc-arguments
;; csharp-flymake-csc-arguments)
;; (list source)))))))
(defun csharp-flymake-install ()
"Change flymake variables and fns to work with C#.
This fn does these things:
1. add a C# entry to the flymake-allowed-file-name-masks,
or replace it if it already exists.
2. add a C# entry to flymake-err-line-patterns.
This isn't strictly necessary because of item #4.
3. override the definition for flymake-process-sentinel
to NOT check the process status on exit. MSBuild.exe
sets a non-zero status code when compile errors occur,
which causes flymake to disable itself with the regular
flymake-process-sentinel.
4. redefine flymake-start-syntax-check-process to unset the
query-on-exit flag for flymake processes. This allows emacs to
exit even if flymake is currently running.
5. provide advice to flymake-parse-line and
flymake-parse-err-lines, specifically set up for C#
buffers. This allows optimized searching for errors in csc.exe
output, and storing column numbers, for use in #6.
6. define advice to flymake-goto-line , to allow it to goto the
appropriate column for the error on a given line. This advice
looks in flymake-er-info, a list, and uses the heuristic that
the first error that matches the given line number, is the error
we want. This will break if there is more than one error on a
single line.
"
(flymake-log 2 "csharp-flymake-install")
(or csharp--flymake-has-been-installed
(progn
;; 1. add a C# entry to the flymake-allowed-file-name-masks
(let* ((key "\\.cs\\'")
(csharpentry (assoc key flymake-allowed-file-name-masks)))
(if csharpentry
(setcdr csharpentry '(csharp-flymake-init csharp-flymake-cleanup))
(add-to-list
'flymake-allowed-file-name-masks
(list key 'csharp-flymake-init 'csharp-flymake-cleanup))))
;; 2. add a C# entry to flymake-err-line-patterns
;;
;; The value of each entry is a list, (STRING IX1 IX2 IX3 IX4), where
;; STRING is the regex, and the other 4 values are indexes into the
;; regex captures for the filename, line, column, and error text,
;; respectively.
(add-to-list
'flymake-err-line-patterns
(list csharp-flymake-csc-error-pattern 1 2 3 4))
;; 3. override the definition for flymake-process-sentinel
;;
;; DPC - 2011 Feb 26
;; Redefining a function is a bit unusual, but I think it is necessary
;; to remove the check on process exit status. For VBC.exe, it gives
;; a 1 status when compile errors result. Likewise msbuild.exe. This
;; means flymake turns itself off, which we don't want. This really
;; ought to be tunable in flymake, but I guess no one asked for that
;; feature yet.
(defun flymake-process-sentinel (process event)
"Sentinel for syntax check buffers."
(when (memq (process-status process) '(signal exit))
(let* ((exit-status (process-exit-status process))
(command (process-command process))
(source-buffer (process-buffer process))
(cleanup-f (flymake-get-cleanup-function (buffer-file-name source-buffer))))
(flymake-log 2 "process %d exited with code %d"
(process-id process) exit-status)
(condition-case err
(progn
(flymake-log 3 "cleaning up using %s" cleanup-f)
(when (buffer-live-p source-buffer)
(with-current-buffer source-buffer
(funcall cleanup-f)))
(delete-process process)
(setq flymake-processes (delq process flymake-processes))
(when (buffer-live-p source-buffer)
(with-current-buffer source-buffer
(flymake-parse-residual)
;;(flymake-post-syntax-check exit-status command)
(flymake-post-syntax-check 0 command)
(setq flymake-is-running nil))))
(error
(let ((err-str (format "Error in process sentinel for buffer %s: %s"
source-buffer (error-message-string err))))
(flymake-log 0 err-str)
(with-current-buffer source-buffer
(setq flymake-is-running nil))))))))
;; 4. redefine this fn - the reason is to allow exit without query on
;; flymake processes. Not sure why this is not the default.
(defun flymake-start-syntax-check-process (cmd args dir)
"Start syntax check process."
(let* ((process nil))
(condition-case err
(progn
(when dir
(let ((default-directory dir))
(flymake-log 3 "starting process on dir %s" default-directory)))
(setq process (apply 'start-process "flymake-proc" (current-buffer) cmd args))
;; dino - exit without query on active flymake processes
(set-process-query-on-exit-flag process nil)
(set-process-sentinel process 'flymake-process-sentinel)
(set-process-filter process 'flymake-process-filter)
(push process flymake-processes)
(setq flymake-is-running t)
(setq flymake-last-change-time nil)
(setq flymake-check-start-time (flymake-float-time))
(flymake-report-status nil "*")
(flymake-log 2 "started process %d, command=%s, dir=%s"
(process-id process) (process-command process)
default-directory)
process)
(error
(let* ((err-str (format "Failed to launch syntax check process '%s' with args %s: %s"
cmd args (error-message-string err)))
(source-file-name buffer-file-name)
(cleanup-f (flymake-get-cleanup-function source-file-name)))
(flymake-log 0 err-str)
(funcall cleanup-f)
(flymake-report-fatal-status "PROCERR" err-str))))))
;; 5. define some advice for the error parsing
(defadvice flymake-parse-err-lines (before
csharp-flymake-parse-line-patch-1
activate compile)
(if (string-match "\\.[Cc][Ss]$" (file-relative-name buffer-file-name))
;; clear the auxiliary line information list, when a new parse
;; starts.
(setq csharp-flymake-aux-error-info nil)))
(defadvice flymake-parse-line (around
csharp-flymake-parse-line-patch-2
activate compile)
;; This advice will run in all buffers. Let's may sure we
;; actually execute the important stiff only when a C# buffer is active.
(if (string-match "\\.[Cc][Ss]$" (file-relative-name buffer-file-name))
(let (raw-file-name
e-text
result
(pattern (list csharp-flymake-csc-error-pattern 1 2 3 4))
(line-no 0)
(col-no 0)
(err-type "e"))
(if (string-match (car pattern) line)
(let* ((file-idx (nth 1 pattern))
(line-idx (nth 2 pattern))
(col-idx (nth 3 pattern))
(e-idx (nth 4 pattern)))
(flymake-log 3 "parse line: fx=%s lx=%s ex=%s"
file-idx line-idx e-idx)
(setq raw-file-name (if file-idx (match-string file-idx line) nil))
(setq line-no (if line-idx (string-to-number (match-string line-idx line)) 0))
(setq col-no (if col-idx (string-to-number (match-string col-idx line)) 0))
(setq e-text (if e-idx
(match-string e-idx line)
(flymake-patch-e-text (substring line (match-end 0)))))
(or e-text (setq e-text "<no error text>"))
(if (and e-text (string-match "^[wW]arning" e-text))
(setq err-type "w"))
(flymake-log 3 "parse line: fx=%s/%s lin=%s/%s col=%s/%s text=%s"
file-idx raw-file-name
line-idx line-no
col-idx (prin1-to-string col-no)
e-text)
;; add one entry to the list of auxiliary error information.
(add-to-list 'csharp-flymake-aux-error-info
(list line-no col-no))
(setq ad-return-value
(flymake-ler-make-ler raw-file-name line-no err-type e-text nil))
)))
;; else - not in a C# buffer
ad-do-it))
;; 6. finally, define some advice for the line navigation. It moves
;; to the proper column, given the line number containing the
;; error. It first calls the normal `flymake-goto-line', and assumes
;; that the result is that the cursor is on the line that contains the
;; error. At exit from that fn, the column is not important. This advice
;; sets the column.
(defadvice flymake-goto-line (around
csharp-flymake-goto-line-patch
activate compile)
;; This advice will run in all buffers. Let's may sure we
;; actually execute the important stuff only when a C# buffer is active.
ad-do-it
(if (string-match "\\.[Cc][Ss]$" (file-relative-name buffer-file-name))
(let* ((lno (ad-get-arg 0))
(epair (assoc lno csharp-flymake-aux-error-info)))
(if epair
(forward-char (- (cadr epair) (current-column) 1))))))
;; 7. finally, set the flag
(setq csharp--flymake-has-been-installed t))))
;; Need to temporarily turn off flymake while reverting.
;; There' some kind of race-condition where flymake is trying
;; to compile while the buffer is being changed, and that
;; causes flymake to choke.
(defadvice revert-buffer (around
csharp-advise-revert-buffer
activate compile)
(let ((is-flymake-enabled
(and (boundp 'flymake-mode)
flymake-mode)))
;; disable
(if is-flymake-enabled
(flymake-mode-off))
;; revert
ad-do-it
;; enable
(if is-flymake-enabled
(flymake-mode-on))))
;; ++++++++++++++++++++++
;; ========================================================================
;; moving
;; alist of regexps for various structures in a csharp source file.
(eval-and-compile
(defconst csharp--regexp-alist
(list
`(func-start
,(concat
"^[ \t\n\r\f\v]*" ;; leading whitespace
"\\("
"public\\(?: static\\)?\\|" ;; 1. access modifier
"private\\(?: static\\)?\\|"
"protected\\(?: internal\\)?\\(?: static\\)?\\|"
"static\\|"
"\\)"
"[ \t\n\r\f\v]+"
"\\(?:override[ \t\n\r\f\v]+\\)?" ;; optional
"\\([[:alpha:]_][^\t\(\n]+\\)" ;; 2. return type - possibly generic
"[ \t\n\r\f\v]+"
"\\([[:alpha:]_][[:alnum:]_]*\\)" ;; 3. name of func
"[ \t\n\r\f\v]*"
"\\(\([^\)]*\)\\)" ;; 4. params w/parens
"[ \t\n\r\f\v]*"
))
`(ctor-start
,(concat
"^[ \t\n\r\f\v]*" ;; leading whitespace
"\\("
"public\\|" ;; 1. access modifier
"private\\|"
"protected\\(?: internal\\)?\\|"
"static\\|"
"\\)"
"[ \t\n\r\f\v]+"
"\\([[:alpha:]_][[:alnum:]_]*\\)" ;; 2. name of ctor
"[ \t\n\r\f\v]*"
"\\(\([^\)]*\)\\)" ;; 3. parameter list (with parens)
"\\(" ;; 4. ctor dependency
"[ \t\n]*:[ \t\n]*" ;; colon
"\\(?:this\\|base\\)" ;; this or base
"[ \t\n\r\f\v]*"
"\\(?:\([^\)]*\)\\)" ;; parameter list (with parens)
"\\)?" ;; possibly
"[ \t\n\r\f\v]*"
))
`(using-stmt
,(concat
;;"^[ \t\n\r\f\v]*"
"\\(\\<using\\)"
"[ \t\n\r\f\v]+"
"\\(?:"
"\\([[:alpha:]_][[:alnum:]_]*\\)" ;; alias
"[ \t\n\r\f\v]*"
"="
"[ \t\n\r\f\v]*"
"\\)?"
"\\("
"\\(?:[A-Za-z_][[:alnum:]]*\\.\\)*"
"[A-Za-z_][[:alnum:]]*"
"\\)" ;; imported namespace
"[ \t\n\r\f\v]*"
";"
))
`(class-start
,(concat
"^[ \t]*" ;; leading whitespace
"\\("
"public\\(?: \\(?:static\\|sealed\\)\\)?[ \t]+\\|" ;; access modifiers
"internal\\(?: \\(?:static\\|sealed\\)\\)?[ \t]+\\|"
"static\\(?: internal\\)?[ \t]+\\|"
"sealed\\(?: internal\\)?[ \t]+\\|"
"static[ \t]+\\|"
"sealed[ \t]+\\|"
"\\)"
"\\(\\(?:partial[ \t]+\\)?class\\|struct\\)" ;; class/struct keyword
"[ \t]+"
"\\([[:alpha:]_][[:alnum:]]*\\)" ;; type name
"\\("
"[ \t\n]*:[ \t\n]*" ;; colon
"\\([[:alpha:]_][^\t\(\n]+\\)" ;; base / intf - poss generic
"\\("
"[ \t\n]*,[ \t\n]*"
"\\([[:alpha:]_][^\t\(\n]+\\)" ;; addl interface - poss generic
"\\)*"
"\\)?" ;; possibly
"[ \t\n\r\f\v]*"
))
`(genclass-start
,(concat
"^[ \t]*" ;; leading whitespace
"\\("
"public\\(?: \\(?:static\\|sealed\\)\\)?[ \t]+\\|" ;; access modifiers
"internal\\(?: \\(?:static\\|sealed\\)\\)?[ \t]+\\|"
"static\\(?: internal\\)?[ \t]+\\|"
"sealed\\(?: internal\\)?[ \t]+\\|"
"static[ \t]+\\|"
"sealed[ \t]+\\|"
"\\)"
"\\(\\(?:partial[ \t]+\\)?class\\|struct\\)" ;; class/struct keyword
"[ \t]+"
"\\([[:alpha:]_][[:alnum:]_<>, ]*\\)" ;; type name (generic)
"\\("
"[ \t\n]*:[ \t\n]*" ;; colon
"\\([[:alpha:]_][^\t\(\n]+\\)" ;; base / intf - poss generic
"\\("
"[ \t\n]*,[ \t\n]*"
"\\([[:alpha:]_][^\t\(\n]+\\)" ;; addl interface - poss generic
"\\)*"
"\\)?" ;; possibly
"[ \t\n\r\f\v]*"
))
`(enum-start
,(concat
"^[ \t\f\v]*" ;; leading whitespace
"\\("
"public[ \t]+enum\\|" ;; enum keyword
"enum"
"\\)"
"[ \t\n\r\f\v]+"
"\\([[:alpha:]_][[:alnum:]_]*\\)" ;; name of enum
"[ \t\n\r\f\v]*"
"\\(:[ \t\n\r\f\v]*"
"\\("
"sbyte\\|byte\\|short\\|ushort\\|int\\|uint\\|long\\|ulong"
"\\)"
"[ \t\n\r\f\v]*"
"\\)?" ;; possibly
"[ \t\n\r\f\v]*"
))
`(intf-start
,(concat
"^[ \t\f\v]*" ;; leading whitespace
"\\(?:"
"public\\|internal\\|" ;; access modifier
"\\)"
"[ \t\n\r\f\v]+"
"\\(interface\\)"
"[ \t\n\r\f\v]+"
"\\([[:alpha:]_][[:alnum:]_]*\\)" ;; name of interface
"[ \t\n\r\f\v]*"
))
`(prop-start
,(concat
"^[ \t\f\v]*" ;; leading whitespace
"\\("
"public\\|" ;; 1: access modifier
"private\\|"
"protected internal\\|"
"internal protected\\|"
"internal\\|"
"\\)"
"[ \t\n\r\f\v]+"
"\\([[:alpha:]_][^\t\(\n]+\\)" ;; 2: return type - possibly generic
"[ \t\n\r\f\v]+"
"\\("
"\\(?:[A-Za-z_][[:alnum:]_]*\\.\\)*" ;; possible prefix interface
"[[:alpha:]_][[:alnum:]_]*" ;; 3: name of prop
"\\)"
"[ \t\n\r\f\v]*"
))
`(indexer-start
,(concat
"^[ \t\f\v]*" ;; leading whitespace
"\\("
"public\\|" ;; 1: access modifier
"private\\|"
"protected internal\\|"
"internal protected\\|"
"internal\\|"
"\\)"
"[ \t\n\r\f\v]+"
"\\([[:alpha:]_][^\t\(\n]+\\)" ;; 2: return type - possibly generic
"[ \t\n\r\f\v]+"
"\\(this\\)" ;; 3: 'this' keyword
"[ \t\n\r\f\v]*"
"\\[" ;; open square bracket
"[ \t\n\r\f\v]*"
"\\([^\]]+\\)" ;; 4: index type
"[ \t\n\r\f\v]+"
"[[:alpha:]_][[:alnum:]_]*" ;; index name - a simple identifier
"\\]" ;; closing sq bracket
"[ \t\n\r\f\v]*"
))
`(namespace-start
,(concat
"^[ \t\f\v]*" ;; leading whitespace
"\\(namespace\\)"
"[ \t\n\r\f\v]+"
"\\("
"\\(?:[A-Za-z_][[:alnum:]_]*\\.\\)*" ;; name of namespace
"[A-Za-z_][[:alnum:]]*"
"\\)"
"[ \t\n\r\f\v]*"
))
)))
(defun csharp--regexp (symbol)
"Retrieves a regexp from the `csharp--regexp-alist' corresponding
to the given symbol.
"
(let ((elt (assoc symbol csharp--regexp-alist)))
(if elt (cadr elt) nil)))
(defun csharp-move-back-to-beginning-of-block ()
"Moves to the previous open curly.
"
(interactive)
(re-search-backward "{" (point-min) t))
(defun csharp--move-back-to-beginning-of-something (must-match &optional must-not-match)
"Moves back to the open-curly that defines the beginning of *something*,
defined by the given MUST-MATCH, a regexp which must match immediately
preceding the curly. If MUST-NOT-MATCH is non-nil, it is treated
as a regexp that must not match immediately preceding the curly.
This is a helper fn for `csharp-move-back-to-beginning-of-defun' and
`csharp-move-back-to-beginning-of-class'
"
(interactive)
(let (done
(found (point))
(need-to-backup (not (looking-at "{"))))
(while (not done)
(if need-to-backup
(setq found (csharp-move-back-to-beginning-of-block)))
(if found
(setq done (and (looking-back must-match)
(or (not must-not-match)
(not (looking-back must-not-match))))
need-to-backup t)
(setq done t)))
found))
(defun csharp-move-back-to-beginning-of-defun ()
"Moves back to the open-curly that defines the beginning of the
enclosing method. If point is outside a method, then move back to the
beginning of the prior method.
See also, `csharp-move-fwd-to-end-of-defun'.
"
(interactive)
(cond
((bobp) nil)
(t
(let (found)
(save-excursion
;; handle the case where we're at the top of a fn now.
;; if the user is asking to move back, then obviously
;; he wants to move back to a *prior* defun.
(if (and (looking-at "{")
(looking-back (csharp--regexp 'func-start))
(not (looking-back (csharp--regexp 'namespace-start))))
(forward-char -1))
;; now do the real work
(setq found (csharp--move-back-to-beginning-of-something
(csharp--regexp 'func-start)
(csharp--regexp 'namespace-start))))
(if found
(goto-char found))))))
(defun csharp--on-defun-close-curly-p ()
"return t when point is on the close-curly of a method."
(and (looking-at "}")
(save-excursion
(and
(progn (forward-char) (forward-sexp -1) t)
(not (looking-back (csharp--regexp 'class-start)))
(not (looking-back (csharp--regexp 'namespace-start)))
(looking-back (csharp--regexp 'func-start))))))
(defun csharp--on-ctor-close-curly-p ()
"return t when point is on the close-curly of a constructor."
(and (looking-at "}")
(save-excursion
(and
(progn (forward-char) (forward-sexp -1) t)
(looking-back (csharp--regexp 'ctor-start))))))
(defun csharp--on-class-close-curly-p ()
"return t when point is on the close-curly of a class or struct."
(and (looking-at "}")
(save-excursion
(and
(progn (forward-char) (forward-sexp -1) t)
(not (looking-back (csharp--regexp 'namespace-start)))
(looking-back (csharp--regexp 'class-start))))))
(defun csharp--on-intf-close-curly-p ()
"return t when point is on the close-curly of an interface."
(and (looking-at "}")
(save-excursion
(and
(progn (forward-char) (forward-sexp -1) t)
(looking-back (csharp--regexp 'intf-start))))))
(defun csharp--on-enum-close-curly-p ()
"return t when point is on the close-curly of an enum."
(and (looking-at "}")
(save-excursion
(and
(progn (forward-char) (forward-sexp -1) t)
(looking-back (csharp--regexp 'enum-start))))))
(defun csharp--on-namespace-close-curly-p ()
"return t when point is on the close-curly of a namespace."
(and (looking-at "}")
(save-excursion
(and
(progn (forward-char) (forward-sexp -1) t)
(looking-back (csharp--regexp 'namespace-start))))))
(defun csharp--on-defun-open-curly-p ()
"return t when point is on the open-curly of a method."
(and (looking-at "{")
(not (looking-back (csharp--regexp 'class-start)))
(not (looking-back (csharp--regexp 'namespace-start)))
(looking-back (csharp--regexp 'func-start))))
(defun csharp--on-class-open-curly-p ()
"return t when point is on the open-curly of a class."
(and (looking-at "{")
(not (looking-back (csharp--regexp 'namespace-start)))
(looking-back (csharp--regexp 'class-start))))
(defun csharp--on-genclass-open-curly-p ()
"return t when point is on the open-curly of a generic class."
(and (looking-at "{")
(looking-back (csharp--regexp 'genclass-start))))
(defun csharp--on-namespace-open-curly-p ()
"return t when point is on the open-curly of a namespace."
(and (looking-at "{")
(looking-back (csharp--regexp 'namespace-start))))
(defun csharp--on-ctor-open-curly-p ()
"return t when point is on the open-curly of a ctor."
(and (looking-at "{")
(looking-back (csharp--regexp 'ctor-start))))
(defun csharp--on-intf-open-curly-p ()
"return t when point is on the open-curly of a interface."
(and (looking-at "{")
(looking-back (csharp--regexp 'intf-start))))
(defun csharp--on-prop-open-curly-p ()
"return t when point is on the open-curly of a property."
(and (looking-at "{")
(not (looking-back (csharp--regexp 'class-start)))
(looking-back (csharp--regexp 'prop-start))))
(defun csharp--on-indexer-open-curly-p ()
"return t when point is on the open-curly of a C# indexer."
(and (looking-at "{")
(looking-back (csharp--regexp 'indexer-start))))
(defun csharp--on-enum-open-curly-p ()
"return t when point is on the open-curly of a interface."
(and (looking-at "{")
(looking-back (csharp--regexp 'enum-start))))
(defun csharp-move-fwd-to-end-of-defun ()
"Moves forward to the close-curly that defines the end of the enclosing
method. If point is outside a method, moves forward to the close-curly that
defines the end of the next method.
See also, `csharp-move-back-to-beginning-of-defun'.
"
(interactive)
(let ((really-move
(lambda ()
(let ((start (point))
dest-char)
(save-excursion
(csharp-move-back-to-beginning-of-defun)
(forward-sexp)
(if (>= (point) start)
(setq dest-char (point))))
(if dest-char
(goto-char dest-char))))))
(cond
;; case 1: end of buffer. do nothing.
((eobp) nil)
;; case 2: we're at the top of a class
((csharp--on-class-open-curly-p)
(let (found-it)
(save-excursion
(forward-char 1) ;; get off the curly
(setq found-it
(and ;; look for next open curly
(re-search-forward "{" (point-max) t)
(funcall really-move))))
(if found-it
(goto-char found-it))))
;; case 3: we're at the top of a fn now.
((csharp--on-defun-open-curly-p)
(forward-sexp))
;; case 4: we're at the bottom of a fn now (possibly
;; after just calling csharp-move-fwd-to-end-of-defun.
((and (looking-back "}")
(save-excursion
(forward-sexp -1)
(csharp--on-defun-open-curly-p)))
(let (found-it)
(save-excursion
(setq found-it
(and (re-search-forward "{" (point-max) t)
(funcall really-move))))
(if found-it
(goto-char found-it))))
;; case 5: we're at none of those places.
(t
(funcall really-move)))))
(defun csharp-move-back-to-beginning-of-class ()
"Moves back to the open-curly that defines the beginning of the
enclosing class. If point is outside a class, then move back to the
beginning of the prior class.
See also, `csharp-move-fwd-to-end-of-defun'.
"
(interactive)
(cond
((bobp) nil)
(t
(let (found)
(save-excursion
;; handle the case where we're at the top of a class now.
;; if the user is asking to move back, then obviously
;; he wants to move back to a *prior* defun.
(if (and (looking-at "{")
(looking-back (csharp--regexp 'class-start))
(not (looking-back (csharp--regexp 'namespace-start))))
(forward-char -1))
;; now do the real work
(setq found (csharp--move-back-to-beginning-of-something
(csharp--regexp 'class-start)
(csharp--regexp 'namespace-start))))
(if found
(goto-char found))))))
(defun csharp-move-fwd-to-end-of-class ()
"Moves forward to the close-curly that defines the end of the
enclosing class.
See also, `csharp-move-back-to-beginning-of-class'.
"
(interactive)
(let ((start (point))
dest-char)
(save-excursion
(csharp-move-back-to-beginning-of-class)
(forward-sexp)
(if (>= (point) start)
(setq dest-char (point))))
(if dest-char
(goto-char dest-char))))
(defun csharp-move-back-to-beginning-of-namespace ()
"Moves back to the open-curly that defines the beginning of the
enclosing namespace. If point is outside a namespace, then move back
to the beginning of the prior namespace.
"
(interactive)
(cond
((bobp) nil)
(t
(let (found)
(save-excursion
;; handle the case where we're at the top of a namespace now.
;; if the user is asking to move back, then obviously
;; he wants to move back to a *prior* defun.
(if (and (looking-at "{")
(looking-back (csharp--regexp 'namespace-start)))
(forward-char -1))
;; now do the real work
(setq found (csharp--move-back-to-beginning-of-something
(csharp--regexp 'namespace-start))))
(if found
(goto-char found))))))
;; moving
;; ========================================================================
;; ==================================================================
;;; imenu stuff
;; define some advice for menu construction.
;; The way imenu constructs menus from the index alist, in
;; `imenu--split-menu', is ... ah ... perplexing. If the csharp
;; create-index fn returns an ordered menu, and the imenu "sort" fn has
;; been set to nil, imenu still sorts the menu, according to the rule
;; that all submenus must appear at the top of any menu. Why? I don't
;; know. This advice disables that weirdness in C# buffers.
(defadvice imenu--split-menu (around
csharp--imenu-split-menu-patch
activate compile)
;; This advice will run in all buffers. Let's may sure we
;; actually execute the important bits only when a C# buffer is active.
(if (and (string-match "\\.[Cc][Ss]$" (file-relative-name buffer-file-name))
(boundp 'csharp-want-imenu)
csharp-want-imenu)
(let ((menulist (copy-sequence menulist))
keep-at-top)
(if (memq imenu--rescan-item menulist)
(setq keep-at-top (list imenu--rescan-item)
menulist (delq imenu--rescan-item menulist)))
;; This is the part from the original imenu code
;; that puts submenus at the top. huh? why?
;; --------------------------------------------
;; (setq tail menulist)
;; (dolist (item tail)
;; (when (imenu--subalist-p item)
;; (push item keep-at-top)
;; (setq menulist (delq item menulist))))
(if imenu-sort-function
(setq menulist (sort menulist imenu-sort-function)))
(if (> (length menulist) imenu-max-items)
(setq menulist
(mapcar
(lambda (menu)
(cons (format "From: %s" (caar menu)) menu))
(imenu--split menulist imenu-max-items))))
(setq ad-return-value
(cons title
(nconc (nreverse keep-at-top) menulist))))
;; else
ad-do-it))
;;
;; I used this to examine the performance of the imenu scanning.
;; It's not necessary during normal operation.
;;
;; (defun csharp-imenu-begin-profile ()
;; "turn on profiling"
;; (interactive)
;; (let ((fns '(csharp--on-class-open-curly-p
;; csharp--on-namespace-open-curly-p
;; csharp--on-ctor-open-curly-p
;; csharp--on-enum-open-curly-p
;; csharp--on-intf-open-curly-p
;; csharp--on-prop-open-curly-p
;; csharp--on-indexer-open-curly-p
;; csharp--on-defun-open-curly-p
;; csharp--imenu-create-index-helper
;; looking-back
;; looking-at)))
;; (if (fboundp 'elp-reset-all)
;; (elp-reset-all))
;; (mapc 'elp-instrument-function fns)))
(defun csharp--imenu-remove-param-names-from-paramlist (s)
"The input string S is a parameter list, of the form seen in a
C# method. TYPE1 NAME1 [, TYPE2 NAME2 ...]
This fn returns a string of the form TYPE1 [, TYPE2...]
Upon entry, it's assumed that the parens included in S.
"
(if (string= s "()")
s
(save-match-data
(let* (new
(state 0) ;; 0 => ws, 1=>slurping param...
c
cs
nesting
need-type
ix2
(s2 (substring s 1 -1))
(len (length s2))
(i (1- len)))
(while (> i 0)
(setq c (aref s2 i) ;; current character
cs (char-to-string c)) ;; s.t. as a string
(cond
;; backing over whitespace "after" the param
((= state 0)
(cond
;; more ws
((string-match "[ \t\f\v\n\r]" cs)
t)
;; a legal char for an identifier
((string-match "[A-Za-z_0-9]" cs)
(setq state 1))
(t
(error "unexpected char (A)"))))
;; slurping param name
((= state 1)
(cond
;; ws signifies the end of the param
((string-match "[ \t\f\v\n\r]" cs)
(setq state 2))
;; a legal char for an identifier
((string-match "[A-Za-z_0-9]" cs)
t)
(t
(error "unexpected char (B)"))))
;; ws between typespec and param name
((= state 2)
(cond
((string-match "[ \t\f\v\n\r]" cs)
t)
;; non-ws indicates the type spec is beginning
(t
(incf i)
(setq state 3
need-type nil
nesting 0
ix2 i))))
;; slurping type
((= state 3)
(cond
((= ?> c) (incf nesting))
((= ?< c)
(decf nesting)
(setq need-type t))
;; ws or comma maybe signifies the end of the typespec
((string-match "[ \t\f\v\n\r,]" cs)
(if (and (= nesting 0) (not need-type))
(progn
(setq new (cons (substring s2 (1+ i) ix2) new))
(setq state
(if (= c ?,) 0 4)))))
((string-match "[A-Za-z_0-9]" cs)
(setq need-type nil))))
;; awaiting comma or b-o-s
((= state 4)
(cond
((= ?, c)
(if (= nesting 0)
(setq state 0)))
((string-match "[ \t\f\v\n\r]" cs)
t)
((= 93 c) (incf nesting)) ;; sq brack
((= 91 c) ;; open sq brack
(decf nesting))
;; handle this (extension methods), out, ref, params
((and (>= i 5)
(string= (substring s2 (- i 5) (1+ i)) "params"))
(setf (car new) (concat "params " (car new)))
(setq i (- i 5)))
((and (>= i 3)
(string= (substring s2 (- i 3) (1+ i)) "this"))
(setf (car new) (concat "this " (car new)))
(setq i (- i 3)))
((and (>= i 2)
(string= (substring s2 (- i 2) (1+ i)) "ref"))
(setf (car new) (concat "ref " (car new)))
(setq i (- i 2)))
((and (>= i 2)
(string= (substring s2 (- i 2) (1+ i)) "out"))
(setf (car new) (concat "out " (car new)))
(setq i (- i 2)))
(t
(error "unexpected char (C)"))))
)
(decf i))
(if (and (= state 3) (= nesting 0))
(setq new (cons (substring s2 i ix2) new)))
(concat "("
(if new
(mapconcat 'identity new ", ")
"")
")")))))
(defun csharp--imenu-item-basic-comparer (a b)
"Compares the car of each element, assumed to be a string."
(string-lessp (car a) (car b)))
(defun csharp--imenu-get-method-name-from-sig (sig)
"Extract a method name with its parameter list from a method
signature, SIG. This is used to aid in sorting methods by name,
and secondarily by parameter list.
For this input:
private Dict<String, int> DoSomething(int, string)
...the output is:
DoSomething(int, string)
"
(let* (c
result
(state 0)
(len (length sig))
(i (1- len)))
(while (> i 0)
(setq c (aref sig i))
(cond
((and (= state 0) (= c 40))
(setq state 1))
((and (= state 1) (or (= c 9) (= c 32)))
(setq result (substring sig (1+ i))
i 0)))
(decf i))
result))
(defun csharp--imenu-item-method-name-comparer (a b)
"Compares the method names in the respective cars of each element.
The car of each element is assumed to be a string with multiple
tokens in it, representing a method signature, including access
modifier, return type, and parameter list (surrounded by parens).
If the method takes no params, then it's just an empty pair of
parens.
This fn extracts the method name and param list from that
signature and compares *that*.
"
(let ((methoda (csharp--imenu-get-method-name-from-sig (car a)))
(methodb (csharp--imenu-get-method-name-from-sig (car b))))
;;(csharp-log -1 "compare '%s' <> '%s'" methoda methodb)
(string-lessp methoda methodb)))
(defun csharp--imenu-create-index-helper (&optional parent-ns indent-level
consider-usings consider-namespaces)
"Helper fn for `csharp-imenu-create-index'.
Scans a possibly narrowed section of a c# buffer. It finds
namespaces, classes, structs, enums, interfaces, and methods
within classes and structs.
The way it works: it looks for an open-curly. If the open-curly
is a namespace or a class, it narrows to whatever is inside the
curlies, then recurses.
Otherwise (the open-curly is neither of those things), this fn
tries to recognize the open-curly as the beginning of an enum,
method, or interface.
If it succeeds, then a menu item is created for the thing. Then
it jumps to the matching close-curly, and continues. Stop when no
more open-curlies are found.
"
;; A C# module consists of zero of more explicitly denoted (and
;; possibly nested) namespaces. In the absence of an
;; explicitly-denoted namespace, the global namespace is implicitly
;; applied. Within each namespace there can be zero or more
;; "container" things - like class, struct, or interface; each with
;; zero or more indexable items - like methods, constructors.
;; and so on.
;; This fn parses the module and indexes those items, creating a
;; hierarchically organized list to describe them. Each container
;; (ns/class/struct/etc) is represented on a separate submenu.
;; It works like this:
;; (start at the top of the module)
;;
;; 1. look for a using clause
;; yes - insert an item in the menu; move past all using clauses.
;;
;; 2. go to next open curly
;;
;; 2. beginning of a container? (a class or namespace)
;;
;; yes - narrow, and recurse
;;
;; no - create a menu item for the thing, whatever it is. add to
;; the submenu. Go to the end of the thing (to the matching
;; close curly) then goto step 1.
;;
(let (container-name
(pos-last-curly -1)
this-flavor
this-item
this-menu
found-usings
done)
(while (not done)
;; move to the next thing
(c-forward-syntactic-ws)
(cond
((and consider-usings
(re-search-forward (csharp--regexp 'using-stmt) (point-max) t))
(goto-char (match-beginning 1))
(setq found-usings t
done nil))
((re-search-forward "{" (point-max) t)
(if (= pos-last-curly (point))
(progn
;;(csharp-log -1 "imenu: No advance? quitting (%d)" (point))
(setq done t)) ;; haven't advanced- likely a loop
(setq pos-last-curly (point))
(let ((literal (csharp-in-literal)))
;; skip over comments?
(cond
((memq literal '(c c++))
(while (memq literal '(c c++))
(end-of-line)
(forward-char 1)
(setq literal (csharp-in-literal)))
(if (re-search-forward "{" (point-max) t)
(forward-char -1)
;;(csharp-log -1 "imenu: No more curlies (A) (%d)" (point))
(setq done t)))
((eq literal 'string)
(if (re-search-forward "\"" (point-max) t)
(forward-char 1)
;;(csharp-log -1 "imenu: Never-ending string? posn(%d)" (point))
(setq done t)))
(t
(forward-char -1)))))) ;; backup onto the curly
(t
;;(csharp-log -1 "imenu: No more curlies (B) posn(%d)" (point))
(setq done t)))
(if (not done)
(cond
;; case 1: open curly for an array initializer
((looking-back "\\[\\][ \t\n\r]*")
(forward-sexp 1))
;; case 2: just jumped over a string
((looking-back "\"")
(forward-char 1))
;; case 3: at the head of a block of using statements
(found-usings
(setq found-usings nil
consider-usings nil) ;; only one batch
(let ((first-using (match-beginning 1))
(count 0)
marquis
;; don't search beyond next open curly
(limit (1-
(save-excursion
(re-search-forward "{" (point-max) t)))))
;; count the using statements
(while (re-search-forward (csharp--regexp 'using-stmt) limit t)
(incf count))
(setq marquis (if (eq count 1) "using (1)"
(format "usings (%d)" count)))
(push (cons marquis first-using) this-menu)))
;; case 4: an interface or enum inside the container
;; (must come before class / namespace )
((or (csharp--on-intf-open-curly-p)
(csharp--on-enum-open-curly-p))
(setq consider-namespaces nil
consider-usings nil
this-menu
(append this-menu
(list
(cons (concat
(match-string-no-properties 1) ;; thing flavor
" "
(match-string-no-properties 2)) ;; intf name
(match-beginning 1)))))
(forward-sexp 1))
;; case 5: at the start of a container (class, namespace)
((or (and consider-namespaces (csharp--on-namespace-open-curly-p))
(csharp--on-class-open-curly-p)
(csharp--on-genclass-open-curly-p))
;; produce a fully-qualified name for this thing
(if (string= (match-string-no-properties 1) "namespace")
(setq this-flavor (match-string-no-properties 1)
this-item (match-string-no-properties 2))
(setq this-flavor (match-string-no-properties 2)
this-item (match-string-no-properties 3)
consider-usings nil
consider-namespaces nil))
(setq container-name (if parent-ns
(concat parent-ns "." this-item)
this-item))
;; create a submenu
(let (submenu
(top (match-beginning 1))
(open-curly (point))
(close-curly (save-excursion
(forward-sexp 1)
(point))))
(setq submenu
(list
(concat this-flavor " " container-name)
(cons "(top)" top)))
;; find all contained items
(save-restriction
(narrow-to-region (1+ open-curly) (1- close-curly))
(let* ((yok (string= this-flavor "namespace"))
(child-menu
(csharp--imenu-create-index-helper container-name
(concat indent-level " ")
yok yok)))
(if child-menu
(setq submenu
(append submenu
(sort child-menu
'csharp--imenu-item-basic-comparer))))))
(setq submenu
(append submenu
(list (cons "(bottom)" close-curly))))
(setq this-menu
(append this-menu (list submenu)))
(goto-char close-curly)))
;; case 6: a property
((csharp--on-prop-open-curly-p)
(setq consider-namespaces nil
consider-usings nil
this-menu
(append this-menu
(list
(cons (concat
"prop "
(match-string-no-properties 3)) ;; prop name
(match-beginning 1)))))
(forward-sexp 1))
;; case 7: an indexer
((csharp--on-indexer-open-curly-p)
(setq consider-namespaces nil
consider-usings nil
this-menu
(append this-menu
(list
(cons (concat
"indexer "
(match-string-no-properties 4)) ;; index type
(match-beginning 1)))))
(forward-sexp 1))
;; case 8: a constructor inside the container
((csharp--on-ctor-open-curly-p)
(setq consider-namespaces nil
consider-usings nil
this-menu
(append this-menu
(list
(cons (concat
"ctor "
(match-string-no-properties 2) ;; ctor name
(csharp--imenu-remove-param-names-from-paramlist
(match-string-no-properties 3))) ;; ctor params
(match-beginning 1)))))
(forward-sexp 1))
;; case 9: a method inside the container
((csharp--on-defun-open-curly-p)
(setq consider-namespaces nil
consider-usings nil
this-menu
(append this-menu
(list
(cons (concat
"method "
(match-string-no-properties 2) ;; return type
" "
(match-string-no-properties 3) ;; func name
(csharp--imenu-remove-param-names-from-paramlist
(match-string-no-properties 4))) ;; fn params
(match-beginning 1)))))
(forward-sexp 1))
;; case 10: unknown open curly - just jump over it.
((looking-at "{")
(forward-sexp 1))
;; case 11: none of the above. shouldn't happen?
(t
(forward-char 1)))))
this-menu))
;; =======================================================
;; DPC Thu, 19 May 2011 11:25
;; There are two challenges with the imenu support: generating the
;; index, and generating a reasonable display for the index. The index
;; generation is pretty straightforward: use regexi to locate
;; interesting stuff in the buffer.
;;
;; The menu generation is a little trickier. Long lists of methods
;; mixed with properties and interfaces (etc) will be displayed in the
;; menu but will look Very Bad. Better to organize the menu into
;; submenus, organized primarily by category. Also the menus should be
;; sorted, for ease of human scanning. The next section of logic is
;; designed to do the stuff for the menu generation.
(defcustom csharp-imenu-max-similar-items-before-extraction 6
"The maximum number of things of a particular
category (constructor, property, method, etc) that will be
separely displayed on an imenu without factoring them into a
separate submenu.
For example, if a module has 3 consructors, 5 methods, and 7
properties, and the value of this variable is 4, then upon
refactoring, the constructors will remain in the toplevel imenu
and the methods and properties will each get their own
category-specific submenu.
See also `csharp-imenu-min-size-for-sub-submenu'.
For more information on how csharp-mode uses imenu,
see `csharp-want-imenu', and `csharp-mode'.
"
:type 'integer
:group 'csharp)
(defcustom csharp-imenu-min-size-for-sub-submenu 18
"The minimum number of imenu items of a particular
category (constructor, property, method, etc) that will be
broken out into sub-submenus.
For example, if a module has 28 properties, then the properties will
be placed in a submenu, and then that submenu with be further divided
into smaller submenus.
See also `csharp-imenu-max-similar-items-before-extraction'
For more information on how csharp-mode uses imenu,
see `csharp-want-imenu', and `csharp-mode'.
"
:type 'integer
:group 'csharp)
(defun csharp--first-word (s)
"gets the first word from the given string.
It had better be a string!"
(car (split-string s nil t)))
(defun csharp--make-plural (s)
"make a word plural. For use within the generated imenu."
(cond
((string= s "prop") "properties")
((string= s "class") "classes")
((string= s "ctor") "constructors")
(t (concat s "s"))))
(defun csharp--imenu-counts (list)
"Returns an alist, each item is a cons cell where the car is a
unique first substring of an element of LIST, and the cdr is the
number of occurrences of that substring in elements in the
list.
For a complicated imenu generated for a large C# module, the result of
this fn will be something like this:
((\"(top)\" . 1)
(\"properties\" . 38)
(\"methods\" . 12)
(\"constructors\" . 7)
(\"(bottom)\" . 1))
"
(flet ((helper (list new)
(if (null list) new
(let* ((elt (car list))
(topic (csharp--make-plural (csharp--first-word (car elt))))
(xelt (assoc topic new)))
(helper (cdr list)
(if xelt
(progn (incf (cdr xelt)) new)
(cons (cons topic 1) new)))))))
(nreverse (helper list nil))))
(defun csharp--imenu-get-submenu-size (n)
"Gets the preferred size of submenus given N, the size of the
flat, unparceled menu.
Suppose there are 50 properties in a given C# module. This fn maps
from that number, to the maximum size of the submenus into which the
large set of properties should be broken.
Currently the submenu size for 50 is 12. To change this, change
the lookup table.
The reason it's a lookup table and not a simple arithmetic
function: I think it would look silly to have 2 submenus each
with 24 items. Sixteen or 18 items on a submenu seems fine when
you're working through 120 items total. But if you have only 28
items, better to have 3 submenus with 10 and 9 items each. So
it's not a linear function. That's what this lookup tries to do.
"
(let ((size-pairs '((100 . 22)
(80 . 20)
(60 . 18)
(40 . 15)
(30 . 14)
(24 . 11)
(0 . 9)))
elt
(r 0))
(while (and size-pairs (eq r 0))
(setq elt (car size-pairs))
(if (> n (car elt))
(setq r (cdr elt)))
(setq size-pairs (cdr size-pairs)))
r))
(defun csharp--imenu-remove-category-names (menu-list)
"Input is a list, each element is (LABEL . LOCATION). This fn
returns a modified list, with the first word - the category name
- removed from each label.
"
(mapcar (lambda (elt)
(let ((tokens (split-string (car elt) "[ \t]" t)))
(cons (mapconcat 'identity (cdr tokens) " ")
(cdr elt))))
menu-list))
(defun string-indexof (s c)
"Returns the index of the first occurrence of character C in string S.
Returns nil if not found.
See also, `string-lastindexof'
"
(let ((len (length s))
(i 0) ix c2)
(while (and (< i len) (not ix))
(setq c2 (aref s i))
(if (= c c2)
(setq ix i))
(incf i))
ix))
(defun string-lastindexof (s c)
"Returns the index of the last occurrence of character C in string S.
Returns nil if not found.
See also, `string-indexof'
"
(let ((i (length s))
ix c2)
(while (and (>= i 0) (not ix))
(setq c2 (aref s i))
(if (= c c2)
(setq ix i))
(decf i))
ix))
(defun csharp--imenu-submenu-label (sig flavor)
"generate a submenu label from the given signature, SIG.
The sig is a method signature, property type-and-name,
constructor, and so on, indicated by FLAVOR.
This fn returns a simple name that can be used in the label for a
break out submenu.
"
(if (string= flavor "method")
(let ((method-name (csharp--imenu-get-method-name-from-sig sig)))
(substring method-name 0 (string-indexof method-name 40)))
(substring sig (1+ (string-lastindexof sig 32)))))
(defun csharp--imenu-break-one-menu-into-submenus (menu-list)
"Parcels a flat list MENU-LIST up into smaller sublists. It tries
to balance the number of sublists and the size of each sublist.
The max size of any sublist will be about 20 (arbitrary) and the
min size will be 7 or so. See `csharp--imenu-get-submenu-size'
for how this is done.
It does this destructively, using `nbutlast'.
Returns a new list, containing sublists.
"
(let ((len (length menu-list))
(counts (csharp--imenu-counts menu-list)))
(cond
;; a small number, and all the same flavor
((and (< len csharp-imenu-min-size-for-sub-submenu) (= (length counts) 1))
(csharp--imenu-remove-category-names
(sort menu-list
(if (string= (caar counts) "methods")
'csharp--imenu-item-method-name-comparer
'csharp--imenu-item-basic-comparer))))
;; is the length already pretty short?
((< len csharp-imenu-min-size-for-sub-submenu)
menu-list)
((/= (length counts) 1)
menu-list)
(t
(let* ((lst (sort menu-list
(if (string= (caar counts) "methods")
'csharp--imenu-item-method-name-comparer
'csharp--imenu-item-basic-comparer)))
new
(sz (csharp--imenu-get-submenu-size len)) ;; goal max size of sublist
(n (ceiling (/ (* 1.0 len) sz))) ;; total number of sublists
(adj-sz (ceiling (/ (* 1.0 len) n))) ;; maybe a little less than sz
(nsmall (mod (- adj-sz (mod len adj-sz)) adj-sz)) ;; num of (n-1) lists
(i 0)
(base-name (csharp--first-word (caar lst)))
label
chunksz
this-chunk)
(while lst
(setq chunksz (if (> nsmall i) (1- adj-sz) adj-sz)
this-chunk (csharp--imenu-remove-category-names
(nthcdr (- len chunksz) lst))
lst (nbutlast lst chunksz)
;;label (format "%s %d" plural-name (- n i))
label (concat "from " (csharp--imenu-submenu-label (caar this-chunk) base-name))
new (cons (cons label this-chunk) new)
len (- len chunksz))
(incf i))
new)))))
(defun csharp--imenu-break-into-submenus (menu-list)
"For an imenu menu-list with category-based submenus,
possibly break a submenu into smaller sublists, based on size.
"
(mapcar (lambda (elt)
(if (imenu--subalist-p elt)
(cons (car elt)
(csharp--imenu-break-one-menu-into-submenus (cdr elt)))
elt))
menu-list))
(defun csharp--imenu-reorg-alist-intelligently (menu-alist)
"Accepts an imenu alist. Returns an alist, reorganized.
Things get sorted, factored out into category submenus,
and split into multiple submenus, where conditions warrant.
For example, suppose this imenu alist is generated from a scan:
((\"usings (4)\" . 1538)
(\"namespace Ionic.Zip\"
(\"(top)\" . 1651)
(\"partial class Ionic.Zip.ZipFile\"
(\"(top)\" . 5473)
(\"prop FullScan\" . 8036)
...
(\"prop Comment\" . 21118)
(\"prop Verbose\" . 32278)
(\"method override String ToString\" . 96577)
(\"method internal void NotifyEntryChanged\" . 97608)
....
(\"method internal void Reset\" . 98231)
(\"ctor ZipFile\" . 103598)
...
(\"ctor ZipFile\" . 109723)
(\"ctor ZipFile\" . 116487)
(\"indexer int\" . 121232)
(\"indexer String\" . 124933)
(\"(bottom)\" . 149777))
(\"public enum Zip64Option\" . 153839)
(\"enum AddOrUpdateAction\" . 154815)
(\"(bottom)\" . 154893)))
This is displayed as a toplevel menu with 2 items; the namespace
menu has 5 items (top, bottom, the 2 enums, and the class). The
class menu has 93 items. It needs to be reorganized to be more usable.
After transformation of the alist through this fn, the result is:
((\"usings (4)\" . 1538)
(\"namespace Ionic.Zip\"
(\"(top)\" . 1651)
(\"partial class Ionic.Zip.ZipFile\"
(\"(top)\" . 5473)
(\"properties\"
(\"WriteStream\" . 146489)
(\"Count\" . 133827)
....
(\"BufferSize\" . 12837)
(\"FullScan\" . 8036))
(\"methods\"
(\"virtual void Dispose\" . 144389)
(\"void RemoveEntry\" . 141027)
....
(\"method override String ToString\" . 96577)
(\"method bool ContainsEntry\" . 32517))
(\"constructors\"
(\"ZipFile\" . 116487)
....
(\"ZipFile\" . 105698)
(\"ZipFile\" . 103598))
(\"indexer int\" . 121232)
(\"indexer String\" . 124933)
(\"(bottom)\" . 149777))
(\"public enum Zip64Option\" . 153839)
(\"enum AddOrUpdateAction\" . 154815)
(\"(bottom)\" . 154893)))
All menus are the same except the class menu, which has been
organized into subtopics, each of which gets its own cascaded
submenu. If the submenu itself holds more than
`csharp-imenu-max-similar-items-before-extraction' items that are
all the same flavor (properties, methods, etc), thos get split
out into multiple submenus.
"
(let ((counts (csharp--imenu-counts menu-alist)))
(flet ((helper
(list new)
(if (null list)
new
(let* ((elt (car list))
(topic (csharp--make-plural (csharp--first-word (car elt))))
(xelt (assoc topic new)))
(helper
(cdr list)
(if xelt
(progn
(rplacd xelt (cons elt (cdr xelt)))
new)
(cons
(cond
((> (cdr (assoc topic counts))
csharp-imenu-max-similar-items-before-extraction)
(cons topic (list elt)))
((imenu--subalist-p elt)
(cons (car elt)
(csharp--imenu-reorg-alist-intelligently (cdr elt))))
(t
elt))
new)))))))
(csharp--imenu-break-into-submenus
(nreverse (helper menu-alist nil))))))
(defun csharp-imenu-create-index ()
"This function is called by imenu to create an index for the
current C# buffer, conforming to the format specified in
`imenu--index-alist' .
See `imenu-create-index-function' for background information.
To produce the index, which lists the classes, functions,
methods, and properties for the current buffer, this function
scans the entire buffer.
This can take a long time for a large buffer. The scan uses
regular expressions that attempt to match on the general-case C#
syntax, for classes and functions, generic types, base-classes,
implemented interfaces, and so on. This can be time-consuming.
For a large source file, say 160k, it can take 10 seconds or more.
The UI hangs during the scan.
imenu calls this fn when it feels like it, I suppose when it
thinks the buffer has been updated. The user can also kick it off
explicitly by selecting *Rescan* from the imenu menu.
After generating the hierarchical list of props, methods,
interfaces, classes, and namespaces, csharp-mode re-organizes the
list as appropriate:
- it extracts sets of like items into submenus. All properties
will be placed on a submenu. See
`csharp-imenu-max-similar-items-before-extraction' for a way
to tune this.
- it converts those submenus into sub-submenus, if there are more than
`csharp-imenu-min-size-for-sub-submenu' items.
- it sorts each set of items on the outermost menus lexicographically.
The result of these transformations is what is provided to imenu
to generate the visible menus. Just FYI - the reorganization of
the scan results is much much faster than the actual generation
of the scan results. If you're looking to save time, the re-org
logic is not where the cost is.
imenu itself likes to sort the menus. See `imenu--split-menu' and
also `csharp--imenu-split-menu-patch', which is advice that
attempts to disable the weird re-jiggering that imenu performs.
"
;; I think widen/narrow causes the buffer to be marked as
;; modified. This is a bit surprising, but I have no other
;; explanation for the source of the problem.
;; So I use `c-save-buffer-state' so that the buffer is not
;; marked modified when the scan completes.
(c-save-buffer-state ()
(save-excursion
(save-restriction
(widen)
(goto-char (point-min))
(let ((index-alist
(csharp--imenu-create-index-helper nil "" t t)))
(csharp--imenu-reorg-alist-intelligently index-alist)
;;index-alist
;; What follows is No longer used.
;; =======================================================
;; If the index menu contains exactly one element, and it is
;; a namespace menu, then remove it. This simplifies the
;; menu, and results in no loss of information: all types
;; get fully-qualified names anyway. This will probably
;; cover the majority of cases; often a C# source module
;; defines either one class, or a set of related classes
;; inside a single namespace.
;; To remove that namespace, we need to prune & graft the tree.
;; Remove the ns hierarchy level, but also remove the 1st and
;; last elements in the sub-menu, which represent the top and
;; bottom of the namespace.
;; (if (and
;; (= 1 (length index-alist))
;; (consp (car index-alist))
;; (let ((tokens (split-string
;; (car (car index-alist))
;; "[ \t]" t)))
;; (and (<= 1 (length tokens))
;; (string= (downcase
;; (nth 0 tokens)) "namespace"))))
;;
;; (let (elt
;; (newlist (cdar index-alist)))
;; (setf (car (car newlist)) (car (car index-alist)))
;; newlist)
;;
;; index-alist)
)))))
;; ==================================================================
;; ==================================================================
;; 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-event)
(cb0 (char-before (- (point) 0)))
(cb1 (char-before (- (point) 1)))
is-first-non-whitespace
did-auto-insert
)
;; check if two prior chars were slash, in other words,
;; check if this is the third slash in a row.
(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
(forward-line -1)
(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.
(forward-line (- 0 (/ 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'.")
(defun csharp-time ()
"returns the time of day as a string. Used in the `csharp-log' function."
(substring (current-time-string) 11 19)) ;24-hr time
(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 "C# %s %s" (csharp-time) 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 "max-bostmt dash(%d)" dash)
(goto-char curpos)
(setq nodash (progn (c-beginning-of-statement 1) (point)))
(csharp-log 3 "max-bostmt nodash(%d)" nodash)
(goto-char curpos)
(max dash nodash)))
(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 "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 '(6)) ; (6) = expression prefix, (3) = symbol
;;(message (format "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 "set-s-t: open quote pos(%d) chr(%c)"
;; curpos (char-after curpos)))
))
(setq state (+ 1 state)))
((= state 2)
(cond
;; handle backslash inside the string
((= (char-after curpos) ?\\)
(c-put-char-property curpos 'syntax-table '(2)) ; (1) = punctuation, (2) = word
;;(message (format "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 "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 "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 "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 "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 "verblit 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 "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 "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 "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 "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.
"
(csharp-log 2 "before font lock %d %d %d %d" beg end old-len (point))
(let ((start-scan (progn
;; is this right? I think
(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 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
;; ==================================================================
;;
;; The following 2 defuns redefine functions from cc-mode, to add
;; special cases for C#. 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[ \t\n\f\v\r]+\\([[: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))))
(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-safe (c-forward-sexp -1))
(let (bracepos)
(if (and (or (looking-at c-brace-list-key)
(progn
(c-safe (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-safe (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 . +)
))
))
(defun csharp-guess-compile-command ()
"set `compile-command' intelligently depending on the
current buffer, or the contents of the current directory.
"
(interactive)
(set (make-local-variable 'compile-command)
(cond
((or (file-expand-wildcards "*.csproj" t)
(file-expand-wildcards "*.vcproj" t)
(file-expand-wildcards "*.vbproj" t)
(file-expand-wildcards "*.shfbproj" t)
(file-expand-wildcards "*.sln" t))
(concat csharp-msbuild-tool " "))
;; sometimes, not sure why, the buffer-file-name is
;; not set. Can use it only if set.
(buffer-file-name
(let ((filename (file-name-nondirectory buffer-file-name)))
(cond
;; editing a c# file - check for an explicitly-specified command
((string-equal (substring buffer-file-name -3) ".cs")
(let ((explicitly-specified-command
(csharp-get-value-from-comments "compile" csharp-cmd-line-limit)))
(if explicitly-specified-command
(csharp-replace-command-tokens explicitly-specified-command)
(concat csharp-make-tool " " ;; assume a makefile exists
(file-name-sans-extension filename)
".exe"))))
;; something else - do a typical .exe build
(t
(concat csharp-make-tool " "
(file-name-sans-extension filename)
".exe")))))
(t
;; punt
(concat csharp-make-tool " ")))))
(defun csharp-invoke-compile-interactively ()
"fn to wrap the `compile' function. This simply
checks to see if `compile-command' has been previously set, and
if not, invokes `csharp-guess-compile-command' to set the value.
Then it invokes the `compile' function, interactively.
The effect is to guess the compile command only once, per buffer.
I tried doing this with advice attached to the `compile'
function, but because of the interactive nature of the fn, it
didn't work the way I wanted it to. So this fn should be bound to
the key sequence the user likes for invoking compile, like ctrl-c
ctrl-e.
"
(interactive)
(cond
((not (boundp 'csharp-local-compile-command-has-been-set))
(csharp-guess-compile-command)
(set (make-local-variable 'csharp-local-compile-command-has-been-set) t)))
;; local compile command has now been set
(call-interactively 'compile))
;;; 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#.
Normally, you'd want to autoload this mode by setting `auto-mode-alist' with
an entry for csharp, in your .emacs file:
(autoload 'csharp-mode \"csharp-mode\" \"Major mode for editing C# code.\" t)
(setq auto-mode-alist
(append '((\"\\.cs$\" . csharp-mode)) auto-mode-alist))
The mode provides fontification and indent for C# syntax, as well
as some other handy features.
At mode startup, there are two interesting hooks that run:
`c-mode-common-hook' is run with no args, then `csharp-mode-hook' is run after
that, also with no args.
To run your own logic after csharp-mode starts, do this:
(defun my-csharp-mode-fn ()
\"my function that runs when csharp-mode is initialized for a buffer.\"
(turn-on-font-lock)
(turn-on-auto-revert-mode) ;; helpful when also using Visual Studio
(setq indent-tabs-mode nil) ;; tabs are evil
(flymake-mode 1)
(yas/minor-mode-on)
(require 'rfringe) ;; handy for flymake
(require 'flymake-cursor) ;; also handy for flymake
....your own code here...
)
(add-hook 'csharp-mode-hook 'my-csharp-mode-fn t)
The function above is just a suggestion.
Compile integration:
========================
csharp-mode binds the function `csharp-invoke-compile-interactively' to
\"\C-x\C-e\" . This function attempts to intellgently guess the format of the
compile command to use for a buffer. It looks in the comments at the head of
the buffer for a line that begins with compile: . For exammple:
// compile: csc.exe /t:library /r:Mylib.dll Foo.cs
If csharp-mode finds a line like this, it will suggest the text that follows
as the compilation command when running `compile' for the first time. If such
a line is not found, csharp-mode falls back to a msbuild or nmake command.
See the documentation on `csharp-cmd-line-limit' for further information. If
you don't want this magic, then you can just run `compile' directly, rather
than `csharp-invoke-compile-interactively' .
This mode will also automatically add a symbol and regexp to the
`compilation-error-regexp-alist' and`compilation-error-regexp-alist-alist'
respectively, for Csc.exe error and warning messages. If you invoke `compile',
then `next-error' should work properly for error messages produced by csc.exe.
Flymake Integraiton
========================
You can use flymake with csharp mode to automatically check the syntax of your
csharp code, and highlight errors. To do so, add a comment line like this to
each .cs file that you use flymake with:
// flymake: csc.exe /t:module /R:Foo.dll @@FILE@@
csharp-mode replaces special tokens in the command with different values:
@@ORIG@@ - gets replaced with the original filename
@@FILE@@ - gets replaced with the name of the temporary file
created by flymake. This is usually what you want in place of the
name of the file to be compiled.
See the documentation on `csharp-cmd-line-limit' for further information.
You may also want to run a syntax checker, like fxcop:
// flymake: fxcopcmd.exe /c /F:MyLibrary.dll
In this case you don't need either of the tokens described above.
If the module has no external dependencies, then you need not specify any
flymake command at all. csharp-mode will implicitly act as if you had
specified the command:
// flymake: csc.exe /t:module /nologo @@FILE@@
It looks for the EXE on the path. You can specify a full path if you like.
YASnippet and IMenu Integraiton
===============================
Check the menubar for menu entries for YASnippet and Imenu; the latter
is labelled \"Index\".
The Imenu index gets computed when the file is .cs first opened and loaded.
This may take a moment or two. If you don't like this delay and don't
use imenu, you can turn this off with the variable `csharp-want-imenu'.
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)
;; compile
(local-set-key "\C-x\C-e" 'csharp-invoke-compile-interactively)
;; to allow next-error to work with csc.exe:
(setq compilation-scroll-output t)
;; csc.exe, the C# Compiler, produces errors like this:
;; file.cs(6,18): error CS1006: Name of constructor must match name of class
(if (boundp 'compilation-error-regexp-alist-alist)
(progn
(add-to-list
'compilation-error-regexp-alist-alist
'(ms-csharp "^[ \t]*\\([-_:A-Za-z0-9][^\n(]*\\.\\(?:cs\\|xaml\\)\\)(\\([0-9]+\\)[,]\\([0-9]+\\)) ?: \\(error\\|warning\\) CS[0-9]+:" 1 2 3))
(add-to-list
'compilation-error-regexp-alist
'ms-csharp)))
;; flymake
(eval-after-load "flymake"
'(progn
(if csharp-want-flymake-fixup
(csharp-flymake-install))))
(eval-after-load "yasnippet"
'(progn
(if csharp-want-yasnippet-fixup
(csharp-yasnippet-fixup))))
(local-set-key (kbd "/") 'csharp-maybe-insert-codedoc)
(local-set-key (kbd "{") 'csharp-insert-open-brace)
;; 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
;; This will happen on font; it's necessary only
;; if font-lock is disabled. But it won't hurt.
(csharp-scan-for-verbatim-literals-and-set-props nil nil)
;; Allow fill-paragraph to work on xml code doc
;; This setting gets overwritten quietly by c-run-mode-hooks,
;; so I put it afterwards to make it stick.
(make-local-variable 'paragraph-separate)
;;(message "C#: set paragraph-separate")
;; Speedbar handling
(if (fboundp 'speedbar-add-supported-extension)
(speedbar-add-supported-extension '(".cs"))) ;; idempotent
(c-update-modeline)
(c-run-mode-hooks 'c-mode-common-hook 'csharp-mode-hook)
;; maybe do imenu scan after hook returns
(if csharp-want-imenu
(progn
;; There are two ways to do imenu indexing. One is to provide a
;; function, via `imenu-create-index-function'. The other is to
;; provide imenu with a list of regexps via
;; `imenu-generic-expression'; imenu will do a "generic scan" for you.
;; csharp-mode uses the former method.
;;
(setq imenu-create-index-function 'csharp-imenu-create-index)
(imenu-add-menubar-index)))
;; The paragraph-separate variable was getting stomped by
;; other hooks, so it must reside here.
(setq paragraph-separate
"[ \t]*\\(//+\\|\\**\\)\\([ \t]+\\|[ \t]+<.+?>\\)$\\|^\f")
(setq beginning-of-defun-function 'csharp-move-back-to-beginning-of-defun)
;; end-of-defun-function can remain forward-sexp !!
(set (make-local-variable 'comment-auto-fill-only-comments) t)
)
;; =======================================================
;;
;; This section attempts to workaround an anomalous display behavior
;; for tooltips. It's not strictly necessary, only for aesthetics. The
;; issue is that tooltips can get clipped. This is the topic of Emacs
;; bug #5908, unfixed in v23 and present in v22.
(defadvice tooltip-show (before
flymake-for-csharp-fixup-tooltip
(arg &optional use-echo-area)
activate compile)
(progn
(if ;;(and (not use-echo-area) (eq major-mode 'csharp-mode))
(not use-echo-area)
(let ((orig (ad-get-arg 0)))
(ad-set-arg 0 (concat " " (cheeso-string-trim (cheeso-reform-string 74 orig) ?\ )))
))))
;; ========================================================================
;; YA-snippet integration
(defun csharp-yasnippet-fixup ()
"Sets snippets into ya-snippet for C#, if yasnippet is loaded,
and if the snippets do not already exist.
"
(if (not csharp--yasnippet-has-been-fixed)
(if (fboundp 'yas/snippet-table-fetch)
;; yasnippet is present
(let ((snippet-table (yas/snippet-table 'csharp-mode))
(keymap (if yas/use-menu
(yas/menu-keymap-for-mode 'csharp-mode)
nil))
(yas/require-template-condition nil)
(builtin-snips
'(
("xmls" "{
XmlSerializer s1 = new XmlSerializer(typeof(${1:type}));
// use this to \"suppress\" the default xsd and xsd-instance namespaces
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add(\"\", \"\");
s1.Serialize(new XTWFND(System.Console.Out), object, ns);
System.Console.WriteLine(\"\\n\");
}
$0
/// XmlTextWriterFormattedNoDeclaration
/// helper class : eliminates the XML Documentation at the
/// start of a XML doc.
/// XTWFND = XmlTextWriterFormattedNoDeclaration
/// usage: s1.Serialize(new XTWFND(System.Console.Out), thing, ns);
public class XTWFND : System.Xml.XmlTextWriter
{
public XTWFND(System.IO.StringWriter w) : base(w) { Formatting=System.Xml.Formatting.Indented; }
public XTWFND(System.IO.TextWriter w) : base(w) { Formatting = System.Xml.Formatting.Indented; }
public XTWFND(System.IO.Stream s) : base(s, null) { Formatting = System.Xml.Formatting.Indented; }
public XTWFND(string filename) : base(filename, null) { Formatting = System.Xml.Formatting.Indented; }
public override void WriteStartDocument() { }
}
" "xmlserializer { ... }" nil)
("wl" "System.Console.WriteLine(${0://thing to do});
" "WriteLine( ... );" nil)
("while" "while (${1:condition})
{
${0://thing to do}
}" "while (...) { ... }" nil)
("using" "using (${1:type} ${2:var} = new ${1:type}(${4:ctor args}))
{
${5:// body...}
}" "using ... { ... }" nil)
("try" "try
{
$0
}
catch (System.Exception exc1)
{
throw new Exception(\"uncaught exception\", exc1);
}" "try { ... } catch { ... }" nil)
("sum" "/// <summary>
/// ${1:description}
/// </summary>
///
/// <remarks>
/// ${2:comments}
/// </remarks>
" "/// <summary>..." nil)
("sing" "#region Singleton ${1:className}
public sealed class $1
{
private readonly static $1 _instance = new $1();
public static $1 Instance { get { return _instance; } }
static $1() { /* required for lazy init */ }
private $1()
{
// implementation here
}
}
#endregion
" "public sealed class Singleton {...}" nil)
("setting" " #region Property${1:PropName}
private string default$1 = ${2:defaultValue};
private string _$1;
public string $1
{
get
{
if (_$1 == null)
{
_$1 = System.Configuration.ConfigurationManager.AppSettings[\"$1\"];
if (string.IsNullOrEmpty(_$1))
{
_$1 = default$1;
}
}
return this._$1;
}
set
{
string new$1 = value;
// optional validation:
//Validation.EnforceXxxx(new$1, \"$1\");
_$1 = new$1;
}
}
#endregion
" "config setting" nil)
("prop" "private ${1:Type} _${2:Name};
public ${1:Type} ${2:Name}
{
get
{
${3://get impl}
}
set
{
${4://get impl}
}
}" "property ... { ... }" nil)
("pa" " for (int i=0; i < args.Length; i++)
{
switch (args[i])
{
case \"-?\":
case \"-help\":
throw new ArgumentException(args[i]);
default: // positional args
if ($2 != 0)
// we have all the args we need
throw new ArgumentException(args[i]);
if ($1 == null)
$1 = args[i];
else
$2 = System.Int32.Parse(args[i]);
break;
}
}
// check values
if ($1 == null)
throw new ArgumentException();
if ($2 == 0)
throw new ArgumentException();
" "switch(args[0]) {...}" nil)
("openread" " using(Stream src = File.OpenRead($1))
{
using(Stream dest= File.Create($2))
{
if (compress)
Compress(src, dest);
else
Decompress(src, dest);
}
}
" "File.OpenRead(...)" nil)
("ofd" "var dlg = new System.Windows.Forms.OpenFileDialog();
dlg.Filter = \"${1:filter string}\"; // ex: \"C# (*.cs)|*.cs|Text (*.txt)|*.txt\";
if (dlg.ShowDialog() == DialogResult.OK)
{
string fileName = dlg.FileName;
$0
}
" "new OpenFileDialog; if (DialogResult.OK) { ... }" nil)
("ife" "if (${1:predicate})
{
${2:// then clause}
}
else
{
${3:// else clause}
}" "if (...) { ... } else { ... }" nil)
("fore" "foreach (${1:type} ${2:var} in ${3:IEnumerable})
{
${4:// body...}
}" "foreach ... { ... }" nil)
("for" "for (int ${1:index}=0; $1 < ${2:Limit}; $1++)
{
${3:// body...}
}" "for (...) { ... }" nil)
("fbd" "using (FolderBrowserDialog dialog = new FolderBrowserDialog())
{
dialog.Description = \"Open a folder which contains the xml output\";
dialog.ShowNewFolderButton = false;
dialog.RootFolder = Environment.SpecialFolder.MyComputer;
if(dialog.ShowDialog() == DialogResult.OK)
{
string folder = dialog.SelectedPath;
foreach (string fileName in Directory.GetFiles(folder, \"*.xml\", SearchOption.TopDirectoryOnly))
{
SQLGenerator.GenerateSQLTransactions(Path.GetFullPath(fileName));
}
}
}
" "new FolderBrowserDialog; if (DialogResult.OK) { ... }" nil)
("doc" "/// <summary>
/// ${1:summary}
/// </summary>
///
/// <remarks>
/// ${2:remarks}
/// </remarks>
$0" "XML Documentation" nil)
("conv" " List<String> Values = new List<String>() { \"7\", \"13\", \"41\", \"3\" };
// ConvertAll maps the given delegate across all the List elements
var foo = Values.ConvertAll((s) => { return System.Convert.ToInt32(s); }) ;
System.Console.WriteLine(\"typeof(foo) = {0}\", foo.GetType().ToString());
Array.ForEach(foo.ToArray(),Console.WriteLine);
" "ConvertAll((s) => { ... });" nil)
("cla" "public class ${1:Classname}
{
// default ctor
public ${1:Classname}()
{
}
${2:// methods here}
}" "class ... { ... }" nil)
("ca" " List<String> Values = new List<String>() { \"7\", \"13\", \"41\", \"3\" };
// ConvertAll maps the given delegate across all the List elements
var foo = Values.ConvertAll((s) => { return System.Convert.ToInt32(s); }) ;
System.Console.WriteLine(\"typeof(foo) = {0}\", foo.GetType().ToString());
Array.ForEach(foo.ToArray(),Console.WriteLine);
" "ConvertAll((s) => { ... });" nil)
("ass" "
[assembly: AssemblyTitle(\"$1\")]
[assembly: AssemblyCompany(\"${2:YourCoName}\")]
[assembly: AssemblyProduct(\"${3}\")]
[assembly: AssemblyCopyright(\"Copyright <20> ${4:Someone} 2011\")]
[assembly: AssemblyTrademark(\"\")]
[assembly: AssemblyCulture(\"\")]
[assembly: AssemblyConfiguration(\"\")]
[assembly: AssemblyDescription(\"${5}\")]
[assembly: AssemblyVersion(\"${6:1.0.1.0}\")]
[assembly: AssemblyFileVersion(\"${7:1.0.1.0}\")]
" "assembly info" nil)
)))
(setq csharp--yasnippet-has-been-fixed t)
(add-to-list 'yas/known-modes 'csharp-mode)
;; It's possible that Csharp-mode is not on the yasnippet menu
;; Install it here.
(when yas/use-menu
(define-key
yas/menu-keymap
(vector 'csharp-mode)
`(menu-item "C#" ,keymap)))
;; Insert the snippets from above into the table if they
;; are not already defined.
(mapcar
'(lambda (item)
(let* ((full-key (car item))
(existing-snip
(yas/snippet-table-fetch snippet-table full-key)))
(if (not existing-snip)
(let* ((key (file-name-sans-extension full-key))
(name (caddr item))
(condition (nth 3 item))
(template (yas/make-template (cadr item)
(or name key)
condition)))
(yas/snippet-table-store snippet-table
full-key
key
template)
(when yas/use-menu
(define-key keymap (vector (make-symbol full-key))
`(menu-item ,(yas/template-name template)
,(yas/make-menu-binding (yas/template-content template))
:keys ,(concat key yas/trigger-symbol))))))))
builtin-snips)))))
(message (concat "Done loading " load-file-name))
(provide 'csharp-mode)
;;; csharp-mode.el ends here