1

While imenu works very well in most environments, I am having issues when using it in C++ files where functions are not defined flush left.

(Update:) Steps to reproduce the issue (thanks to Drew's comment below).

  • Start emacs -Q
  • Load the file below.
  • Do M-x imenu
  • I get the error message: "No items suitable for an index found in this buffer".
  • If the two functions are not indented (the lines starting with "int"), I instead get the prompt "Index item: ", as expected.

Test file:

namespace this_breaks_imenu
{
   int other_function( int argc, char** argv )
   {
     float b;
     return 0;
   }

  int main( int argc, char** argv )
  {
    float a;
    return 0;
  }
}

If the code is indented like that (with a couple of extra spaces along the left margin), doing S-down-mouse-3 which is bound to (imenu INDEX-ITEM) gives me "No kbd macro has been defined". The same file without the spaces to the left works fine.

Why is this, and is there any way of working around it, other than changing the indentation of the files (which will not be appreciated by my collaborators)?

  • just curious, why would you want extra spaces along the left margin ? Is it just as aesthetic thing ? – Chakravarthy Raghunandan Sep 7 '16 at 12:33
  • Usually, it is because the functions are within a namespace{} scope, which makes them indented like this. – Supernormal Sep 7 '16 at 13:18
  • 1
    I just tested this code in my emacs and imenu works for me (I have not added any config for c/c++ modes). I'm pretty sure indentation does not matter for imenu – Chakravarthy Raghunandan Sep 7 '16 at 14:09
  • 1
    Do you see the same thing when starting Emacs with no init file (emacs -Q)? If not, recursively bisect your init file to find the culprit. If yes, please provide a step-by-step recipe to repro (starting from `emacs -Q). – Drew Sep 7 '16 at 15:04
  • 1
    The regexps in cc-imenu-c++-generic-expression are each anchored to the beginning of a word at the start of a line, but don't match indented lines. – politza Sep 14 '16 at 6:35
2
+50

I copied over the definition of the variable cc-imenu-c++-generic-expression and patched the regular expressions that included ^ to ^[ \t]*. Eval the setq (maybe put it in your init.el, then open the test file.

 (setq cc-imenu-c++-generic-expression
 `(
 ;; Try to match ::operator definitions first. Otherwise `X::operator new ()'
 ;; will be incorrectly recognized as function `new ()' because the regexps
 ;; work by backtracking from the end of the definition.
 (nil
  ,(concat
    "^\\<.*"
    "[^" c-alnum "_:<>~]"                  ; match any non-identifier char
                       ; (note: this can be `\n')
    "\\("
       "\\([" c-alnum "_:<>~]*::\\)?"      ; match an operator
       "operator\\>[ \t]*"
       "\\(()\\|[^(]*\\)"                  ; special case for `()' operator
    "\\)"

    "[ \t]*([^)]*)[ \t]*[^ \t;]"           ; followed by ws, arg list,
                       ; require something other than
                       ; a `;' after the (...) to
                       ; avoid prototypes.  Can't
                       ; catch cases with () inside
                       ; the parentheses surrounding
                       ; the parameters.  e.g.:
                       ; `int foo(int a=bar()) {...}'
    ) 1)
 ;; Special case to match a line like `main() {}'
 ;; e.g. no return type, not even on the previous line.
 (nil
  ,(concat
    "^[ \t]*"
    "\\([" c-alpha "_][" c-alnum "_:<>~]*\\)" ; match function name
    "[ \t]*("                 ; see above, BUT
    "[ \t]*\\([^ \t(*][^)]*\\)?)"          ; the arg list must not start
    "[ \t]*[^ \t;(]"                       ; with an asterisk or parentheses
    ) 1)
 ;; General function name regexp
 (nil
  ,(concat
    "^[ \t]*\\<"                                 ; line MUST start with word char
    ;; \n added to prevent overflow in regexp matcher.
    ;; http://lists.gnu.org/archive/html/emacs-pretest-bug/2007-02/msg00021.html
    "[^()\n]*"                             ; no parentheses before
    "[^" c-alnum "_:<>~]"                  ; match any non-identifier char
    "\\([" c-alpha "_][" c-alnum "_:<>~]*\\)" ; match function name
    "\\([ \t\n]\\|\\\\\n\\)*("        ; see above, BUT the arg list
    "\\([ \t\n]\\|\\\\\n\\)*"         ; must not start
    "\\([^ \t\n(*]"               ; with an asterisk or parentheses
    "[^()]*\\(([^()]*)[^()]*\\)*"         ; Maybe function pointer arguments
    "\\)?)"
    "\\([ \t\n]\\|\\\\\n\\)*[^ \t\n;(]"
    ) 1)
 ;; Special case for definitions using phony prototype macros like:
 ;; `int main _PROTO( (int argc,char *argv[]) )'.
 ;; This case is only included if cc-imenu-c-prototype-macro-regexp is set.
 ;; Only supported in c-code, so no `:<>~' chars in function name!
 ,@(if cc-imenu-c-prototype-macro-regexp
     `((nil
          ,(concat
        "^[ \t]*\\<.*"                   ; line MUST start with word char
        "[^" c-alnum "_]"          ; match any non-identifier char
        "\\([" c-alpha "_][" c-alnum "_]*\\)" ; match function name
        "[ \t]*"                   ; whitespace before macro name
        cc-imenu-c-prototype-macro-regexp
        "[ \t]*("                  ; ws followed by first paren.
        "[ \t]*([^)]*)[ \t]*)[ \t]*[^ \t;]" ; see above
        ) 1)))
 ;; Class definitions
 ("Class"
  ,(concat
      "^[ \t]*"                                  ; beginning of line is required
      "\\(template[ \t]*<[^>]+>[ \t]*\\)?" ; there may be a `template <...>'
      "\\(class\\|struct\\)[ \t]+"
      "\\("                                ; the string we want to get
      "[" c-alnum "_]+"                    ; class name
      "\\(<[^>]+>\\)?"                     ; possibly explicitly specialized
      "\\)"
      "\\([ \t\n]\\|\\\\\n\\)*[:{]"
      ) 3))
 )

Edit: Can't respond to your comment, but the following doesn't work:

(setq cc-imenu-c++-generic-expression ((nil "^\\<

That's because the (( is not quoted. (Emacs) Lisp sees a bare ( as the command to call a function. You need to use ` or ' to quote it. So (( says call the function returned by calling nil ...

| improve this answer | |
  • Have you observed a wide array of false positives with this change? I see imenu entries representing single keywords like if. It matches something like if (condition) as a function. – InHarmsWay Sep 21 '16 at 22:46
  • No, I didn't test it on large files. But yeah, matching things like if (condition) is probably why the had the no-whitespace-in-beginning-of-line rule :( – omajid Sep 22 '16 at 0:48
  • Yes, it's unfortunate. imenu is often useless at my work due to the namespaces used. – InHarmsWay Sep 22 '16 at 10:30
  • 1
    Have you tried using semantic instead? – omajid Sep 23 '16 at 19:06
  • 1
    I have not. The code base is fairly large, and I have been scared off by warnings of slowness in semantic and cedet. But you are right, this should be my next step. – InHarmsWay Sep 23 '16 at 22:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.