;;; csharp-mode.el --- C# mode derived mode
|
|
|
|
;; Author : Dylan R. E. Moonfire (original)
|
|
;; Maintainer : Jostein Kjønigsen <jostein@gmail.com>
|
|
;; Created : Feburary 2005
|
|
;; Modified : 2018
|
|
;; Version : 0.9.2
|
|
;; Package-Version: 20190717.1024
|
|
;; Keywords : c# languages oop mode
|
|
;; X-URL : https://github.com/josteink/csharp-mode
|
|
;; Last-saved : 2018-Jul-08
|
|
|
|
;;
|
|
;; 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
|
|
;; 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.
|
|
;;
|
|
;; - compatible with electric-pair-mode for intelligent insertion
|
|
;; of matched braces, quotes, etc.
|
|
;;
|
|
;; - 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)
|
|
;; ...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.
|
|
;;
|
|
;;
|
|
;; 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 ??
|
|
;; - extern keyword
|
|
;;
|
|
;; 0.8.7 2014 November 29
|
|
;; - Fix broken cl-dependency in emacs24.4 and defadvice for tooltips.
|
|
;;
|
|
;; 0.8.8 2014 December 3
|
|
;; - Fix broken byte-compile.
|
|
;; - Add extra C# keywords.
|
|
;; - Call prog-mode hooks.
|
|
;;
|
|
;; 0.8.9 2015 March 15
|
|
;; - (Re)add compilation-mode support for msbuild and xbuild.
|
|
;;
|
|
;; 0.8.10 2015 May 31th
|
|
;; - Imenu: Correctly handle support for default-values in paramlist.
|
|
;;
|
|
;; 0.8.11 2015 November 21st
|
|
;; - Make mode a derived mode. Improve evil-support.
|
|
;; - Add support for devenv compilation-output.
|
|
;; - Fix all runtime warnings
|
|
;; - Fix error with string-values in #region directives.
|
|
;;
|
|
;; 0.8.12 2016 January 6th
|
|
;; - Various fixes and improvements for imenu indexing.
|
|
;;
|
|
;; 0.9.0 2016 September 9th
|
|
;; - Fix issues with compilation-mode and lines with arrays.
|
|
;; - Fontification of compiler directives.
|
|
;; - Much faster, completely rewritten imenu-implementation.
|
|
;; - Fix indentation issues.
|
|
;; - Fix Emacs-25 related bugs.
|
|
;; - Cleaned up dead code.
|
|
;;
|
|
;; 0.9.1 2017
|
|
;; - Fix indentation for generic type-initializers.
|
|
;; - Fix fontification of using and namespace-statements with
|
|
;; underscores in them.
|
|
;; - Fixes for indentation for many kinds of type-initializers.
|
|
;;
|
|
;; 0.9.2 2018 July
|
|
;; - Try to fix some breakage introduced by changes in Emacs 27.
|
|
;;
|
|
;;; Code:
|
|
|
|
(require 'cc-mode)
|
|
(require 'cc-fonts)
|
|
(require 'cl-lib)
|
|
|
|
;; prevent warnings like
|
|
;; csharp-mode.el:4134:21:Warning: reference to free variable
|
|
;; `compilation-error-regexp-alist-alist'
|
|
(require 'compile)
|
|
|
|
;; Work around emacs bug#23053
|
|
(eval-when-compile
|
|
(require 'cc-langs))
|
|
|
|
;; Work around emacs bug#18845
|
|
(eval-when-compile
|
|
(when (and (= emacs-major-version 24) (>= emacs-minor-version 4))
|
|
(require 'cl)))
|
|
|
|
(require 'imenu)
|
|
|
|
;; ==================================================================
|
|
;; 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)))
|
|
|
|
|
|
(defgroup csharp nil
|
|
"Major mode for editing C# code."
|
|
:group 'prog-mode)
|
|
|
|
|
|
;; Custom variables
|
|
;; ensure all are defined before using ...;
|
|
|
|
(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)
|
|
|
|
|
|
(defcustom csharp-want-imenu t
|
|
"*Whether to generate a buffer index via imenu for C# buffers."
|
|
:type 'boolean :group 'csharp)
|
|
|
|
|
|
|
|
|
|
|
|
;; 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
|
|
;; ==================================================================
|
|
;; constants used in this module
|
|
;; ==================================================================
|
|
|
|
(defconst csharp-type-initializer-statement-re
|
|
(concat
|
|
"\\<new[ \t\n\r\f\v]+"
|
|
"\\([[:alpha:]_][[:alnum:]_<>\\.]*\\)")
|
|
"Regexp that captures a type-initializer statement in C#")
|
|
|
|
(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#")
|
|
|
|
;; ==================================================================
|
|
|
|
;; Make our mode known to the language constant system. Use Java
|
|
;; mode as the fallback for the constants we don't change here.
|
|
;; This needs to be done also at compile time since the language
|
|
;; constants are evaluated then.
|
|
(c-add-language 'csharp-mode 'java-mode))
|
|
|
|
;; ==================================================================
|
|
;; end of c# upfront stuff
|
|
;; ==================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; ==================================================================
|
|
;; csharp-mode utility and feature defuns
|
|
;; ==================================================================
|
|
|
|
(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 2 cases in C#:
|
|
|
|
- after an attribute that decorates a class, method, field, or
|
|
property.
|
|
|
|
- in an object initializer, before the open-curly?
|
|
|
|
An example of the former is [WebMethod] or [XmlElement].
|
|
|
|
Providing this function allows the indenting in `csharp-mode'
|
|
to work properly with code that includes attributes."
|
|
(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]+"
|
|
;; typename
|
|
"\\(?:[A-Za-z_][[:alnum:]]*\\.\\)*"
|
|
"[A-Za-z_][[:alnum:]]*"
|
|
;; simplified generic constraint.
|
|
;; handles generic sub-types.
|
|
;; { is optional because otherwise initializers with
|
|
;; bracket on same line will indent wrongly.
|
|
"\\(?:<[[:alnum:], <>]+>[ \t\n\f\v\r]*{?\\)?"
|
|
;; optional array-specifier
|
|
"\\(?:\\[\\]\\)?"
|
|
;; spacing
|
|
"[\ t\n\f\v\r]*") nil)
|
|
(looking-at "[ \t\n\f\v\r]*{"))
|
|
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))
|
|
)))
|
|
|
|
|
|
;; ==================================================================
|
|
;; 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 "\\)")
|
|
'((if (fboundp 'c-font-lock-invalid-string)
|
|
(c-font-lock-invalid-string)
|
|
(csharp-mode-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-z0-9_]+\\)"
|
|
"[ \t\n\f\v\r]*="
|
|
"[ \t\n\f\v\r]*"
|
|
"\\)?"
|
|
"\\(\\(?:[A-Za-z0-9_]+\\.\\)*[A-Za-z0-9_]+\\)"
|
|
"[ \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)
|
|
))
|
|
|
|
|
|
|
|
(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
|
|
;; is square parenthesis block? - start
|
|
(let* ((start (point)) ;; used to hold our position, so that we know that
|
|
(end)) ;; our code isn't stuck trying to look for a non-existant sexp.
|
|
(and (eq (char-after) 91) ;; open square
|
|
(while (and (eq (char-after) 91)
|
|
(not (eq start end)))
|
|
(c-safe (c-forward-sexp 1))
|
|
(setq end (point)))
|
|
(eq (char-before) 93)))
|
|
;; is square parenthesis block? - end
|
|
(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
|
|
(with-no-warnings
|
|
(condition-case nil
|
|
(c-font-lock-declarators limit t nil)
|
|
(wrong-number-of-arguments
|
|
(c-font-lock-declarators limit t nil nil)))))
|
|
(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
|
|
"#\\s *\\(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))
|
|
|
|
;; 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-z0-9_]+\\.\\)*[A-Za-z0-9_]+\\)"
|
|
2 font-lock-constant-face t)
|
|
|
|
|
|
;; Highlight function-invocation.
|
|
;; (this may in the future use font-lock-function-call-face, if standardized)
|
|
,`(,"\\.\\([A-Za-z0-9_]+\\)("
|
|
1 font-lock-function-name-face t)
|
|
|
|
|
|
))
|
|
|
|
;; verbatim string literals can be multiline
|
|
(c-lang-defconst c-multiline-string-start-char
|
|
csharp ?@)
|
|
|
|
(defun csharp-mode-syntax-propertize-function (beg end)
|
|
"Apply syntax table properties to special constructs in region BEG to END.
|
|
Currently handled:
|
|
|
|
- Fontify verbatim literal strings correctly
|
|
- Highlight text after #region or #pragma as comment"
|
|
(save-excursion
|
|
(goto-char beg)
|
|
(while (search-forward "@\"" end t)
|
|
(let ((in-comment-or-string-p (save-excursion
|
|
(goto-char (match-beginning 0))
|
|
(or (nth 3 (syntax-ppss))
|
|
(nth 4 (syntax-ppss))))))
|
|
(when (not in-comment-or-string-p)
|
|
(let (done)
|
|
(while (and (not done) (< (point) end))
|
|
(skip-chars-forward "^\"\\\\" end)
|
|
(cond
|
|
((= (following-char) ?\\)
|
|
(put-text-property (point) (1+ (point))
|
|
'syntax-table (string-to-syntax "."))
|
|
(forward-char 1))
|
|
((= (following-char) ?\")
|
|
(forward-char 1)
|
|
(if (= (following-char) ?\")
|
|
(progn
|
|
(put-text-property (1- (point)) (1+ (point))
|
|
'syntax-table (string-to-syntax "/"))
|
|
(forward-char 1))
|
|
(setq done t)))))))))
|
|
|
|
(goto-char beg)
|
|
(while (re-search-forward "^\\s *#\\s *\\(region\\|pragma\\)\\s " end t)
|
|
(when (looking-at "\\s *\\S ")
|
|
;; mark the whitespace separating the directive from the comment
|
|
;; text as comment starter to allow correct word movement
|
|
(put-text-property (1- (point)) (point)
|
|
'syntax-table (string-to-syntax "< b"))))))
|
|
|
|
;; 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" "struct" "interface"))
|
|
|
|
(c-lang-defconst c-block-prefix-disallowed-chars
|
|
|
|
;; Allow ':' for inherit list starters.
|
|
csharp (cl-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" "volatile" "new"))
|
|
|
|
|
|
;; 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"
|
|
"implicit" "explicit" "fixed"
|
|
"override" "params" "internal" "async" "extern" "unsafe"))
|
|
|
|
|
|
;; 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")
|
|
)
|
|
|
|
(c-lang-defconst c-opt-op-identifier-prefix
|
|
"Regexp matching the token before the ones in
|
|
`c-overloadable-operators' when operators are specified in their \"identifier form\".
|
|
|
|
This regexp is assumed to not match any non-operator identifier."
|
|
csharp (c-make-keywords-re t '("operator")))
|
|
|
|
;; Define the keywords that can have something following after them.
|
|
(c-lang-defconst c-type-list-kwds
|
|
csharp '("struct" "class" "interface" "is" "as" "operator"
|
|
"delegate" "event" "set" "get" "add" "remove"))
|
|
|
|
;; Handle typeless variable declaration
|
|
(c-lang-defconst c-typeless-decl-kwds
|
|
csharp '("var"))
|
|
|
|
;; 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" "else" "try" "finally"))
|
|
|
|
|
|
;; Statement keywords followed by a paren sexp and then by a substatement.
|
|
(c-lang-defconst c-block-stmt-2-kwds
|
|
csharp '("for" "if" "switch" "while" "catch" "foreach" "using"
|
|
"fixed"
|
|
"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" "value"))
|
|
|
|
;; Keywords that start "primary expressions."
|
|
(c-lang-defconst c-primary-expr-kwds
|
|
csharp '("this" "base" "operator"))
|
|
|
|
;; 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" "let" "orderby" "ascending" "descending"
|
|
"await" "async"))
|
|
|
|
(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 *#\\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)))
|
|
|
|
|
|
;; allow strings as switch-case values by leaving out string
|
|
;; delimiters in this definition
|
|
(c-lang-defconst c-nonlabel-token-key
|
|
csharp (c-make-keywords-re t
|
|
(cl-set-difference (c-lang-const c-keywords)
|
|
(append (c-lang-const c-label-kwds)
|
|
(c-lang-const c-protection-kwds))
|
|
:test 'string-equal)))
|
|
|
|
(defconst csharp-font-lock-keywords-1 (c-lang-const c-matchers-1 csharp)
|
|
"Minimal highlighting for C# mode.")
|
|
|
|
(defconst csharp-font-lock-keywords-2 (c-lang-const c-matchers-2 csharp)
|
|
"Fast normal highlighting for C# mode.")
|
|
|
|
(defconst csharp-font-lock-keywords-3 (c-lang-const c-matchers-3 csharp)
|
|
"Accurate normal highlighting for C# mode.")
|
|
|
|
(defvar csharp-font-lock-keywords csharp-font-lock-keywords-3
|
|
"Default expressions to highlight in C# mode.")
|
|
|
|
|
|
(defvar csharp-mode-syntax-table nil
|
|
"Syntax table used in ‘csharp-mode’ buffers.")
|
|
(or csharp-mode-syntax-table
|
|
(setq csharp-mode-syntax-table
|
|
(funcall (c-lang-const c-make-mode-syntax-table csharp))))
|
|
|
|
(defvar csharp-mode-abbrev-table nil
|
|
"Abbreviation table used in ‘csharp-mode’ buffers.")
|
|
(c-define-abbrev-table 'csharp-mode-abbrev-table
|
|
;; Keywords that if they occur first on a line might alter the
|
|
;; syntactic context, and which therefore should trig reindentation
|
|
;; when they are completed.
|
|
'(("else" "else" c-electric-continued-statement 0)
|
|
("while" "while" c-electric-continued-statement 0)
|
|
("catch" "catch" c-electric-continued-statement 0)
|
|
("finally" "finally" c-electric-continued-statement 0)))
|
|
|
|
(defvar csharp-mode-map (let ((map (c-make-inherited-keymap)))
|
|
;; Add bindings which are only useful for C#
|
|
map)
|
|
"Keymap used in ‘csharp-mode’ buffers.")
|
|
|
|
|
|
;; TODO
|
|
;; Defines our constant for finding attributes.
|
|
;;(defconst csharp-attribute-regex "\\[\\([XmlType]+\\)(")
|
|
;;(defconst csharp-attribute-regex "\\[\\(.\\)")
|
|
;; This doesn't work because the string regex happens before this point
|
|
;; and getting the font-locking to work before and after is fairly difficult
|
|
;;(defconst csharp-attribute-regex
|
|
;; (concat
|
|
;; "\\[[a-zA-Z][ \ta-zA-Z0-9.]+"
|
|
;; "\\((.*\\)?"
|
|
;;))
|
|
|
|
|
|
;; ==================================================================
|
|
;; end of c# values for "language constants" defined in cc-langs.el
|
|
;; ==================================================================
|
|
|
|
|
|
;; ========================================================================
|
|
;; moving
|
|
|
|
;; alist of regexps for various structures in a csharp source file.
|
|
(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]+"
|
|
"\\(" ;; 3. begin name of func
|
|
"\\(?:[A-Za-z_][[:alnum:]_]*\\.\\)*" ;; possible prefix interface
|
|
"[[:alpha:]_][[:alnum:]_]*" ;; actual func name
|
|
"\\(?:<\\(?:[[:alpha:]][[:alnum:]]*\\)\\(?:[, ]+[[:alpha:]][[:alnum:]]*\\)*>\\)?" ;; (with optional generic type parameter(s)
|
|
"\\)" ;; 3. end of name of func
|
|
"[ \t\n\r\f\v]*"
|
|
"\\(\([^\)]*\)\\)" ;; 4. params w/parens
|
|
"\\(?:[ \t]*/[/*].*\\)?" ;; optional comment at end of line
|
|
"[ \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]*"
|
|
))
|
|
|
|
`(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)
|
|
"Retrieve a regexp from `csharp--regexp-alist' corresponding to SYMBOL."
|
|
(let ((elt (assoc symbol csharp--regexp-alist)))
|
|
(if elt (cadr elt) nil)))
|
|
|
|
|
|
(defun csharp-move-back-to-beginning-of-block ()
|
|
"Move 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)
|
|
"Move back to the open-curly that begin *something*.
|
|
*something* is defined by 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 nil)
|
|
(or (not must-not-match)
|
|
(not (looking-back must-not-match nil))))
|
|
need-to-backup t)
|
|
(setq done t)))
|
|
found))
|
|
|
|
|
|
|
|
(defun csharp-move-back-to-beginning-of-defun ()
|
|
"Move back to the open-curly that start 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) nil)
|
|
(not (looking-back (csharp--regexp 'namespace-start) nil)))
|
|
(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-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) nil))
|
|
(not (looking-back (csharp--regexp 'namespace-start) nil))
|
|
(looking-back (csharp--regexp 'func-start) nil)))
|
|
|
|
|
|
(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) nil))
|
|
(looking-back (csharp--regexp 'class-start) nil)))
|
|
|
|
|
|
(defun csharp-move-fwd-to-end-of-defun ()
|
|
"Move forward to the close-curly that ends 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 "}" nil)
|
|
(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 ()
|
|
"Move back to the open-curly that begin 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) nil)
|
|
(not (looking-back (csharp--regexp 'namespace-start) nil)))
|
|
(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 ()
|
|
"Move forward to the close-curly that ends 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 ()
|
|
"Move back to the open-curly that begins 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) nil))
|
|
(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
|
|
|
|
(defconst csharp--imenu-expression
|
|
(let* ((single-space "[ \t\n\r\f\v]")
|
|
(optional-space (concat single-space "*"))
|
|
(bol "^[ \t]*") ;; BOL shouldnt accept lineshift.
|
|
(space (concat single-space "+"))
|
|
(access-modifier (regexp-opt '( "public" "private" "protected" "internal"
|
|
"static" "sealed" "partial" "override" "virtual"
|
|
"abstract" "async" "new" "unsafe")))
|
|
;; this will allow syntactically invalid combinations of modifiers
|
|
;; but that's a compiler problem, not a imenu-problem
|
|
(access-modifier-list (concat "\\(?:" access-modifier space "\\)"))
|
|
(access-modifiers (concat access-modifier-list "*"))
|
|
(basic-type (concat
|
|
;; typename
|
|
"\\(?:[A-Za-z_][[:alnum:]_]*\\.\\)*"
|
|
"[A-Za-z_][[:alnum:]_]*"
|
|
))
|
|
(type (concat
|
|
basic-type
|
|
;; simplified, optional generic constraint.
|
|
;; handles generic sub-types.
|
|
"\\(?:<[[:alnum:],<> \t\n\f\v\r]+>\\)?"))
|
|
(return-type (concat
|
|
type
|
|
;; optional array-specifier
|
|
"\\(?:\\[\\]\\)?"))
|
|
(interface-prefix (concat "\\(?:" type "\\.\\)"))
|
|
;; param-list with parens
|
|
(parameter-list "\\(?:\([^!\)]*\)\\)")
|
|
(inheritance-clause (concat "\\(?:"
|
|
optional-space
|
|
":"
|
|
optional-space type
|
|
"\\(?:" optional-space "," optional-space type "\\)*"
|
|
"\\)?")))
|
|
|
|
(list (list "namespace"
|
|
(concat bol "namespace" space
|
|
"\\(" basic-type "\\)") 1)
|
|
;; not all these are classes, but they can hold other
|
|
;; members, so they are treated uniformly.
|
|
(list "class"
|
|
(concat bol
|
|
access-modifiers
|
|
"\\("
|
|
(regexp-opt '("class" "struct" "interface")) space
|
|
type inheritance-clause "\\)") 1)
|
|
(list "enum"
|
|
(concat bol
|
|
access-modifiers
|
|
"\\(" "enum" space
|
|
basic-type "\\)") 1)
|
|
(list "ctor"
|
|
(concat bol
|
|
;; ctor MUST have access modifiers, or else we pick
|
|
;; every if statement in the file...
|
|
access-modifier-list "+"
|
|
"\\("
|
|
basic-type
|
|
optional-space
|
|
parameter-list
|
|
"\\)"
|
|
"\\(?:"
|
|
optional-space
|
|
":"
|
|
optional-space
|
|
"\\(?:this\\|base\\)"
|
|
optional-space
|
|
parameter-list
|
|
"\\)?"
|
|
optional-space "{") 1)
|
|
(list "method"
|
|
(concat bol
|
|
;; we MUST require modifiers, or else we cannot reliably
|
|
;; identify declarations, without also dragging in lots of
|
|
;; if statements and what not.
|
|
access-modifier-list "+"
|
|
return-type space
|
|
"\\("
|
|
type
|
|
optional-space
|
|
parameter-list
|
|
"\\)"
|
|
;; optional // or /* comment at end
|
|
"\\(?:[ \t]*/[/*].*\\)?"
|
|
optional-space
|
|
"{") 1)
|
|
(list "method-inf"
|
|
(concat bol
|
|
return-type space
|
|
"\\("
|
|
interface-prefix
|
|
type
|
|
optional-space
|
|
parameter-list
|
|
"\\)"
|
|
;; optional // or /* comment at end
|
|
"\\(?:[ \t]*/[/*].*\\)?"
|
|
optional-space
|
|
"{") 1)
|
|
(list "method-abs-ext"
|
|
(concat bol
|
|
access-modifier-list "+"
|
|
(regexp-opt '("extern" "abstract")) space
|
|
return-type space
|
|
"\\("
|
|
type
|
|
optional-space
|
|
parameter-list
|
|
"\\)"
|
|
optional-space
|
|
;; abstract/extern methods are terminated with ;
|
|
";") 1)
|
|
;; delegates are almost like abstract methods, so pick them up here
|
|
(list "delegate"
|
|
(concat bol
|
|
access-modifiers
|
|
"delegate" space
|
|
return-type space
|
|
"\\("
|
|
type
|
|
"\\)"
|
|
optional-space
|
|
parameter-list
|
|
;; optional // or /* comment at end
|
|
optional-space
|
|
";") 1)
|
|
(list "prop"
|
|
(concat bol
|
|
;; must require access modifiers, or else we
|
|
;; pick up pretty much anything.
|
|
access-modifiers
|
|
return-type space
|
|
"\\("
|
|
type
|
|
"\\)"
|
|
optional-space "{" optional-space
|
|
;; unless we are super-specific and expect the accesors,
|
|
;; lots of weird things gets slurped into the name.
|
|
;; including the accessors themselves.
|
|
(regexp-opt '("get" "set"))
|
|
) 1)
|
|
(list "prop-inf"
|
|
(concat bol
|
|
return-type space
|
|
"\\("
|
|
interface-prefix
|
|
type
|
|
"\\)"
|
|
optional-space "{" optional-space
|
|
;; unless we are super-specific and expect the accesors,
|
|
;; lots of weird things gets slurped into the name.
|
|
;; including the accessors themselves.
|
|
(regexp-opt '("get" "set"))
|
|
) 1)
|
|
;; adding fields... too much?
|
|
(list "field"
|
|
(concat bol
|
|
access-modifier-list "+"
|
|
;; fields can be readonly/const/volatile
|
|
"\\(?:" (regexp-opt '("readonly" "const" "volatile")) space "\\)?"
|
|
return-type space
|
|
"\\("
|
|
type
|
|
"\\)"
|
|
optional-space
|
|
;; optional assignment
|
|
"\\(?:=[^;]+\\)?"
|
|
";") 1)
|
|
(list "indexer"
|
|
(concat bol
|
|
access-modifiers
|
|
return-type space
|
|
"this" optional-space
|
|
"\\("
|
|
;; opening bracket
|
|
"\\[" optional-space
|
|
;; type
|
|
"\\([^\]]+\\)" optional-space
|
|
type
|
|
;; closing brackets
|
|
"\\]"
|
|
"\\)"
|
|
optional-space "{" optional-space
|
|
;; unless we are super-specific and expect the accesors,
|
|
;; lots of weird things gets slurped into the name.
|
|
;; including the accessors themselves.
|
|
(regexp-opt '("get" "set"))) 1)
|
|
(list "event"
|
|
(concat bol
|
|
access-modifier-list "+"
|
|
optional-space "event" optional-space
|
|
"\\("
|
|
return-type space
|
|
type
|
|
"\\)"
|
|
optional-space
|
|
";") 1))))
|
|
|
|
(defun csharp--imenu-get-pos (pair)
|
|
"Return `position' from a (title . position) cons-pair `PAIR'.
|
|
|
|
The position may be a integer, or a marker (as returned by
|
|
imenu-indexing). This function ensures what is returned is an
|
|
integer which can be used for easy comparison."
|
|
(let ((pos (cdr pair)))
|
|
(if (markerp pos)
|
|
(marker-position pos)
|
|
pos)))
|
|
|
|
(defun csharp--imenu-get-container (item containers previous)
|
|
"Return the container which `ITEM' belongs to.
|
|
|
|
`ITEM' is a (title . position) cons-pair. `CONTAINERS' is a
|
|
list of such. `PREVIOUS' is the name of the previous
|
|
container found when recursing through `CONTAINERS'.
|
|
|
|
The final result is based on item's position relative to those
|
|
found in `CONTAINERS', or nil if none is found."
|
|
(if (not containers)
|
|
previous
|
|
(let* ((item-pos (csharp--imenu-get-pos item))
|
|
(container (car containers))
|
|
(container-pos (csharp--imenu-get-pos container))
|
|
(rest (cdr containers)))
|
|
(if (and container-pos
|
|
(< item-pos container-pos))
|
|
previous
|
|
(csharp--imenu-get-container item rest container)))))
|
|
|
|
(defun csharp--imenu-get-container-name (item containers)
|
|
"Return the name of the container which `ITEM' belongs to.
|
|
|
|
`ITEM' is a (title . position) cons-pair.
|
|
`CONTAINERS' is a list of such.
|
|
|
|
The name is based on the results from
|
|
`csharp--imenu-get-container'."
|
|
(let ((container (csharp--imenu-get-container item containers nil)))
|
|
(if (not container)
|
|
nil
|
|
(let ((container-p1 (car (split-string (car container)))) ;; namespace
|
|
(container-p2 (cadr (split-string (car container))))) ;; class/interface
|
|
;; use p1 (namespace) when there is no p2
|
|
(if container-p2
|
|
container-p2
|
|
container-p1)))))
|
|
|
|
(defun csharp--imenu-sort (items)
|
|
"Sort an imenu-index list `ITEMS' by the string-portion."
|
|
(sort items (lambda (item1 item2)
|
|
(string< (car item1) (car item2)))))
|
|
|
|
(defun csharp--imenu-get-class-name (class namespaces)
|
|
"Gets a name for a imenu-index `CLASS'.
|
|
|
|
Result is based on its own name and `NAMESPACES' found in the same file."
|
|
(let ((namespace (csharp--imenu-get-container-name class namespaces))
|
|
(class-name (car class)))
|
|
(if (not namespace)
|
|
class-name
|
|
;; reformat to include namespace
|
|
(let* ((words (split-string class-name))
|
|
(type (car words))
|
|
(name (cadr words)))
|
|
(concat type " " namespace "." name)))))
|
|
|
|
(defun csharp--imenu-get-class-nodes (classes namespaces)
|
|
"Create a new alist with CLASSES as root nodes with NAMESPACES added.
|
|
|
|
Each class will have one imenu index-entry \"( top)\" added by
|
|
default."
|
|
|
|
(mapcar (lambda (class)
|
|
(let ((class-name (csharp--imenu-get-class-name class namespaces))
|
|
(class-pos (cdr class)))
|
|
;; construct a new alist-entry where value is itself
|
|
;; a list of alist-entries with -1- entry which the top
|
|
;; of the class itself.
|
|
(cons class-name
|
|
(list
|
|
(cons "( top )" class-pos)))))
|
|
classes))
|
|
|
|
(defun csharp--imenu-get-class-node (result item classes namespaces)
|
|
"Get the class-node in `RESULT' which an `ITEM' should be inserted into.
|
|
|
|
For this calculation, the original index items `CLASSES' and `NAMESPACES'
|
|
is needed."
|
|
(let* ((class-item (csharp--imenu-get-container item classes nil))
|
|
(class-name (csharp--imenu-get-class-name class-item namespaces)))
|
|
(assoc class-name result)))
|
|
|
|
(defun csharp--imenu-format-item-node (item type)
|
|
"Format an ITEM with a specified TYPE as an imenu item to be inserted into the index."
|
|
(cons
|
|
(concat "(" type ") " (car item))
|
|
(cdr item)))
|
|
|
|
(defun csharp--imenu-append-items-to-menu (result key name index classes namespaces)
|
|
"Formats the imenu-index using the provided values.
|
|
|
|
This is done by modifying the contents of `RESULT' in place."
|
|
;; items = all methods, all events, etc based on "type"
|
|
(let* ((items (cdr (assoc key index))))
|
|
(dolist (item items)
|
|
(let ((class-node (csharp--imenu-get-class-node result item classes namespaces))
|
|
(item-node (csharp--imenu-format-item-node item name)))
|
|
(nconc class-node (list item-node))))))
|
|
|
|
(defun csharp--imenu-transform-index (index)
|
|
"Transform an imenu INDEX based on `IMENU-GENERIC-EXPRESSION'.
|
|
|
|
The resulting structure should be based on full type-names, with
|
|
type-members nested hierarchially below its parent.
|
|
|
|
See `csharp-mode-tests.el' for examples of expected behaviour
|
|
of such transformations."
|
|
(let* ((result nil)
|
|
(namespaces (cdr (assoc "namespace" index)))
|
|
(classes (cdr (assoc "class" index)))
|
|
(class-nodes (csharp--imenu-get-class-nodes classes namespaces)))
|
|
;; be explicit about collection variable
|
|
(setq result class-nodes)
|
|
(dolist (type '(("ctor")
|
|
("method")
|
|
("method-inf" "method")
|
|
("method-abs-ext" "method")
|
|
("prop")
|
|
("prop-inf" "prop")
|
|
("field")
|
|
("event")
|
|
("indexer")))
|
|
(let* ((key (car type))
|
|
(name (car (last type))))
|
|
(csharp--imenu-append-items-to-menu result key name index classes namespaces)))
|
|
|
|
;; add enums and delegates to main result list, as own items.
|
|
;; We don't support nested types. EOS.
|
|
;;
|
|
;; This has the issue that they get reported as "function" in
|
|
;; `helm-imenu', but there's nothing we can do about that.
|
|
;; The alternative is making it a menu with -1- submenu which
|
|
;; says "( top )" but that will be very clicky...
|
|
|
|
;; before adding delegates, we need to pad the entry so that it
|
|
;; matches the "<type> <name>" signature used by all the other
|
|
;; imenu entries
|
|
(let ((delegates (cdr (assoc "delegate" index))))
|
|
(dolist (delegate delegates)
|
|
(setf (car delegate) (concat "delegate " (car delegate)))))
|
|
|
|
(dolist (type '("enum" "delegate"))
|
|
(dolist (item (cdr (assoc type index)))
|
|
(let ((item-name (csharp--imenu-get-class-name item namespaces)))
|
|
(setq result (cons (cons item-name (cdr item))
|
|
result)))))
|
|
|
|
;; sort individual sub-lists
|
|
(dolist (item result)
|
|
(when (listp (cdr item))
|
|
(setf (cdr item) (csharp--imenu-sort (cdr item)))))
|
|
|
|
;; sort main list
|
|
;; (Enums always sort last though, because they dont have
|
|
;; sub-menus)
|
|
(csharp--imenu-sort result)))
|
|
|
|
(defun csharp--imenu-create-index-function ()
|
|
"Create an imenu index."
|
|
(csharp--imenu-transform-index
|
|
(imenu--generic-function csharp--imenu-expression)))
|
|
|
|
(defun csharp--setup-imenu ()
|
|
"Set up `imenu' for `csharp-mode'."
|
|
|
|
;; 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.
|
|
;;
|
|
;; We use both.
|
|
;;
|
|
;; First we use the `imenu-generic-expression' to build a index for
|
|
;; us, but we do so inside a `imenu-create-index-function'
|
|
;; implementation which allows us to tweak the results slightly
|
|
;; before returning it to Emacs.
|
|
(setq imenu-create-index-function #'csharp--imenu-create-index-function)
|
|
(imenu-add-menubar-index))
|
|
|
|
|
|
|
|
|
|
|
|
;; ==================================================================
|
|
;; 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 on third consecutive slash.
|
|
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.
|
|
|
|
The prefix argument ARG is passed on to `self-insert-command'
|
|
when the code documentation template isn't triggered. This makes
|
|
sure that M-10 / still produces 10 consecutive slashes as expected.
|
|
|
|
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
|
|
;; ==================================================================
|
|
|
|
(defun csharp-time ()
|
|
"Return 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))))
|
|
|
|
;; ==================================================================
|
|
;; 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)
|
|
"Make `c-forward-objc-directive' a no-op in `csharp-mode'."
|
|
(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.
|
|
;;
|
|
|
|
;; verabatim copy of c-font-lock-invalid-string before it was removed
|
|
;; from emacs/cc-mode in Git commit bb591f139f0602af292c772f974dcc14dabb1deb.
|
|
|
|
(defun csharp-mode-font-lock-invalid-string ()
|
|
;; Assuming the point is after the opening character of a string,
|
|
;; fontify that char with `font-lock-warning-face' if the string
|
|
;; decidedly isn't terminated properly.
|
|
;;
|
|
;; This function does hidden buffer changes.
|
|
(let ((start (1- (point))))
|
|
(save-excursion
|
|
(and (eq (elt (parse-partial-sexp start (c-point 'eol)) 8) start)
|
|
(if (if (eval-when-compile (integerp ?c))
|
|
;; Emacs
|
|
(integerp c-multiline-string-start-char)
|
|
;; XEmacs
|
|
(characterp c-multiline-string-start-char))
|
|
;; There's no multiline string start char before the
|
|
;; string, so newlines aren't allowed.
|
|
(not (eq (char-before start) c-multiline-string-start-char))
|
|
;; Multiline strings are allowed anywhere if
|
|
;; c-multiline-string-start-char is t.
|
|
(not c-multiline-string-start-char))
|
|
(if c-string-escaped-newlines
|
|
;; There's no \ before the newline.
|
|
(not (eq (char-before (point)) ?\\))
|
|
;; Escaped newlines aren't supported.
|
|
t)
|
|
(c-put-font-lock-face start (1+ start) 'font-lock-warning-face)))))
|
|
|
|
(advice-add 'c-looking-at-inexpr-block
|
|
:around 'csharp--c-looking-at-inexpr-block-hack)
|
|
|
|
(defun csharp--c-looking-at-inexpr-block-hack (orig-fun &rest args)
|
|
(apply
|
|
(if (eq major-mode 'csharp-mode)
|
|
#'csharp--c-looking-at-inexpr-block
|
|
orig-fun)
|
|
args))
|
|
|
|
(defun csharp--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))))
|
|
|
|
(advice-add 'c-inside-bracelist-p
|
|
:around 'csharp-inside-bracelist-or-c-inside-bracelist-p)
|
|
|
|
(defun csharp-inside-bracelist-or-c-inside-bracelist-p (command &rest args)
|
|
"Run `csharp-inside-bracelist-p' if in `csharp-mode'.
|
|
|
|
Otherwise run `c-inside-bracelist-p'."
|
|
(if (eq major-mode 'csharp-mode)
|
|
(csharp-inside-bracelist-p (nth 0 args) (nth 1 args))
|
|
(apply command args)))
|
|
|
|
(defun csharp-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))
|
|
|
|
(and (c-major-mode-is 'csharp-mode)
|
|
(or
|
|
;; 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
|
|
(progn
|
|
(c-safe (c-forward-sexp -1))
|
|
(looking-at csharp-enum-decl-re))
|
|
|
|
;; type-initializers are not properly detected and
|
|
;; indented unless we help out. (no need to forward
|
|
;; when looking here, because enum-check already did
|
|
;; it!)
|
|
(looking-at csharp-type-initializer-statement-re))))
|
|
|
|
(setq bracepos (c-down-list-forward (point)))
|
|
(or
|
|
(not (c-crosses-statement-barrier-p (point)
|
|
(- bracepos 2)))
|
|
;; this little hack (combined with the regexp-check above)
|
|
;; fixes indentation for all type-initializers.
|
|
(c-major-mode-is 'csharp-mode)))
|
|
(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)))
|
|
|
|
;;; Compilation regexps
|
|
;; When invoked by MSBuild, csc’s errors look like this:
|
|
;; subfolder\file.cs(6,18): error CS1006: Name of constructor must
|
|
;; match name of class [c:\Users\user\project.csproj]
|
|
|
|
(defun csharp--compilation-error-file-resolve ()
|
|
"Resolve an msbuild error to a (filename . dirname) cons cell."
|
|
;; http://stackoverflow.com/a/18049590/429091
|
|
(cons (match-string 1) (file-name-directory (match-string 4))))
|
|
|
|
(defconst csharp-compilation-re-msbuild-error
|
|
(concat
|
|
"^[[:blank:]]*\\(?:[[:digit:]]+>\\)?"
|
|
"\\([^(\r\n)]+\\)(\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?): "
|
|
"error [[:alnum:]]+: [^\r\n]+\\[\\([^]\r\n]+\\)\\]$")
|
|
"Regexp to match compilation error from msbuild.")
|
|
|
|
(defconst csharp-compilation-re-msbuild-warning
|
|
(concat
|
|
"^[[:blank:]]*\\(?:[[:digit:]]+>\\)?"
|
|
"\\([^(\r\n)]+\\)(\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?): "
|
|
"warning [[:alnum:]]+: [^\r\n]+\\[\\([^]\r\n]+\\)\\]$")
|
|
"Regexp to match compilation warning from msbuild.")
|
|
|
|
;; Notes on xbuild and devenv commonalities
|
|
;;
|
|
;; These regexes were tailored for xbuild, but apart from the concurrent
|
|
;; build-marker ("1>") they share exactly the same match-markers.
|
|
;;
|
|
;; If we don't exclude the match-markers explicitly, these regexes
|
|
;; will also be used to match for devenv as well, including the build-marker
|
|
;; in the file-name, causing the lookup to fail.
|
|
;;
|
|
;; So if we don't want devenv to fail, we actually need to handle it in our
|
|
;; xbuild-regexes, but then we automatically get devenv-support for free.
|
|
|
|
(defconst csharp-compilation-re-xbuild-error
|
|
(concat
|
|
"^[[:blank:]]*\\(?:[[:digit:]]+>\\)?"
|
|
"\\([^(\r\n)]+\\)(\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?"
|
|
;; handle weird devenv output format with 4 numbers, not 2 by having optional
|
|
;; extra capture-groups.
|
|
"\\(?:,\\([0-9]+\\)\\)*): "
|
|
"error [[:alnum:]]+: .+$")
|
|
"Regexp to match compilation error from xbuild.")
|
|
|
|
(defconst csharp-compilation-re-xbuild-warning
|
|
(concat
|
|
"^[[:blank:]]*\\(?:[[:digit:]]+>\\)?"
|
|
"\\([^(\r\n)]+\\)(\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?"
|
|
;; handle weird devenv output format with 4 numbers, not 2 by having optional
|
|
;; extra capture-groups.
|
|
"\\(?:,\\([0-9]+\\)\\)?*): "
|
|
"warning [[:alnum:]]+: .+$")
|
|
"Regexp to match compilation warning from xbuild.")
|
|
|
|
(eval-after-load 'compile
|
|
(lambda ()
|
|
(dolist
|
|
(regexp
|
|
`((xbuild-error
|
|
,csharp-compilation-re-xbuild-error
|
|
1 2 3 2)
|
|
(xbuild-warning
|
|
,csharp-compilation-re-xbuild-warning
|
|
1 2 3 1)
|
|
(msbuild-error
|
|
,csharp-compilation-re-msbuild-error
|
|
csharp--compilation-error-file-resolve
|
|
2
|
|
3
|
|
2
|
|
nil
|
|
(1 compilation-error-face)
|
|
(4 compilation-error-face))
|
|
(msbuild-warning
|
|
,csharp-compilation-re-msbuild-warning
|
|
csharp--compilation-error-file-resolve
|
|
2
|
|
3
|
|
1
|
|
nil
|
|
(1 compilation-warning-face)
|
|
(4 compilation-warning-face))))
|
|
(add-to-list 'compilation-error-regexp-alist-alist regexp)
|
|
(add-to-list 'compilation-error-regexp-alist (car regexp)))))
|
|
|
|
|
|
;;; 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 . 0)
|
|
(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 . 0)
|
|
(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 . +)
|
|
))
|
|
))
|
|
|
|
|
|
;;;###autoload
|
|
(define-derived-mode csharp-mode prog-mode "C#"
|
|
"Major mode for editing C# code.
|
|
|
|
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:
|
|
`prog-mode-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
|
|
....your own code here...
|
|
)
|
|
(add-hook 'csharp-mode-hook 'my-csharp-mode-fn t)
|
|
|
|
|
|
The function above is just a suggestion.
|
|
|
|
|
|
Imenu Integration
|
|
===============================
|
|
|
|
Check the menubar for menu entries for Imenu; it 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}"
|
|
(make-local-variable 'beginning-of-defun-function)
|
|
(make-local-variable 'end-of-defun-function)
|
|
(c-initialize-cc-mode t)
|
|
|
|
;; 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)
|
|
|
|
;; `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)
|
|
|
|
;; Use our predefined "C#" style unless a file local or default
|
|
;; style is found. This is done by rebinding `c-default-style'
|
|
;; during the `c-common-init' call. 'c-common-init' will initialize
|
|
;; the buffer's style using the value of `c-default-style'.
|
|
(let ((c-default-style (if (or c-file-style
|
|
(stringp c-default-style)
|
|
(assq 'csharp-mode c-default-style))
|
|
c-default-style
|
|
"C#")))
|
|
;; `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))
|
|
|
|
(define-key csharp-mode-map (kbd "/") 'csharp-maybe-insert-codedoc)
|
|
|
|
;; 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)
|
|
|
|
;; 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)
|
|
|
|
;; Speedbar handling
|
|
(when (fboundp 'speedbar-add-supported-extension)
|
|
(speedbar-add-supported-extension '(".cs"))) ;; idempotent
|
|
|
|
(c-update-modeline)
|
|
|
|
;; maybe do imenu scan after hook returns
|
|
(when csharp-want-imenu
|
|
(csharp--setup-imenu))
|
|
|
|
;; The paragraph-separate variable was getting stomped by
|
|
;; other hooks, so it must reside here.
|
|
(setq-local paragraph-separate
|
|
"[ \t]*\\(//+\\|\\**\\)\\([ \t]+\\|[ \t]+<.+?>\\)$\\|^\f")
|
|
|
|
(setq-local 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)
|
|
|
|
(set (make-local-variable 'syntax-propertize-function)
|
|
'csharp-mode-syntax-propertize-function)
|
|
|
|
;; required since Emacs git master
|
|
;; https://github.com/emacs-mirror/emacs/commit/edcdf64960a2ab4e8d9ce4419874e43b6d3ccee4
|
|
;;
|
|
;; Basically syntax-propertize-function is a construct which belongs
|
|
;; to font-lock. But correct indentation depends on
|
|
;; syntax-properties of the text, and that should ideally be
|
|
;; independent of font-lock being activated or not.
|
|
;;
|
|
;; For csharp-mode, this means that with font-lock disabled, we wont
|
|
;; have our syntax-properties set correctly, and indentation will
|
|
;; suffer.
|
|
;;
|
|
;; To patch our way around this, we issue a syntax-propertize call
|
|
;; manually, font-lock enabled or not.
|
|
(with-silent-modifications
|
|
(csharp-mode-syntax-propertize-function (point-min) (point-max))))
|
|
|
|
(provide 'csharp-mode)
|
|
|
|
;;; csharp-mode.el ends here
|