branch: externals/csharp-mode commit e2c9f85336c812eb96740c9b9a3bfb9b814cc7c6 Author: Jostein Kjønigsen <jost...@kjonigsen.net> Commit: Jostein Kjønigsen <jost...@kjonigsen.net>
New imenu-implementation for csharp-mode. **Main features:** * all data collected through single scan, using standard regexpes. * most code is 100% pure and functional, with near zero state kept. (Good bye FSM) * orders of magnitudes faster than before * orders of magnitudes more maintainable than before (you can make feature-requests now!) * less hierarchies, less clicky (for those using the menus) * indexes "everything": fields, props, methods, ctors, indexers, classes, enums, etc. **Inherent limitations:** * Does not support/resolve names correctly for nested types. At all. Will not be supported. * Some imenu targets are hard to pick by regexp without getting tons of noise. For this reason methods and props does *require* access-modifiers to be indexed. Other types are less strict. **Otherwise** This commit fixes or addresses the following github issues: * https://github.com/josteink/csharp-mode/issues/71 * https://github.com/josteink/csharp-mode/issues/68 * https://github.com/josteink/csharp-mode/issues/67 * https://github.com/josteink/csharp-mode/issues/60 --- csharp-mode-tests.el | 154 ++- csharp-mode.el | 1882 ++++++--------------------- test-files/imenu-interface-property-test.cs | 12 +- test-files/imenu-namespace-test.cs | 6 +- 4 files changed, 519 insertions(+), 1535 deletions(-) diff --git a/csharp-mode-tests.el b/csharp-mode-tests.el index b3994a7..7a8a0c6 100644 --- a/csharp-mode-tests.el +++ b/csharp-mode-tests.el @@ -165,84 +165,124 @@ (equal expected (match-string 1))))) (kill-buffer buffer)))) -(ert-deftest imenu-parsing-supports-default-values () - (dolist (test-case - '(;; should support bools - ("(bool a, bool b = true)" "(bool, bool)") - ("(bool a=true, bool b)" "(bool, bool)") - ;; should support strings - ("(string a, string b = \"quoted string\")" "(string, string)") - ("(string a = \"quoted string\", string b)" "(string, string)") - ;; should support chars - ("(char a, char b = 'b')" "(char, char)") - ("(char a = 'a', char b)" "(char, char)") - ;; should support self-object-access - ("(object o = Const)" "(object)") - ;; should support other-object-access - ("(object o = ConstObject.Const)" "(object)") - )) - (let* ((test-value (car test-case)) - (expected-value (cadr test-case)) - (result (csharp--imenu-remove-param-names-from-paramlist test-value))) - (should (equal expected-value result))))) - (defmacro def-imenutest (testname filename index &rest body) `(ert-deftest ,testname () (let* ((find-file-hook nil) ;; avoid vc-mode file-hooks when opening! (buffer (find-file-read-only ,filename)) - (,index (csharp--imenu-create-index-helper nil "" t t)) ;; same line as in `csharp-imenu-create-index'. - ) + (,index (csharp--imenu-create-index-function))) ,@body (kill-buffer buffer)))) +(defun imenu-get-item (index haystack) + (let ((result)) + (dolist (item index) + (when (not result) + (let ((name (car item)) + (value (cdr item))) + (if (string-prefix-p haystack name) + (setq result item) + (when (listp value) + (setq result (imenu-get-item value haystack))))))) + result)) + (def-imenutest imenu-parsing-supports-generic-parameters "./test-files/imenu-generics-test.cs" imenu-index - (let* ((class-entry (cadr imenu-index)) - (class-entries (cdr class-entry)) - (imenu-items (mapconcat 'car class-entries " "))) - - ;; ("(top)" "method void NoGeneric(this IAppBuilder, params object[])" "method void OneGeneric<T>(this IAppBuilder, params object[])" "method void TwoGeneric<T1,T2>(this IAppBuilder, params object[])" "(bottom)") - (should (string-match-p "NoGeneric" imenu-items)) - (should (string-match-p "OneGeneric<T>" imenu-items)) - (should (string-match-p "TwoGeneric<T1,T2>" imenu-items)))) + (dolist (item '("NoGeneric(" "OneGeneric<T>(" "TwoGeneric<T1,T2>(")) + (should (imenu-get-item imenu-index (concat "(method) " item))))) (def-imenutest imenu-parsing-supports-comments "./test-files/imenu-comment-test.cs" imenu-index - (let* ((class-entry (cadr imenu-index)) - (class-entries (cdr class-entry)) - (imenu-items (mapconcat 'car class-entries " "))) - (should (string-match-p "HasNoComment" imenu-items)) - (should (string-match-p "HasComment" imenu-items)) - (should (string-match-p "CommentedToo" imenu-items)))) + (dolist (item '("HasNoComment(" "HasComment(" "CommentedToo(")) + (should (imenu-get-item imenu-index (concat "(method) " item))))) (def-imenutest imenu-parsing-supports-explicit-interface-properties "./test-files/imenu-interface-property-test.cs" imenu-index - (let* ((class-entry (cl-caddr imenu-index)) - (class-entries (cdr class-entry)) - (imenu-items (mapconcat 'car class-entries " "))) - (should (string-match-p "prop IIMenuTest.InterfaceString" imenu-items)))) + (should (imenu-get-item imenu-index "(prop) IImenuTest.InterfaceString"))) (def-imenutest imenu-parsing-supports-explicit-interface-methods "./test-files/imenu-interface-property-test.cs" imenu-index - (let* ((class-entry (cl-caddr imenu-index)) - (class-entries (cdr class-entry)) - (imenu-items (mapconcat 'car class-entries " "))) - (should (string-match-p "method string IIMenuTest.MethodName" imenu-items)))) - -(def-imenutest imenu-parsing-supports-namespaces - "./test-files/imenu-namespace-test.cs" imenu-index - (let* ((ns-entry (cadr imenu-index)) - (ns-item (car ns-entry))) - (should (string-match-p "namespace ImenuTest" ns-item)))) + (should (imenu-get-item imenu-index "(method) IImenuTest.MethodName"))) (def-imenutest imenu-parsing-provides-types-with-namespace-names "./test-files/imenu-namespace-test.cs" imenu-index - (let* ((ns-entry (cadr imenu-index)) - (ns-items (cdr ns-entry)) - (imenu-items (mapconcat 'car ns-items " "))) - (should (string-match-p "interface ImenuTest.ImenuTestInterface" imenu-items)) - (should (string-match-p "class ImenuTest.ImenuTestClass" imenu-items)) - (should (string-match-p "enum ImenuTest.ImenuTestEnum" imenu-items)))) + (should (imenu-get-item imenu-index "class ImenuTest.ImenuTestClass")) + (should (imenu-get-item imenu-index "interface ImenuTest.ImenuTestInterface")) + (should (imenu-get-item imenu-index "enum ImenuTest.ImenuTestEnum"))) + +(ert-deftest imenu-indexing-resolves-correct-container () + (let* ((testcase-no-namespace '( ("class Global" . 10) + (("namespace_a" . 20) ("namespace_b" . 30)) + nil)) + (testcase-namespace-a '( ("class A" . 10) + (("namespace_a" . 0) ("namespace_b" . 30)) + "namespace_a")) + (testcase-namespace-b '( ("class B" . 40) + (("namespace_a" . 0) ("namespace_b" . 30)) + "namespace_b")) + (testcases (list testcase-no-namespace + testcase-namespace-a + testcase-namespace-b))) + (dolist (testcase testcases) + (let ((class (car testcase)) + (namespaces (cadr testcase)) + (expected (caddr testcase))) + (should (equal expected + (csharp--imenu-get-container-name class namespaces))))))) + +(ert-deftest imenu-indexing-resolves-correct-name () + (let* ((testcase-no-namespace '( ("class Global" . 10) + (("namespace_a" . 20) ("namespace_b" . 30)) + "class Global")) + (testcase-namespace-a '( ("class A" . 10) + (("namespace_a" . 0) ("namespace_b" . 30)) + "class namespace_a.A")) + (testcase-namespace-b '( ("class B" . 40) + (("namespace_a" . 0) ("namespace_b" . 30)) + "class namespace_b.B")) + (testcases (list testcase-no-namespace + testcase-namespace-a + testcase-namespace-b))) + (dolist (testcase testcases) + (let ((class (car testcase)) + (namespaces (cadr testcase)) + (expected (caddr testcase))) + (should (equal expected + (csharp--imenu-get-class-name class namespaces))))))) + +(ert-deftest imenu-transforms-index-correctly () + ;; this test-case checks for the following aspects of the transformation: + ;; 1. hierarchial nesting + ;; 2. sorting of members + (should (equalp + '(("class A" . (("( top )" . 20) + ("(method) method_a1" . 30) + ("(method) method_a2" . 25))) + ("class B" . (("( top )" . 0) + ("(method) method_b1" . 15) + ("(method) method_b2" . 10)))) + + (csharp--imenu-transform-index + '(("class" . (("class B" . 0) ("class A" . 20))) + ("method" . (("method_b2" . 10) ("method_b1" . 15) + ("method_a2" . 25) ("method_a1" . 30)))))))) + +(ert-deftest imenu-transforms-index-correctly-with-namespaces () + ;; this test-case checks for the following aspects of the transformation: + ;; 1. hierarchial nesting + ;; 2. sorting of members + (should (equalp + '(("class ns.A" . (("( top )" . 20) + ("(method) method_a1" . 30) + ("(method) method_a2" . 25))) + ("class ns.B" . (("( top )" . 0) + ("(method) method_b1" . 15) + ("(method) method_b2" . 10)))) + + (csharp--imenu-transform-index + '(("namespace" . (("ns" . 0))) + ("class" . (("class B" . 0) ("class A" . 20))) + ("method" . (("method_b2" . 10) ("method_b1" . 15) + ("method_a2" . 25) ("method_a1" . 30)))))))) (defvar csharp-hook1 nil) (defvar csharp-hook2 nil) diff --git a/csharp-mode.el b/csharp-mode.el index c8461a3..69725a9 100644 --- a/csharp-mode.el +++ b/csharp-mode.el @@ -5,10 +5,10 @@ ;; Maintainer : Jostein Kjønigsen <jost...@gmail.com> ;; Created : Feburary 2005 ;; Modified : 2016 -;; Version : 0.8.13 +;; Version : 0.9.0 ;; Keywords : c# languages oop mode ;; X-URL : https://github.com/josteink/csharp-mode -;; Last-saved : 2016-Feb-17 +;; Last-saved : 2016-May-28 ;; ;; This program is free software; you can redistribute it and/or modify @@ -287,9 +287,10 @@ ;; 0.8.12 2016 January 6th ;; - Various fixes and improvements for imenu indexing. ;; -;; 0.8.13 2016 ... +;; 0.9.0 2016 July...? ;; - Fix issues with compilation-mode and lines with arrays. ;; - Fontification of compiler directives. +;; - Much faster, completely rewritten imenu-implementation. ;; (require 'cc-mode) @@ -510,96 +511,6 @@ to work properly with code that includes attributes. ))) - - -(defun csharp-lineup-region (langelem) - "Indent all #region and #endregion blocks inline with code while -retaining normal column-zero indention for #if and the other -processing blocks. - -To use this indenting just put the following in your emacs file: - (c-set-offset 'cpp-macro 'csharp-lineup-region) - -An alternative is to use `csharp-lineup-if-and-region'. -" - - (save-excursion - (back-to-indentation) - (if (re-search-forward "#\\(end\\)?region" (c-point 'eol) [0]) 0 [0]))) - - - - - -(defun csharp-lineup-if-and-region (langelem) - - "Indent all #region/endregion blocks and #if/endif blocks inline -with code while retaining normal column-zero indention for any -other processing blocks. - -To use this indenting just put the following in your emacs file: - (c-set-offset 'cpp-macro 'csharp-lineup-if-and-region) - -Another option is to use `csharp-lineup-region'. - -" - (save-excursion - (back-to-indentation) - (if (re-search-forward "#\\(\\(end\\)?\\(if\\|region\\)\\|else\\)" (c-point 'eol) [0]) 0 [0]))) - - - - -(defun csharp-in-literal (&optional lim detect-cpp) - "Return the type of literal point is in, if any. -Basically this works like `c-in-literal' except it doesn't -use or fill the cache (`c-in-literal-cache'). - -The return value is a symbol: `c' if in a C-style comment, `c++' -if in a C++ style comment, `string' if in a string literal, -`pound' if DETECT-CPP is non-nil and in a preprocessor line, or -nil if somewhere else. Optional LIM is used as the backward -limit of the search. If omitted, or nil, `c-beginning-of-syntax' -is used. - -Note that this function might do hidden buffer changes. See the -comment at the start of cc-engine.el for more info." - - (let ((rtn - (save-excursion - (let* ((pos (point)) - (lim (or lim (progn - (c-beginning-of-syntax) - (point)))) - (state (parse-partial-sexp lim pos))) - (csharp-log 4 "parse lim(%d) state: %s" lim (prin1-to-string state)) - (cond - ((elt state 3) - (csharp-log 4 "in literal string (%d)" pos) - 'string) - ((elt state 4) - (csharp-log 4 "in literal comment (%d)" pos) - (if (elt state 7) 'c++ 'c)) - ((and detect-cpp (c-beginning-of-macro lim)) 'pound) - (t nil)))))) - rtn)) - - -(defun csharp-is-square-parentasis-block-p () - "Attempts to safely assess if the current point is at the opening of -a square parentasis block [ ... ]." - (let* ((start (point)) ;; variables 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))) ;; close square - ) - - - ;; ================================================================== ;; end of csharp-mode utility and feature defuns ;; ================================================================== @@ -1534,216 +1445,70 @@ Most other csharp functions are not instrumented. ;; moving ;; alist of regexps for various structures in a csharp source file. -(eval-and-compile - (defconst csharp--regexp-alist - (list - - `(func-start - ,(concat - "^[ \t\n\r\f\v]*" ;; leading whitespace - "\\(" - "public\\(?: static\\)?\\|" ;; 1. access modifier - "private\\(?: static\\)?\\|" - "protected\\(?: internal\\)?\\(?: static\\)?\\|" - "static\\|" - "\\)" - "[ \t\n\r\f\v]+" - "\\(?:override[ \t\n\r\f\v]+\\)?" ;; optional - "\\([[:alpha:]_][^\t\(\n]+\\)" ;; 2. return type - possibly generic - "[ \t\n\r\f\v]+" - "\\(" ;; 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]*" - )) - - `(ctor-start - ,(concat - "^[ \t\n\r\f\v]*" ;; leading whitespace - "\\(" - "public\\|" ;; 1. access modifier - "private\\|" - "protected\\(?: internal\\)?\\|" - "static\\|" - "\\)" - "[ \t\n\r\f\v]+" - "\\([[:alpha:]_][[:alnum:]_]*\\)" ;; 2. name of ctor - "[ \t\n\r\f\v]*" - "\\(\([^\)]*\)\\)" ;; 3. parameter list (with parens) - "\\(" ;; 4. ctor dependency - "[ \t\n]*:[ \t\n]*" ;; colon - "\\(?:this\\|base\\)" ;; this or base - "[ \t\n\r\f\v]*" - "\\(?:\([^\)]*\)\\)" ;; parameter list (with parens) - "\\)?" ;; possibly - "[ \t\n\r\f\v]*" - )) - - - `(using-stmt - ,(concat - ;;"^[ \t\n\r\f\v]*" - "\\(\\<using\\)" - "[ \t\n\r\f\v]+" - "\\(?:" - "\\([[:alpha:]_][[:alnum:]_]*\\)" ;; alias - "[ \t\n\r\f\v]*" - "=" - "[ \t\n\r\f\v]*" - "\\)?" - "\\(" - "\\(?:[A-Za-z_][[:alnum:]]*\\.\\)*" - "[A-Za-z_][[:alnum:]]*" - "\\)" ;; imported namespace - "[ \t\n\r\f\v]*" - ";" - )) - - `(class-start - ,(concat - "^[ \t]*" ;; leading whitespace - "\\(" - "public\\(?: \\(?:static\\|sealed\\)\\)?[ \t]+\\|" ;; access modifiers - "internal\\(?: \\(?:static\\|sealed\\)\\)?[ \t]+\\|" - "static\\(?: internal\\)?[ \t]+\\|" - "sealed\\(?: internal\\)?[ \t]+\\|" - "static[ \t]+\\|" - "sealed[ \t]+\\|" - "\\)" - "\\(\\(?:partial[ \t]+\\)?class\\|struct\\)" ;; class/struct keyword - "[ \t]+" - "\\([[:alpha:]_][[:alnum:]]*\\)" ;; type name - "\\(" - "[ \t\n]*:[ \t\n]*" ;; colon - "\\([[:alpha:]_][^\t\(\n]+\\)" ;; base / intf - poss generic - "\\(" - "[ \t\n]*,[ \t\n]*" - "\\([[:alpha:]_][^\t\(\n]+\\)" ;; addl interface - poss generic - "\\)*" - "\\)?" ;; possibly - "[ \t\n\r\f\v]*" - )) - - `(genclass-start - ,(concat - "^[ \t]*" ;; leading whitespace - "\\(" - "public\\(?: \\(?:static\\|sealed\\)\\)?[ \t]+\\|" ;; access modifiers - "internal\\(?: \\(?:static\\|sealed\\)\\)?[ \t]+\\|" - "static\\(?: internal\\)?[ \t]+\\|" - "sealed\\(?: internal\\)?[ \t]+\\|" - "static[ \t]+\\|" - "sealed[ \t]+\\|" - "\\)" - "\\(\\(?:partial[ \t]+\\)?class\\|struct\\)" ;; class/struct keyword - "[ \t]+" - "\\([[:alpha:]_][[:alnum:]_<>, ]*\\)" ;; type name (generic) - "\\(" - "[ \t\n]*:[ \t\n]*" ;; colon - "\\([[:alpha:]_][^\t\(\n]+\\)" ;; base / intf - poss generic - "\\(" - "[ \t\n]*,[ \t\n]*" - "\\([[:alpha:]_][^\t\(\n]+\\)" ;; addl interface - poss generic - "\\)*" - "\\)?" ;; possibly - "[ \t\n\r\f\v]*" - )) - - `(enum-start - ,(concat - "^[ \t\f\v]*" ;; leading whitespace - "\\(" - "public[ \t]+enum\\|" ;; enum keyword - "enum" - "\\)" - "[ \t\n\r\f\v]+" - "\\([[:alpha:]_][[:alnum:]_]*\\)" ;; name of enum - "[ \t\n\r\f\v]*" - "\\(:[ \t\n\r\f\v]*" - "\\(" - "sbyte\\|byte\\|short\\|ushort\\|int\\|uint\\|long\\|ulong" - "\\)" - "[ \t\n\r\f\v]*" - "\\)?" ;; possibly - "[ \t\n\r\f\v]*" - )) - - - `(intf-start - ,(concat - "^[ \t\f\v]*" ;; leading whitespace - "\\(?:" - "public\\|internal\\|" ;; access modifier - "\\)" - "[ \t\n\r\f\v]+" - "\\(interface\\)" - "[ \t\n\r\f\v]+" - "\\([[:alpha:]_][[:alnum:]_]*\\)" ;; name of interface - "[ \t\n\r\f\v]*" - )) - - `(prop-start - ,(concat - "^[ \t\f\v]*" ;; leading whitespace - "\\(" - "public\\|" ;; 1: access modifier - "private\\|" - "protected internal\\|" - "internal protected\\|" - "internal\\|" - "\\)" - "[ \t\n\r\f\v]+" - "\\([[:alpha:]_][^\t\(\n]+\\)" ;; 2: return type - possibly generic - "[ \t\n\r\f\v]+" - "\\(" - "\\(?:[A-Za-z_][[:alnum:]_]*\\.\\)*" ;; possible prefix interface - "[[:alpha:]_][[:alnum:]_]*" ;; 3: name of prop - "\\)" - "[ \t\n\r\f\v]*" - )) - - `(indexer-start - ,(concat - "^[ \t\f\v]*" ;; leading whitespace - "\\(" - "public\\|" ;; 1: access modifier - "private\\|" - "protected internal\\|" - "internal protected\\|" - "internal\\|" - "\\)" - "[ \t\n\r\f\v]+" - "\\([[:alpha:]_][^\t\(\n]+\\)" ;; 2: return type - possibly generic - "[ \t\n\r\f\v]+" - "\\(this\\)" ;; 3: 'this' keyword - "[ \t\n\r\f\v]*" - "\\[" ;; open square bracket - "[ \t\n\r\f\v]*" - "\\([^\]]+\\)" ;; 4: index type - "[ \t\n\r\f\v]+" - "[[:alpha:]_][[:alnum:]_]*" ;; index name - a simple identifier - "\\]" ;; closing sq bracket - "[ \t\n\r\f\v]*" - )) - - `(namespace-start - ,(concat - "^[ \t\f\v]*" ;; leading whitespace - "\\(namespace\\)" - "[ \t\n\r\f\v]+" - "\\(" - "\\(?:[A-Za-z_][[:alnum:]_]*\\.\\)*" ;; name of namespace - "[A-Za-z_][[:alnum:]]*" - "\\)" - "[ \t\n\r\f\v]*" - )) - - ))) +(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) @@ -1819,107 +1584,12 @@ See also, `csharp-move-fwd-to-end-of-defun'. (goto-char found)))))) -(defun csharp--on-defun-close-curly-p () - "return t when point is on the close-curly of a method." - (and (looking-at "}") - (save-excursion - (and - (progn (forward-char) (forward-sexp -1) t) - (not (looking-back (csharp--regexp 'class-start) nil)) - (not (looking-back (csharp--regexp 'namespace-start) nil)) - (looking-back (csharp--regexp 'func-start) nil))))) - -(defun csharp--on-ctor-close-curly-p () - "return t when point is on the close-curly of a constructor." - (and (looking-at "}") - (save-excursion - (and - (progn (forward-char) (forward-sexp -1) t) - (looking-back (csharp--regexp 'ctor-start) nil))))) - -(defun csharp--on-class-close-curly-p () - "return t when point is on the close-curly of a class or struct." - (and (looking-at "}") - (save-excursion - (and - (progn (forward-char) (forward-sexp -1) t) - (not (looking-back (csharp--regexp 'namespace-start) nil)) - (looking-back (csharp--regexp 'class-start) nil))))) - -(defun csharp--on-intf-close-curly-p () - "return t when point is on the close-curly of an interface." - (and (looking-at "}") - (save-excursion - (and - (progn (forward-char) (forward-sexp -1) t) - (looking-back (csharp--regexp 'intf-start) nil))))) - -(defun csharp--on-enum-close-curly-p () - "return t when point is on the close-curly of an enum." - (and (looking-at "}") - (save-excursion - (and - (progn (forward-char) (forward-sexp -1) t) - (looking-back (csharp--regexp 'enum-start) nil))))) - -(defun csharp--on-namespace-close-curly-p () - "return t when point is on the close-curly of a namespace." - (and (looking-at "}") - (save-excursion - (and - (progn (forward-char) (forward-sexp -1) t) - (looking-back (csharp--regexp 'namespace-start) nil))))) - -(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--on-genclass-open-curly-p () - "return t when point is on the open-curly of a generic class." - (and (looking-at "{") - (looking-back (csharp--regexp 'genclass-start) nil))) - -(defun csharp--on-namespace-open-curly-p () - "return t when point is on the open-curly of a namespace." - (and (looking-at "{") - (looking-back (csharp--regexp 'namespace-start) nil))) - -(defun csharp--on-ctor-open-curly-p () - "return t when point is on the open-curly of a ctor." - (and (looking-at "{") - (looking-back (csharp--regexp 'ctor-start) nil))) - -(defun csharp--on-intf-open-curly-p () - "return t when point is on the open-curly of a interface." - (and (looking-at "{") - (looking-back (csharp--regexp 'intf-start) nil))) - -(defun csharp--on-prop-open-curly-p () - "return t when point is on the open-curly of a property." - (and (looking-at "{") - (not (looking-back (csharp--regexp 'class-start) nil)) - (looking-back (csharp--regexp 'prop-start) nil))) - -(defun csharp--on-indexer-open-curly-p () - "return t when point is on the open-curly of a C# indexer." - (and (looking-at "{") - (looking-back (csharp--regexp 'indexer-start) nil))) - -(defun csharp--on-enum-open-curly-p () - "return t when point is on the open-curly of a interface." - (and (looking-at "{") - (looking-back (csharp--regexp 'enum-start) nil))) - - (defun csharp-move-fwd-to-end-of-defun () "Moves forward to the close-curly that defines the end of the enclosing @@ -2077,1076 +1747,348 @@ to the beginning of the prior namespace. ;; ================================================================== ;;; imenu stuff -;; define some advice for menu construction. - -;; The way imenu constructs menus from the index alist, in -;; `imenu--split-menu', is ... ah ... perplexing. If the csharp -;; create-index fn returns an ordered menu, and the imenu "sort" fn has -;; been set to nil, imenu still sorts the menu, according to the rule -;; that all submenus must appear at the top of any menu. Why? I don't -;; know. This advice disables that weirdness in C# buffers. - -(defadvice imenu--split-menu (around - csharp--imenu-split-menu-patch - activate compile) - ;; This advice will run in all buffers. Let's may sure we - ;; actually execute the important bits only when a C# buffer is active. - (if (and (string-match "\\.[Cc][Ss]$" (file-relative-name buffer-file-name)) - (boundp 'csharp-want-imenu) - csharp-want-imenu) - (let ((menulist (copy-sequence menulist)) - keep-at-top) - (if (memq imenu--rescan-item menulist) - (setq keep-at-top (list imenu--rescan-item) - menulist (delq imenu--rescan-item menulist))) - ;; This is the part from the original imenu code - ;; that puts submenus at the top. huh? why? - ;; -------------------------------------------- - ;; (setq tail menulist) - ;; (dolist (item tail) - ;; (when (imenu--subalist-p item) - ;; (push item keep-at-top) - ;; (setq menulist (delq item menulist)))) - (if imenu-sort-function - (setq menulist (sort menulist imenu-sort-function))) - (if (> (length menulist) imenu-max-items) - (setq menulist - (mapcar - (lambda (menu) - (cons (format "From: %s" (caar menu)) menu)) - (imenu--split menulist imenu-max-items)))) - (setq ad-return-value - (cons title - (nconc (nreverse keep-at-top) menulist)))) - ;; else - ad-do-it)) - - -;; -;; I used this to examine the performance of the imenu scanning. -;; It's not necessary during normal operation. -;; -;; (defun csharp-imenu-begin-profile () -;; "turn on profiling" -;; (interactive) -;; (let ((fns '(csharp--on-class-open-curly-p -;; csharp--on-namespace-open-curly-p -;; csharp--on-ctor-open-curly-p -;; csharp--on-enum-open-curly-p -;; csharp--on-intf-open-curly-p -;; csharp--on-prop-open-curly-p -;; csharp--on-indexer-open-curly-p -;; csharp--on-defun-open-curly-p -;; csharp--imenu-create-index-helper -;; looking-back -;; looking-at))) -;; (if (fboundp 'elp-reset-all) -;; (elp-reset-all)) -;; (mapc 'elp-instrument-function fns))) - - - -(defun csharp--imenu-remove-param-names-from-paramlist (s) - "The input string S is a parameter list, of the form seen in a -C# method. TYPE1 NAME1 [, TYPE2 NAME2 ...] - -This fn returns a string of the form TYPE1 [, TYPE2...] - -Upon entry, it's assumed that the parens included in S. - -" - (if (string= s "()") - s - (save-match-data - (let* (new - (state 0) ;; 0 => ws, 1=>slurping param... - c - cs - quoting - nesting - need-type - ix2 - (s2 (substring s 1 -1)) - (len (length s2)) - (i (1- len))) - - (while (> i 0) - (setq c (aref s2 i) ;; current character - cs (char-to-string c)) ;; s.t. as a string - - (cond - - ;; backing over whitespace "after" the param - ((= state 0) - (cond - ;; more ws. = is equal to whitespace in the sense that its follows a param-name. - ((string-match "[ \t\f\v\n\r=]" cs) - t) - ((string-match "[\"']" cs) - ;; a quote means we're probably dealing with a stringy default-value - ;; back out until we're back into unquoted context - (setq quoting cs - state 5)) - ;; a legal char for an identifier - ((string-match "[A-Za-z_0-9]" cs) - (setq state 1)) - (t - (error "unexpected char (A)")))) - - - ;; slurping param name - ((= state 1) - (cond - ;; ws signifies the end of the param - ((string-match "[ \t\f\v\n\r]" cs) - (setq state 2)) - ((string-match "[=]" cs) - ;; = means what we slurped was a default-value for a param - ;; go back to slurping param-name - (setq state 0)) - ;; a legal char for an identifier - ;; (or . for object-access in default value) - ((string-match "[A-Za-z_0-9\.]" cs) - t) - (t - (error "unexpected char (B)")))) - - - ;; ws between typespec and param name - ((= state 2) - (cond - ((string-match "[ \t\f\v\n\r]" cs) - t) - ((string-match "[=]" cs) - ;; = means what we slurped was a default-value for a param - ;; go back to slurping param-name - (setq state 0)) - ;; non-ws indicates the type spec is beginning - (t - (cl-incf i) - (setq state 3 - need-type nil - nesting 0 - ix2 i)))) - - - ;; slurping type - ((= state 3) - (cond - ((= ?> c) (cl-incf nesting)) - ((= ?< c) - (cl-decf nesting) - (setq need-type t)) - - ;; ws or comma maybe signifies the end of the typespec - ((string-match "[ \t\f\v\n\r,]" cs) - (if (and (= nesting 0) (not need-type)) - (progn - (setq new (cons (substring s2 (1+ i) ix2) new)) - (setq state - (if (= c ?,) 0 4))))) - - ((string-match "[A-Za-z_0-9]" cs) - (setq need-type nil)))) - - - ;; awaiting comma or b-o-s - ((= state 4) - (cond - - ((= ?, c) - (if (= nesting 0) - (setq state 0))) - - ((string-match "[ \t\f\v\n\r]" cs) - t) - - ((= 93 c) (cl-incf nesting)) ;; sq brack - ((= 91 c) ;; open sq brack - (cl-decf nesting)) - - ;; handle this (extension methods), out, ref, params - ((and (>= i 5) - (string= (substring s2 (- i 5) (1+ i)) "params")) - (setf (car new) (concat "params " (car new))) - (setq i (- i 5))) - - ((and (>= i 3) - (string= (substring s2 (- i 3) (1+ i)) "this")) - (setf (car new) (concat "this " (car new))) - (setq i (- i 3))) - - ((and (>= i 2) - (string= (substring s2 (- i 2) (1+ i)) "ref")) - (setf (car new) (concat "ref " (car new))) - (setq i (- i 2))) - - ((and (>= i 2) - (string= (substring s2 (- i 2) (1+ i)) "out")) - (setf (car new) (concat "out " (car new))) - (setq i (- i 2))) - - (t - (error "unexpected char (C)")))) - - ;; in a quoted context of a default-value. - ;; we're basically waiting for a matching quote, to go back to slurping param-name - ((= state 5) - (cond - ((equal quoting cs) - ;; we're back to unquoted! slurp param-name! - (setq state 0)) - (t - t))) - ) - - (cl-decf i)) - - (if (and (= state 3) (= nesting 0)) - (setq new (cons (substring s2 i ix2) new))) - - (concat "(" - (if new - (mapconcat 'identity new ", ") - "") - ")"))))) - - -(defun csharp--imenu-item-basic-comparer (a b) - "Compares the car of each element, assumed to be a string." - (string-lessp (car a) (car b))) - - -(defun csharp--imenu-get-method-name-from-sig (sig) - "Extract a method name with its parameter list from a method -signature, SIG. This is used to aid in sorting methods by name, -and secondarily by parameter list. - -For this input: - - private Dict<String, int> DoSomething(int, string) - -...the output is: - - DoSomething(int, string) - -" - (let* (c - result - (state 0) - (len (length sig)) - (i (1- len))) - (while (> i 0) - (setq c (aref sig i)) - - (cond - ((and (= state 0) (= c 40)) - (setq state 1)) - - ((and (= state 1) (or (= c 9) (= c 32))) - (setq result (substring sig (1+ i)) - i 0))) - (cl-decf i)) - result)) - - - -(defun csharp--imenu-item-method-name-comparer (a b) - "Compares the method names in the respective cars of each element. - -The car of each element is assumed to be a string with multiple -tokens in it, representing a method signature, including access -modifier, return type, and parameter list (surrounded by parens). -If the method takes no params, then it's just an empty pair of -parens. - -This fn extracts the method name and param list from that -signature and compares *that*. - -" - (let ((methoda (csharp--imenu-get-method-name-from-sig (car a))) - (methodb (csharp--imenu-get-method-name-from-sig (car b)))) - ;;(csharp-log -1 "compare '%s' <> '%s'" methoda methodb) - (string-lessp methoda methodb))) - - - -(defun csharp--imenu-create-index-helper (&optional parent-ns indent-level - consider-usings consider-namespaces) - "Helper fn for `csharp-imenu-create-index'. - -Scans a possibly narrowed section of a c# buffer. It finds -namespaces, classes, structs, enums, interfaces, and methods -within classes and structs. - -The way it works: it looks for an open-curly. If the open-curly -is a namespace or a class, it narrows to whatever is inside the -curlies, then recurses. - -Otherwise (the open-curly is neither of those things), this fn -tries to recognize the open-curly as the beginning of an enum, -method, or interface. - -If it succeeds, then a menu item is created for the thing. Then -it jumps to the matching close-curly, and continues. Stop when no -more open-curlies are found. - -" - - ;; A C# module consists of zero of more explicitly denoted (and - ;; possibly nested) namespaces. In the absence of an - ;; explicitly-denoted namespace, the global namespace is implicitly - ;; applied. Within each namespace there can be zero or more - ;; "container" things - like class, struct, or interface; each with - ;; zero or more indexable items - like methods, constructors. - ;; and so on. - - ;; This fn parses the module and indexes those items, creating a - ;; hierarchically organized list to describe them. Each container - ;; (ns/class/struct/etc) is represented on a separate submenu. - - ;; It works like this: - ;; (start at the top of the module) - ;; - ;; 1. look for a using clause - ;; yes - insert an item in the menu; move past all using clauses. - ;; - ;; 2. go to next open curly - ;; - ;; 2. beginning of a container? (a class or namespace) - ;; - ;; yes - narrow, and recurse +(defconst csharp--imenu-expression + (let* ((single-space "[ \t\n\r\f\v]") + (optional-space (concat single-space "*")) + (bol (concat "^" optional-space)) + (space (concat single-space "+")) + (access-modifier (regexp-opt '( "public" "private" "protected" "internal" + "static" "sealed" "partial" "override" "virtual" + "abstract"))) + ;; 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 "*")) + (return-type "\\(?:[[:alpha:]_][^ =\t\(\n\r\f\v]+\\)") + (identifier "[[:alpha:]_][[:alnum:]_]*") + (interface-prefix (concat "\\(?:" identifier "\\.\\)")) + (generic-identifier (concat identifier + ;; optional generic arguments + "\\(?:<" optional-space identifier + "\\(?:" "," optional-space identifier optional-space "\\)*" + ">\\)?" + )) + ;; param-list with parens + (parameter-list "\\(?:\([^!\)]*\)\\)") + (inheritance-clause (concat "\\(?:" + optional-space + ":" + optional-space generic-identifier + "\\(?:" optional-space "," optional-space generic-identifier "\\)*" + "\\)?"))) + + (list (list "namespace" + (concat bol "namespace" space + "\\(" identifier "\\)") 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 + generic-identifier inheritance-clause "\\)") 1) + (list "enum" + (concat bol + access-modifiers + "\\(" "enum" space + identifier "\\)") 1) + (list "ctor" + (concat bol + ;; ctor MUST have access modifiers, or else we pick + ;; every if statement in the file... + access-modifier-list "+" + "\\(" + identifier + 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 + "\\(" + generic-identifier + optional-space + parameter-list + "\\)" + ;; optional // or /* comment at end + "\\(?:[ \t]*/[/*].*\\)?" + optional-space + "{") 1) + (list "method-inf" + (concat bol + return-type space + "\\(" + interface-prefix + generic-identifier + optional-space + parameter-list + "\\)" + ;; optional // or /* comment at end + "\\(?:[ \t]*/[/*].*\\)?" + optional-space + "{") 1) + (list "prop" + (concat bol + ;; must require access modifiers, or else we + ;; pick up pretty much anything. + access-modifiers + return-type space + "\\(" + generic-identifier + "\\)" + 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 + generic-identifier + "\\)" + 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 + "\\(?:" (regexp-opt '("readonly" "const")) space "\\)?" + "\\(" + return-type space + generic-identifier + "\\)" + optional-space + ;; optional assignment + "\\(?:=[^;]+\\)?" + ";") 1) + (list "indexer" + (concat bol + access-modifiers + return-type space + "this" optional-space + "\\(" + ;; opening bracket + "\\[" optional-space + ;; type + "\\([^\]]+\\)" optional-space + identifier + ;; 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 + generic-identifier + "\\)" + optional-space + ";") 1)))) + +(defun csharp--imenu-get-pos (pair) + "Takes a (title . position) cons-pair `PAIR' and returns position. + + 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) + "Returns 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) + "Returns 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) + "Sorts an imenu-index list `ITMES' 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) + "Creates 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) + "Gets the class-node which a `ITEM' should be inserted into in `RESULT'. + + 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) + "Formats a item with a specified type as a 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) + ;; 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) + "Transforms a 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") + ("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 to main result list, as own items. + ;; We don't support nested types. EOS. + ;; + ;; This has the issue that it gets 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... + (dolist (enum (cdr (assoc "enum" index))) + (let ((enum-name (csharp--imenu-get-class-name enum namespaces))) + (setq result (cons (cons enum-name (cdr enum)) 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 () + (csharp--imenu-transform-index + (imenu--generic-function csharp--imenu-expression))) + +(defun csharp--setup-imenu () + "Sets 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. ;; - ;; no - create a menu item for the thing, whatever it is. add to - ;; the submenu. Go to the end of the thing (to the matching - ;; close curly) then goto step 1. + ;; 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)) - (let (container-name - (pos-last-curly -1) - this-flavor - this-item - this-menu - found-usings - done) - - (while (not done) - - ;; move to the next thing - (c-forward-syntactic-ws) - (cond - ((and consider-usings - (re-search-forward (csharp--regexp 'using-stmt) (point-max) t)) - (goto-char (match-beginning 1)) - (setq found-usings t - done nil)) - - ((re-search-forward "{" (point-max) t) - (if (= pos-last-curly (point)) - (progn - ;;(csharp-log -1 "imenu: No advance? quitting (%d)" (point)) - (setq done t)) ;; haven't advanced- likely a loop - - (setq pos-last-curly (point)) - (let ((literal (csharp-in-literal))) - ;; skip over comments? - (cond - - ((memq literal '(c c++)) - (while (memq literal '(c c++)) - (end-of-line) - (forward-char 1) - (setq literal (csharp-in-literal))) - (if (re-search-forward "{" (point-max) t) - (forward-char -1) - ;;(csharp-log -1 "imenu: No more curlies (A) (%d)" (point)) - (setq done t))) - - ((eq literal 'string) - (if (re-search-forward "\"" (point-max) t) - (forward-char 1) - ;;(csharp-log -1 "imenu: Never-ending string? posn(%d)" (point)) - (setq done t))) - - (t - (forward-char -1)))))) ;; backup onto the curly - - (t - ;;(csharp-log -1 "imenu: No more curlies (B) posn(%d)" (point)) - (setq done t))) - - - (if (not done) - (cond - - ;; case 1: open curly for an array initializer - ((looking-back "\\[\\][ \t\n\r]*" nil) - (forward-sexp 1)) - - ;; case 2: just jumped over a string - ((looking-back "\"" nil) - (forward-char 1)) - - ;; case 3: at the head of a block of using statements - (found-usings - (setq found-usings nil - consider-usings nil) ;; only one batch - (let ((first-using (match-beginning 1)) - (count 0) - marquis - ;; don't search beyond next open curly - (limit (1- - (save-excursion - (re-search-forward "{" (point-max) t))))) - - ;; count the using statements - (while (re-search-forward (csharp--regexp 'using-stmt) limit t) - (cl-incf count)) - - (setq marquis (if (eq count 1) "using (1)" - (format "usings (%d)" count))) - (push (cons marquis first-using) this-menu))) - - - ;; case 4: an interface or enum inside the container - ;; (must come before class / namespace ) - ((or (csharp--on-intf-open-curly-p) - (csharp--on-enum-open-curly-p)) - (setq consider-namespaces nil - consider-usings nil - container-name (if parent-ns - (concat parent-ns ".") - nil) - this-menu (append this-menu - (list - (cons (concat - (match-string-no-properties 1) ;; thing flavor - " " - container-name - (match-string-no-properties 2)) ;; intf name - (match-beginning 1))))) - (forward-sexp 1)) - - - ;; case 5: at the start of a container (class, namespace) - ((or (and consider-namespaces (csharp--on-namespace-open-curly-p)) - (csharp--on-class-open-curly-p) - (csharp--on-genclass-open-curly-p)) - - ;; produce a fully-qualified name for this thing - (if (string= (match-string-no-properties 1) "namespace") - (setq this-flavor (match-string-no-properties 1) - this-item (match-string-no-properties 2)) - (setq this-flavor (match-string-no-properties 2) - this-item (match-string-no-properties 3) - consider-usings nil - consider-namespaces nil)) - - (setq container-name (if parent-ns - (concat parent-ns "." this-item) - this-item)) - - ;; create a submenu - (let (submenu - (top (match-beginning 1)) - (open-curly (point)) - (close-curly (save-excursion - (forward-sexp 1) - (point)))) - (setq submenu - (list - (concat this-flavor " " container-name) - (cons "(top)" top))) - - ;; find all contained items - (save-restriction - (narrow-to-region (1+ open-curly) (1- close-curly)) - - (let* ((yok (string= this-flavor "namespace")) - (child-menu - (csharp--imenu-create-index-helper container-name - (concat indent-level " ") - yok yok))) - (if child-menu - (setq submenu - (append submenu - (sort child-menu - 'csharp--imenu-item-basic-comparer)))))) - (setq submenu - (append submenu - (list (cons "(bottom)" close-curly)))) - - (setq this-menu - (append this-menu (list submenu))) - - (goto-char close-curly))) - - - ;; case 6: a property - ((csharp--on-prop-open-curly-p) - (setq consider-namespaces nil - consider-usings nil - this-menu - (append this-menu - (list - (cons (concat - "prop " - (match-string-no-properties 3)) ;; prop name - (match-beginning 1))))) - (forward-sexp 1)) - - - ;; case 7: an indexer - ((csharp--on-indexer-open-curly-p) - (setq consider-namespaces nil - consider-usings nil - this-menu - (append this-menu - (list - (cons (concat - "indexer " - (match-string-no-properties 4)) ;; index type - (match-beginning 1))))) - (forward-sexp 1)) - - - ;; case 8: a constructor inside the container - ((csharp--on-ctor-open-curly-p) - (setq consider-namespaces nil - consider-usings nil - this-menu - (append this-menu - (list - (cons (concat - "ctor " - (match-string-no-properties 2) ;; ctor name - (csharp--imenu-remove-param-names-from-paramlist - (match-string-no-properties 3))) ;; ctor params - (match-beginning 1))))) - (forward-sexp 1)) - - - ;; case 9: a method inside the container - ((csharp--on-defun-open-curly-p) - (setq consider-namespaces nil - consider-usings nil - this-menu - (append this-menu - (list - (cons (concat - "method " - (match-string-no-properties 2) ;; return type - " " - (match-string-no-properties 3) ;; func name - (csharp--imenu-remove-param-names-from-paramlist - (match-string-no-properties 4))) ;; fn params - (match-beginning 1))))) - (forward-sexp 1)) - - - ;; case 10: unknown open curly - just jump over it. - ((looking-at "{") - (forward-sexp 1)) - - ;; case 11: none of the above. shouldn't happen? - (t - (forward-char 1))))) - - this-menu)) - - -;; ======================================================= -;; DPC Thu, 19 May 2011 11:25 -;; There are two challenges with the imenu support: generating the -;; index, and generating a reasonable display for the index. The index -;; generation is pretty straightforward: use regexi to locate -;; interesting stuff in the buffer. -;; -;; The menu generation is a little trickier. Long lists of methods -;; mixed with properties and interfaces (etc) will be displayed in the -;; menu but will look Very Bad. Better to organize the menu into -;; submenus, organized primarily by category. Also the menus should be -;; sorted, for ease of human scanning. The next section of logic is -;; designed to do the stuff for the menu generation. - - -(defcustom csharp-imenu-max-similar-items-before-extraction 6 - "The maximum number of things of a particular -category (constructor, property, method, etc) that will be -separely displayed on an imenu without factoring them into a -separate submenu. - -For example, if a module has 3 consructors, 5 methods, and 7 -properties, and the value of this variable is 4, then upon -refactoring, the constructors will remain in the toplevel imenu -and the methods and properties will each get their own -category-specific submenu. - -See also `csharp-imenu-min-size-for-sub-submenu'. - -For more information on how csharp-mode uses imenu, -see `csharp-want-imenu', and `csharp-mode'. -" - :type 'integer - :group 'csharp) - - -(defcustom csharp-imenu-min-size-for-sub-submenu 18 - "The minimum number of imenu items of a particular -category (constructor, property, method, etc) that will be -broken out into sub-submenus. - -For example, if a module has 28 properties, then the properties will -be placed in a submenu, and then that submenu with be further divided -into smaller submenus. - -See also `csharp-imenu-max-similar-items-before-extraction' - -For more information on how csharp-mode uses imenu, -see `csharp-want-imenu', and `csharp-mode'. -" - :type 'integer - :group 'csharp) - - -(defun csharp--first-word (s) - "gets the first word from the given string. -It had better be a string!" - (car (split-string s nil t))) - - -(defun csharp--make-plural (s) - "make a word plural. For use within the generated imenu." - (cond - ((string= s "prop") "properties") - ((string= s "class") "classes") - ((string= s "ctor") "constructors") - (t (concat s "s")))) - - -(defun csharp--imenu-counts (list) - "Returns an alist, each item is a cons cell where the car is a -unique first substring of an element of LIST, and the cdr is the -number of occurrences of that substring in elements in the -list. - -For a complicated imenu generated for a large C# module, the result of -this fn will be something like this: - - ((\"(top)\" . 1) - (\"properties\" . 38) - (\"methods\" . 12) - (\"constructors\" . 7) - (\"(bottom)\" . 1)) - -" - (letrec ((helper - (lambda (list new) - (if (null list) new - (let* ((elt (car list)) - (topic (csharp--make-plural - (csharp--first-word(car elt)))) - (xelt (assoc topic new))) - (funcall helper (cdr list) - (if xelt - (progn (cl-incf (cdr xelt)) new) - (cons (cons topic 1) new)))))))) - (nreverse (funcall helper list nil)))) - - - -(defun csharp--imenu-get-submenu-size (n) - "Gets the preferred size of submenus given N, the size of the -flat, unparceled menu. - -Suppose there are 50 properties in a given C# module. This fn maps -from that number, to the maximum size of the submenus into which the -large set of properties should be broken. - -Currently the submenu size for 50 is 12. To change this, change -the lookup table. - -The reason it's a lookup table and not a simple arithmetic -function: I think it would look silly to have 2 submenus each -with 24 items. Sixteen or 18 items on a submenu seems fine when -you're working through 120 items total. But if you have only 28 -items, better to have 3 submenus with 10 and 9 items each. So -it's not a linear function. That's what this lookup tries to do. - -" - (let ((size-pairs '((100 . 22) - (80 . 20) - (60 . 18) - (40 . 15) - (30 . 14) - (24 . 11) - (0 . 9))) - elt - (r 0)) - - (while (and size-pairs (eq r 0)) - (setq elt (car size-pairs)) - (if (> n (car elt)) - (setq r (cdr elt))) - (setq size-pairs (cdr size-pairs))) - r)) - - - -(defun csharp--imenu-remove-category-names (menu-list) - "Input is a list, each element is (LABEL . LOCATION). This fn -returns a modified list, with the first word - the category name -- removed from each label. - -" - (mapcar (lambda (elt) - (let ((tokens (split-string (car elt) "[ \t]" t))) - (cons (mapconcat 'identity (cdr tokens) " ") - (cdr elt)))) - menu-list)) - -(defun string-indexof (s c) - "Returns the index of the first occurrence of character C in string S. -Returns nil if not found. - -See also, `string-lastindexof' - -" - (let ((len (length s)) - (i 0) ix c2) - (while (and (< i len) (not ix)) - (setq c2 (aref s i)) - (if (= c c2) - (setq ix i)) - (cl-incf i)) - ix)) - -(defun string-lastindexof (s c) - "Returns the index of the last occurrence of character C in string S. -Returns nil if not found. - -See also, `string-indexof' - -" - (let ((i (1- (length s))) - ix c2) - (while (and (>= i 0) (not ix)) - (setq c2 (aref s i)) - (if (= c c2) - (setq ix i)) - (cl-decf i)) - ix)) - - -(defun csharp--imenu-submenu-label (sig flavor) - "generate a submenu label from the given signature, SIG. -The sig is a method signature, property type-and-name, -constructor, and so on, indicated by FLAVOR. - -This fn returns a simple name that can be used in the label for a -break out submenu. - -" - (if (string= flavor "method") - (let ((method-name (csharp--imenu-get-method-name-from-sig sig))) - (substring method-name 0 (string-indexof method-name 40))) - (substring sig (1+ (string-lastindexof sig 32))))) - - - - -(defun csharp--imenu-break-one-menu-into-submenus (menu-list) - "Parcels a flat list MENU-LIST up into smaller sublists. It tries -to balance the number of sublists and the size of each sublist. - -The max size of any sublist will be about 20 (arbitrary) and the -min size will be 7 or so. See `csharp--imenu-get-submenu-size' -for how this is done. - -It does this destructively, using `nbutlast'. - -Returns a new list, containing sublists. -" - - (let ((len (length menu-list)) - (counts (csharp--imenu-counts menu-list))) - - (cond - ;; a small number, and all the same flavor - ((and (< len csharp-imenu-min-size-for-sub-submenu) (= (length counts) 1)) - (csharp--imenu-remove-category-names - (sort menu-list - (if (string= (caar counts) "methods") - 'csharp--imenu-item-method-name-comparer - 'csharp--imenu-item-basic-comparer)))) - - ;; is the length already pretty short? - ((< len csharp-imenu-min-size-for-sub-submenu) - menu-list) - - ((/= (length counts) 1) - menu-list) - - (t - (let* ((lst (sort menu-list - (if (string= (caar counts) "methods") - 'csharp--imenu-item-method-name-comparer - 'csharp--imenu-item-basic-comparer))) - new - (sz (csharp--imenu-get-submenu-size len)) ;; goal max size of sublist - (n (ceiling (/ (* 1.0 len) sz))) ;; total number of sublists - (adj-sz (ceiling (/ (* 1.0 len) n))) ;; maybe a little less than sz - (nsmall (mod (- adj-sz (mod len adj-sz)) adj-sz)) ;; num of (n-1) lists - (i 0) - (base-name (csharp--first-word (caar lst))) - label - chunksz - this-chunk) - - (while lst - (setq chunksz (if (> nsmall i) (1- adj-sz) adj-sz) - this-chunk (csharp--imenu-remove-category-names - (nthcdr (- len chunksz) lst)) - lst (nbutlast lst chunksz) - ;;label (format "%s %d" plural-name (- n i)) - label (concat "from " (csharp--imenu-submenu-label (caar this-chunk) base-name)) - new (cons (cons label this-chunk) new) - len (- len chunksz)) - (cl-incf i)) - new))))) - - - -(defun csharp--imenu-break-into-submenus (menu-list) - "For an imenu menu-list with category-based submenus, -possibly break a submenu into smaller sublists, based on size. - -" - (mapcar (lambda (elt) - (if (imenu--subalist-p elt) - (cons (car elt) - (csharp--imenu-break-one-menu-into-submenus (cdr elt))) - elt)) - menu-list)) - - - - - -(defun csharp--imenu-reorg-alist-intelligently (menu-alist) - "Accepts an imenu alist. Returns an alist, reorganized. -Things get sorted, factored out into category submenus, -and split into multiple submenus, where conditions warrant. - -For example, suppose this imenu alist is generated from a scan: - - ((\"usings (4)\" . 1538) - (\"namespace Ionic.Zip\" - (\"(top)\" . 1651) - (\"partial class Ionic.Zip.ZipFile\" - (\"(top)\" . 5473) - (\"prop FullScan\" . 8036) - ... - (\"prop Comment\" . 21118) - (\"prop Verbose\" . 32278) - (\"method override String ToString\" . 96577) - (\"method internal void NotifyEntryChanged\" . 97608) - .... - (\"method internal void Reset\" . 98231) - (\"ctor ZipFile\" . 103598) - ... - (\"ctor ZipFile\" . 109723) - (\"ctor ZipFile\" . 116487) - (\"indexer int\" . 121232) - (\"indexer String\" . 124933) - (\"(bottom)\" . 149777)) - (\"public enum Zip64Option\" . 153839) - (\"enum AddOrUpdateAction\" . 154815) - (\"(bottom)\" . 154893))) - - -This is displayed as a toplevel menu with 2 items; the namespace -menu has 5 items (top, bottom, the 2 enums, and the class). The -class menu has 93 items. It needs to be reorganized to be more usable. - -After transformation of the alist through this fn, the result is: - - ((\"usings (4)\" . 1538) - (\"namespace Ionic.Zip\" - (\"(top)\" . 1651) - (\"partial class Ionic.Zip.ZipFile\" - (\"(top)\" . 5473) - (\"properties\" - (\"WriteStream\" . 146489) - (\"Count\" . 133827) - .... - (\"BufferSize\" . 12837) - (\"FullScan\" . 8036)) - (\"methods\" - (\"virtual void Dispose\" . 144389) - (\"void RemoveEntry\" . 141027) - .... - (\"method override String ToString\" . 96577) - (\"method bool ContainsEntry\" . 32517)) - (\"constructors\" - (\"ZipFile\" . 116487) - .... - (\"ZipFile\" . 105698) - (\"ZipFile\" . 103598)) - (\"indexer int\" . 121232) - (\"indexer String\" . 124933) - (\"(bottom)\" . 149777)) - (\"public enum Zip64Option\" . 153839) - (\"enum AddOrUpdateAction\" . 154815) - (\"(bottom)\" . 154893))) - -All menus are the same except the class menu, which has been -organized into subtopics, each of which gets its own cascaded -submenu. If the submenu itself holds more than -`csharp-imenu-max-similar-items-before-extraction' items that are -all the same flavor (properties, methods, etc), thos get split -out into multiple submenus. - -" - (let ((counts (csharp--imenu-counts menu-alist))) - (letrec ((helper - (lambda (list new) - (if (null list) - new - (let* ((elt (car list)) - (topic (csharp--make-plural - (csharp--first-word (car elt)))) - (xelt (assoc topic new))) - (funcall - helper (cdr list) - (if xelt - (progn - (rplacd xelt (cons elt (cdr xelt))) - new) - (cons - - (cond - ((> (cdr (assoc topic counts)) - csharp-imenu-max-similar-items-before-extraction) - (cons topic (list elt))) - - ((imenu--subalist-p elt) - (cons (car elt) - (csharp--imenu-reorg-alist-intelligently (cdr elt)))) - (t - elt)) - - new)))))))) - - (csharp--imenu-break-into-submenus - (nreverse (funcall helper menu-alist nil)))))) - - - - -(defun csharp-imenu-create-index () - "This function is called by imenu to create an index for the -current C# buffer, conforming to the format specified in -`imenu--index-alist' . - -See `imenu-create-index-function' for background information. - -To produce the index, which lists the classes, functions, -methods, and properties for the current buffer, this function -scans the entire buffer. - -This can take a long time for a large buffer. The scan uses -regular expressions that attempt to match on the general-case C# -syntax, for classes and functions, generic types, base-classes, -implemented interfaces, and so on. This can be time-consuming. -For a large source file, say 160k, it can take 10 seconds or more. -The UI hangs during the scan. - -imenu calls this fn when it feels like it, I suppose when it -thinks the buffer has been updated. The user can also kick it off -explicitly by selecting *Rescan* from the imenu menu. - -After generating the hierarchical list of props, methods, -interfaces, classes, and namespaces, csharp-mode re-organizes the -list as appropriate: - - - it extracts sets of like items into submenus. All properties - will be placed on a submenu. See - `csharp-imenu-max-similar-items-before-extraction' for a way - to tune this. - - - it converts those submenus into sub-submenus, if there are more than - `csharp-imenu-min-size-for-sub-submenu' items. - - - it sorts each set of items on the outermost menus lexicographically. - -The result of these transformations is what is provided to imenu -to generate the visible menus. Just FYI - the reorganization of -the scan results is much much faster than the actual generation -of the scan results. If you're looking to save time, the re-org -logic is not where the cost is. - -imenu itself likes to sort the menus. See `imenu--split-menu' and -also `csharp--imenu-split-menu-patch', which is advice that -attempts to disable the weird re-jiggering that imenu performs. - -" - ;; I think widen/narrow causes the buffer to be marked as - ;; modified. This is a bit surprising, but I have no other - ;; explanation for the source of the problem. - ;; So I use `c-save-buffer-state' so that the buffer is not - ;; marked modified when the scan completes. - - (c-save-buffer-state () - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - - (let ((index-alist - (csharp--imenu-create-index-helper nil "" t t))) - - (csharp--imenu-reorg-alist-intelligently index-alist) - - ;;index-alist - - ;; What follows is No longer used. - ;; ======================================================= - - ;; If the index menu contains exactly one element, and it is - ;; a namespace menu, then remove it. This simplifies the - ;; menu, and results in no loss of information: all types - ;; get fully-qualified names anyway. This will probably - ;; cover the majority of cases; often a C# source module - ;; defines either one class, or a set of related classes - ;; inside a single namespace. - - ;; To remove that namespace, we need to prune & graft the tree. - ;; Remove the ns hierarchy level, but also remove the 1st and - ;; last elements in the sub-menu, which represent the top and - ;; bottom of the namespace. - - ;; (if (and - ;; (= 1 (length index-alist)) - ;; (consp (car index-alist)) - ;; (let ((tokens (split-string - ;; (car (car index-alist)) - ;; "[ \t]" t))) - ;; (and (<= 1 (length tokens)) - ;; (string= (downcase - ;; (nth 0 tokens)) "namespace")))) - ;; - ;; (let (elt - ;; (newlist (cdar index-alist))) - ;; (setf (car (car newlist)) (car (car index-alist))) - ;; newlist) - ;; - ;; index-alist) - - ))))) - - -;; ================================================================== @@ -3791,6 +2733,7 @@ your `csharp-mode-hook' function: (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)) @@ -3957,14 +2900,7 @@ Key bindings: ;; maybe do imenu scan after hook returns (when csharp-want-imenu - ;; There are two ways to do imenu indexing. One is to provide a - ;; function, via `imenu-create-index-function'. The other is to - ;; provide imenu with a list of regexps via - ;; `imenu-generic-expression'; imenu will do a "generic scan" for you. - ;; csharp-mode uses the former method. - - (setq imenu-create-index-function 'csharp-imenu-create-index) - (imenu-add-menubar-index)) + (csharp--setup-imenu)) ;; The paragraph-separate variable was getting stomped by ;; other hooks, so it must reside here. diff --git a/test-files/imenu-interface-property-test.cs b/test-files/imenu-interface-property-test.cs index 2b98460..4e5dd47 100644 --- a/test-files/imenu-interface-property-test.cs +++ b/test-files/imenu-interface-property-test.cs @@ -5,12 +5,20 @@ public interface IImenuTest string InterfaceString { get; } } +/// <summary> +/// This test-case checks whether imenu indexes member declarations +/// with no access-modifier, but instead with the explicit +/// interface-name prefixed. +/// +/// We expect both the method and property to show up in our index, +/// with interface-name included. +/// </summary> public class ImenuTest : IImenuTest { string IImenuTest.InterfaceString { get { return "i"; }} - string IIMenuTest.MethodName(string param1, int param2) + string IImenuTest.MethodName(string param1, int param2) { - + } } diff --git a/test-files/imenu-namespace-test.cs b/test-files/imenu-namespace-test.cs index 1a922ca..e9573b8 100644 --- a/test-files/imenu-namespace-test.cs +++ b/test-files/imenu-namespace-test.cs @@ -2,15 +2,15 @@ using System; namespace ImenuTest { - public interface IMenuTestInterface + public interface ImenuTestInterface { } - public class IMenuTestClass + public class ImenuTestClass { } - public enum IMenuTestEnum + public enum ImenuTestEnum { } }