6

How to swap two arguments for C function calls? eg:

my_function(foo, bar, baz);
~~~~~~~~~~~~~^ (cursor location)

Should be transposed to the right to make:

my_function(bar, foo, baz);

The simple case works for this similar question.

But fails with:

my_function(&foo, *bar, baz[2]);
~~~~~~~~~~~~~ ^ (cursor location)

Giving:

my_function(&*bar, foo, baz[2]);

From what I can tell this is because these functions rely on backward-sexp which doesn't account for some characters used in C code.


For reference, this is the code: from https://emacs.stackexchange.com/a/11062/2418

(defun custom-calculate-stops ()
  (save-excursion
    (let
      (
        (start
          (condition-case e
            (while t (backward-sexp))
            (error (point))))
        stops)
      (push start stops)
      (condition-case e
        (while t
          (forward-sexp)
          (when (looking-at "\\s-*,")
            (push (point) stops)))
        (error (push (point) stops)))
      (nreverse stops))))
(defun custom-transpose-args ()
  (interactive)
  (when (looking-at "\\s-") (backward-sexp))
  (cl-loop with p = (point)
    with previous = nil
    for stop on (custom-calculate-stops)
    for i upfrom 0
    when (<= p (car stop)) do
    (when previous
      (let*
        (
          (end (cadr stop))
          (whole (buffer-substring previous end))
          middle last)
        (delete-region previous end)
        (goto-char previous)
        (setf
          middle
          (if (> i 1) (- (car stop) previous)
            (string-match "[^, \\t]" whole (- (car stop) previous)))
          last
          (if (> i 1) (substring whole 0 middle)
            (concat
              (substring whole (- (car stop) previous) middle)
              (substring whole 0 (- (car stop) previous)))))
        (insert (substring whole middle) last)))
    (cl-return)
    end do (setf previous (car stop))))
  • Not enough detail to format an answer. However, the first example seems to be passing three (3) pointers while the second example seems to be passing 2 pointers and a single character – user3629249 Feb 20 at 7:33
  • @user3629249 added details. – ideasman42 Feb 20 at 7:50
  • Please post the code that causing the problem. We cannot guess (or read your mind) as to what code your using to modify the parameters – user3629249 Feb 20 at 7:53
  • Posted the code, it's from the question linked. – ideasman42 Feb 20 at 9:54
4

The following transposes two arguments of c-functions. The first argument is the one with point in it. It is followed by the second arg and separated by a comma.

It looks for arguments by skipping over sexps and whitespaces-comments until it finds an argument separator, i.e., a comma or a parenthesis.

(defun c-forward-to-argsep ()
  "Move to the end of the current c function argument.
Returns point."
  (interactive)
  (while (progn (comment-forward most-positive-fixnum)
        (looking-at "[^,)]"))
    (forward-sexp))
  (point))

(defun c-backward-to-argsep ()
  "Move to the beginning of the current c function argument.
Returns point."
  (interactive)
  (let ((pt (point))
    cur)
    (up-list -1)
    (forward-char)
    (while (progn
         (setq cur (point))
         (> pt (c-forward-to-argsep)))
      (forward-char))
    (goto-char cur)))

(defun c-transpose-args ()
  "Transpose two arguments of a c-function.
The first arg is the one with point in it."
  (interactive)
  (let* ((pt (point))
     (b (c-backward-to-argsep))
     (sep (progn (goto-char pt)
             (c-forward-to-argsep)))
     (e (progn
          (unless (looking-at ",")
        (user-error "Argument separator not found"))
          (forward-char)
          (c-forward-to-argsep)))
     (ws-first (buffer-substring-no-properties
            (goto-char b)
            (progn (skip-chars-forward "[[:space:]\n]")
               (point))
            ))
     (first (buffer-substring-no-properties (point) sep))
     (ws-second (buffer-substring-no-properties
             (goto-char (1+ sep))
             (progn (skip-chars-forward "[[:space:]\n]")
                (point))))
     (second (buffer-substring-no-properties (point) e)))
    (delete-region b e)
    (insert ws-first second "," ws-second first)))
  • When usnig this w/ my_function(&foo, &bar, baz[2]); on any of the I get the error Scan error: "Containing expression ends prematurely" – ideasman42 Feb 20 at 11:07
  • Odd, this works when I'm in a text file, but not when I'm editing C source code (even if it's the only statement in the file). – ideasman42 Feb 20 at 11:20
  • @ideasman42 The problem is that punctuation characters cause the following character to be joined to the sexp. That is a really strange behavior if one considers (& or &)! Maybe some GUI stuff could act in that way but definitively not scan-sexps. :-( Let us delete this answer until I find a fix. – Tobias Feb 20 at 11:27
  • 1
    @ideasman42 Fixed by avoiding backward-sexp. – Tobias Feb 20 at 11:49
  • Added updates to this answer, feel free to include changes and I'll delete mine. – ideasman42 Feb 20 at 22:15
6

Based on @Tobias's excellent answer.

  • this extends it to have both forward and backward transpose.
  • correct cursor location.
  • don't move the cursor if there is nothing to do.

(defun c-forward-to-argsep ()
  "Move to the end of the current c function argument.
Returns point."
  (interactive)
  (while
    (progn (comment-forward most-positive-fixnum)
      (looking-at "[^,)]"))
    (forward-sexp))
  (point))

(defun c-backward-to-argsep ()
  "Move to the beginning of the current c function argument.
Returns point."
  (interactive)
  (let ((pt (point)) cur)
    (up-list -1)
    (forward-char)
    (while
      (progn
        (setq cur (point))
        (> pt (c-forward-to-argsep)))
      (forward-char))
    (goto-char cur)))
(defun c-transpose-args-direction (is_forward)
  "Transpose two arguments of a c-function.
The first arg is the one with point in it."
  (interactive)
  (let* ( ;; Only different to pt when not 'is_forward'.
          (pt-original (point))
          (pt
            (progn
              (when (not is_forward)
                (goto-char (- (c-backward-to-argsep) 1))
                (unless (looking-at ",")
                  (goto-char pt-original)
                  (user-error "Argument separator not found")))
              (point)))
          (b (c-backward-to-argsep))
          (sep
            (progn (goto-char pt)
              (c-forward-to-argsep)))
          (e
            (progn
              (unless (looking-at ",")
                (goto-char pt-original)
                (user-error "Argument separator not found"))
              (forward-char)
              (c-forward-to-argsep)))
          (ws-first
            (buffer-substring-no-properties
              (goto-char b)
              (progn (skip-chars-forward "[[:space:]\n]")
                (point))))
          (first (buffer-substring-no-properties (point) sep))
          (ws-second
            (buffer-substring-no-properties
              (goto-char (1+ sep))
              (progn (skip-chars-forward "[[:space:]\n]")
                (point))))
          (second (buffer-substring-no-properties (point) e)))
    (delete-region b e)
    (insert ws-first second "," ws-second first)

    ;; Correct the cursor location to be on the same character.
    (if is_forward
      (goto-char
        (+
          ;; Word start.
          (- (point) (length first))
          ;; Apply initial offset within the word.
          (- pt b (length ws-first))))
      (goto-char
        (+
          b (length ws-first)
          ;; Apply initial offset within the word.
          (- pt-original (+ pt 1 (length ws-second))))))))


(defun c-transpose-args-forward () (interactive) (c-transpose-args-direction t))
(defun c-transpose-args-backward () (interactive) (c-transpose-args-direction nil))

  • Thanks, amazing! A small bug though: when c-transpose-args-backward is executed in between <…>, it errors with Containing expression ends prematurely. E.g. this text: foo(monostate, const Slice<Availability> rows). Although executing the function in any point that is not inside < and > works as expected. – Hi-Angel May 24 at 7:47
  • 1
    I fixed it: please, apply this patch to your answer. – Hi-Angel May 27 at 9:40
  • Could you adjust this so it only runs for C++ code? it's not needed in C code for eg. – ideasman42 May 27 at 10:49
  • I don't think it is needed, because the code shouldn't change behavior with regard to C. The patch does 2 things: in c-forward-to-argsep it handles scan-error exception, which only happens on template braces. I just checked: if there's lone > brace in the way of (forward-sexp) call, exception doesn't appear. …And in c-backward-to-argsep it handles that <…> in C++ is a balanced expression. But it isn't in C, so problem is irrelevant there, and the change wouldn't break anything. – Hi-Angel May 27 at 11:14
  • 1
    I should add: ultimately, "argument syntax" in C is a subset of the one in C++, so if this breaks anything for C, it gonna also regress C++. – Hi-Angel May 27 at 14:31

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.