13

How I can I swap two arguments in a call to a Python function?

If I put point on the space between these two arguments:

self.assertEqual(json.loads(some.data), json_data)

and then M-t (transpose-words), I get:

self.assertEqual(json.loads(some.json), data_data)

On the other hand with C-M-t (transpose-sexps) I get:

self.assertEqual(json.loadsjson_data, (some.data))

What I want is:

self.assertEqual(json_data, json.loads(some.data))

Is there a command that will do that?

ideasman42
  • 8,375
  • 1
  • 28
  • 105
Croad Langshan
  • 3,192
  • 14
  • 42
  • 2
    I haven't tried but [Anchored Transpose](http://emacswiki.org/emacs/AnchoredTranspose) can be used for this; it's, though, a 2-step process. – Kaushal Modi May 01 '15 at 10:58
  • There is a core function called `transpose-subr` which takes a `forward` function and translates it into a `transpose` function. So if we had `c-forward-arglist` (function to move from one function arg to the next - AFAICT this doesn't exist) we would have `c-transpose-arglist`. – Brendan Aug 08 '19 at 08:31

3 Answers3

5

I use a variation of transpose-sexps that looks for the case you describe and transposes things-separated-by-commas, or just does regular transpose-sexps. It also leaves the cursor in place instead of dragging it forward, which is a bit different but I personally like.

(defun my-transpose-sexps ()
  "If point is after certain chars transpose chunks around that.
Otherwise transpose sexps."
  (interactive "*")
  (if (not (looking-back "[,]\\s-*" (point-at-bol)))
      (progn (transpose-sexps 1) (forward-sexp -1))
    (let ((beg (point)) end rhs lhs)
      (while (and (not (eobp))
                  (not (looking-at "\\s-*\\([,]\\|\\s)\\)")))
        (forward-sexp 1))
      (setq rhs (buffer-substring beg (point)))
      (delete-region beg (point))
      (re-search-backward "[,]\\s-*" nil t)
      (setq beg (point))
      (while (and (not (bobp))
                  (not (looking-back "\\([,]\\|\\s(\\)\\s-*" (point-at-bol))))
        (forward-sexp -1))
      (setq lhs (buffer-substring beg (point)))
      (delete-region beg (point))
      (insert rhs)
      (re-search-forward "[,]\\s-*" nil t)
      (save-excursion
        (insert lhs)))))
Jordon Biondo
  • 12,332
  • 2
  • 41
  • 62
scottfrazer
  • 314
  • 1
  • 2
4

This is something I, too, wanted to have for a long time, and here I've found some motivation to work on it. It's probably not very robust, but at the first try it seems to cover the cases I tried:

(defun my/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-p "\\s-*,")
              (push (point) stops)))
        (error (push (point) stops)))
      (nreverse stops))))

(defun my/transpose-args ()
  (interactive)
  (when (looking-at-p "\\s-") (backward-sexp))
  (cl-loop with p = (point)
           with previous = nil
           for stop on (my/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-p "[^, \\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))))
ideasman42
  • 8,375
  • 1
  • 28
  • 105
wvxvw
  • 11,222
  • 2
  • 30
  • 55
  • I get this when point is on the space (works when on the comma): let*: Wrong type argument: integer-or-marker-p, nil – Croad Langshan May 04 '15 at 20:24
  • @CroadLangshan the fix was relatively easy (see above). The funny thing I tried to follow Stefan's advise by writing an alternative `forward-sexp-function`, and that appeared to be too cumbersome because of the commas. I then tried to copycat `traspose-sexp` to do the same thing, and, again, having to account for commas makes this really difficult. I don't claim this always does the right thing when it comes to list delimiters, maybe only half of the time... – wvxvw May 05 '15 at 09:40
  • This fails for `(aa, bb)` when the cursor is on the first `a`. This also doesn't work for transposing `FOO(aaa *, bbb, ccc)` The `*` somehow messes up transposing. – ideasman42 Feb 19 '19 at 22:21
  • Also fails for `FOO(&aaa, bbb)` (& doesn't swap). – ideasman42 Feb 20 '19 at 02:09
2

In modes that use SMIE, transpose-sexp should work correctly for that case. They will still fail when the infix symbol (aka "separator") is not a , (or a ;) but is a word (e.g. and).

So, my opinion is that the command for that is transpose-sexp and when this doesn't work correctly, I consider it as a bug (but a bug that may be hard and/or take time to fix and have low-priority, so don't hold your breath). The way to fix it is by setting forward-sexp-function to a function that will know that "after I see a comma, I just jump around the whole argument".

Stefan
  • 26,154
  • 3
  • 46
  • 84
  • 1
    I guess java-mode (for example) doesn't use SMIE? How would one go about fixing that? – Samuel Edwin Ward Oct 21 '15 at 17:58
  • 1
    No, indeed, major modes for C-like languages don't use SMIE and not only for historical reasons: SMIE's parser is just too weak for those languages. This said, SMIE's parser would work just fine if you want to swap two arguments separated by a comma (this part of the grammar is simple enough), so I guess it would be possible to configure SMIE and use it for `forward-sexp-function` but you'd have to add some code to only use SMIE's `forward-sexp-function` in those cases where it works well enough, since in many other cases it would result in confusing behavior. – Stefan Oct 22 '15 at 00:36