3

I'm a C++ programmer using a setup consisting of cc-mode and CEDET and of course our beloved emacs (v24.2).

A feature I'm missing is a function that moves point fast through a list of arguments. Consider this example:

void takesManyArgs( int a1, int a2, int a3, std::pair<int,int> a4, int a5 ){  
    // does something nifty!  
}  
// [...]
takesManyArgs( b1, b2, b3, make_pair( b4, b5 ), b6 );

Where point is just before the first int. Now I'd like to an easy way to quickly move through the list of arguments, i.e. a forward-argument (and backward-argument aswell) function that moves right before the first non-whitespace character past the comma (the argument separator).
I have written a small function that does it but it's not quite the way I'd like it work:

(defun arg-forward ()  
  "Move point forward in the file until we hit an argument separator, i.e. comma, colon or semicolon."  
  (interactive)  
  (progn  
    (re-search-forward "[,]")))  

In principle this function just jumps to the next comma. That's not the behaviour I want.

I'd like a function that:

  • Edit: Jumps to the next argument separator (i.e. comma for C++) and places point just after that argument separator
  • Checks whether there's a whitespace after the comma; in case there's one, point is moved to the first non-whitespace character (i.e. instead of ,| int it will look like , |int where | marks point)
  • Stops at the end of the argument list and prints a message (e.g. "At end of argument list")
  • Stays within its "scope", i.e. it'll go from |int b3 to |make_pair( int b4, int b5 ) to |int a6 where | marks point

Any help from you elisp "hackers" is appreciated!

Edit: Added some clarification.
Edit2: Fixed that example "Code"

elemakil
  • 3,681
  • 28
  • 53
  • 1
    You might want to change your example since the code you present does not seem to be valid C++: `make_pair(int a4, int a5)` is not a type and cannot appear like this in a function declaration/definition. – François Févotte Nov 26 '12 at 21:11
  • 1
    @Francesco The code in the question is certainly wrong, but similar constructs can appear with default argument. Classic C++. – pmr Nov 26 '12 at 21:14
  • Yeah... that's right. I added the make_pair halfway into writing this question (of course it was supposed to go in the function call) and didn't really check for correct code. Fixed it anyway. – elemakil Nov 26 '12 at 21:17

3 Answers3

5

This function seems to do what you requested:

(defun arg-forward ()
  "Move point forward until we hit an argument separator, the comma, colon, semicolon"
  (interactive)
  (condition-case nil
      (let ((separator-regexp "[,:;]"))
        (forward-sexp)
        (while (not (or (looking-at (format "[ \t]*%s" separator-regexp))
                        (save-excursion (forward-char -1)
                                        (looking-at (format "[]\"')}]*[ \t]*%s " separator-regexp)))))
          (forward-sexp))
        (if (looking-at separator-regexp)
            (forward-char 1))
        ;; going forward one sexp and back puts point at front of sexp
        ;; essentially skipping the whitespace, but also ensuring we don't
        ;; jump out of a larger closing set of parentheses 
        (forward-sexp)
        (forward-sexp -1))
    (error
     (beep)
     (message "At end of argument list"))))
Trey Jackson
  • 73,529
  • 11
  • 197
  • 229
  • I tested it and it doesn't stop at the argument separator (comma). For `( int aa, int ab, make_pair( int ac, int ad ), int ae, int af )``with poin just before `int aa` it'll jump to `int ae` instead of the chain `|int ab` -> `|make_pair( int ac, int ad)` -> `|int ae` where `|` marks point. – elemakil Nov 26 '12 at 21:08
  • @elemakil Sorry, I tested it in lisp mode, not C++ mode, and the `forward-sexp` works slightly differently there. I just updated the answer with a fix for C++ mode. – Trey Jackson Nov 26 '12 at 21:13
  • There's still one minor bug. Consider this: `|int aa , int ab, int ac` where `|` marks the point. `arg-forward` moves the right before `int ac`, i.e. in case that there's a trailing space before the argument separator the next argument is omitted. Maybe this is easy to fix aswell? OTher than that it works like a charm! – elemakil Nov 26 '12 at 21:35
  • Trying that function out on some code, there are two bugs. Consider this: `|int aa,int ab, int ac` where `|` marks the point. The function will omit arguments if there's no space _after_ the argument separator: point is placed just before `int ac`. In case that there's a space before the separator but none after point will be placed _before_ the separator (i.e. `|int aa ,int ab` -> `int aa |,int ab`). I guess the (regular?) expression `(format "[ \t]*%s" separator-regexp)` must be changed for this, but I don't know what `%s` it (whitespace char?). – elemakil Nov 27 '12 at 15:54
  • Although I'd never format my code like the two bugged scenarios above I'd be glad if you could fix it :) So far, the elisp functions you posted on SA have been a great addition to my dotemacs and I'd like to add one more. – elemakil Nov 27 '12 at 15:56
  • Well, I fixed the second bug (i.e. space before separator, no space after) by adding the if statement (the only one so far in your code) just after the final `forward-sexp -1` (but in the `let` block). There seems to be no solution for the "no-space before **and** after separator" bug when using `forward-sexp` but maybe you can help? – elemakil Nov 27 '12 at 23:00
2

You could write a function that uses forward-sexp and checks to see if you're at one of "argument separator" or "end of argument list".

I don't seem to have an emacs handy right now, so I can't trivially check an example implementation, but with those at hand, you should be well on your way.

Untested code below:

(defun forward-argument ()
  (interactive)
  (let ((argument-terminator "[),]"))              ;; Arguments stop at commas or close
     (while (not (looking-at argument-terminator)) ;; until we're at end-of-argument...
        (forward-sexp 1))))                        ;; move forward

This could probably benefit from pulling the argument-terminator out into a defvar, so it's easy to make it mode-specific. It could probably benefit from having a count prefix (wrap it in a (while (> count 0) ... (setq count (1- count)) and default count to 1 if no prefix is given) and a few other things., Not to mention actually checking the code against a live emacs instead of trying to remember what arguments functions take and what they're called.

Vatine
  • 20,782
  • 4
  • 54
  • 70
2

Since you are using CEDET with your C++ code, you can take advantage of the TAG navigation commands. I tried this with the version of CEDET from the CEDET bzr repository.

Check in the Development menu, near the bottom is Navigate Tags. You will find keybindings for forward/backward tag on:

C-c , n - next tag

C-c , p - previous tag

These will move you between functions, between arguments, between all sorts of things.

These functions don't do all the things you were asking about, but are a good start.

Eric
  • 3,959
  • 17
  • 15