1

Using Bash, with emacs keybindings set, the transpose-words keybinding (M-t) doesn't transpose arguments, but "words" (by its own definition of words).

So if I have this:

vimdiff project-number-One/Vagrantfile project-number-Two/Vagrantfile.old

and my cursor is between the first and second argument when I enter optiont, I instead end up with

vimdiff project-number-One/project Vagrantfile-number-Two/Vagrantfile.old

which is obviously not what I want. How can I transpose arguments?

iconoclast
  • 9,198
  • 13
  • 57
  • 97

4 Answers4

2

In bash, different commands have different notions of words. C-w kills to the previous whitespace, but most other commands including M-t use punctuation-delimited words.

With the cursor between the first and second argument, C-w C-e SPC C-y will transpose the two words.

If you want to bind a key to transposing whitespace-delimited words, it's more complicated. See confusing behavior of emacs-style keybindings in bash. Here's some minimally tested code.

transpose_whitespace_words () {
  local prefix=${READLINE_LINE:0:$READLINE_POINT} suffix=${READLINE_LINE:$READLINE_POINT}
  if [[ $suffix =~ ^[^[:space:]] ]] && [[ $prefix =~ [^[:space:]]+$ ]]; then
    prefix=${prefix%${BASH_REMATCH[0]}}
    suffix=${BASH_REMATCH[0]}${suffix}
  fi
  if [[ $suffix =~ ^[[:space:]]+ ]]; then
    prefix=${prefix}${BASH_REMATCH[0]}
    suffix=${suffix#${BASH_REMATCH[0]}}
  fi
  if [[ $prefix =~ ([^[:space:]]+)([[:space:]]+)$ ]]; then
    local word1=${BASH_REMATCH[1]} space=${BASH_REMATCH[2]}
    prefix=${prefix%${BASH_REMATCH[0]}}
    if [[ $suffix =~ [^[:space:]]+ ]]; then
      suffix=${suffix#${BASH_REMATCH[0]}}
      READLINE_LINE=${prefix}${BASH_REMATCH[0]}$space$word1$suffix
      READLINE_POINT=$((${#READLINE_LINE} - ${#suffix}))
    fi
  fi
}
bind -x '"\e\C-t": transpose_whitespace_words'

This is all easier in zsh…

1

If your cursor is there:

vimdiff projectOne/Vagrantfile projectTwo/Vagrantfile
                              ^

Press Alt + BTTBBTFTBBTT


Or simple:

Press Ctrl + W, Ctrl + E, insert a whitespace and press Ctrl + Y

iconoclast
  • 9,198
  • 13
  • 57
  • 97
Cyrus
  • 12,309
  • Thanks, but if I can't have a command that handles whole arguments, then it'll actually be a lot more complicated than that. I updated the examples to be closer to real life, with punctuation. – iconoclast Oct 17 '14 at 16:03
  • Your simple approach seems to work, except that I think you meant control (or Ctrl on Windows keyboards) rather than Alt. – iconoclast Oct 17 '14 at 16:06
1

For a quick and simple solution add this to your inputrc (choose suitable keys for yourself):

"\e\C-b": shell-backward-kill-word
"\eh": shell-backward-word
"\e\C-f": shell-forward-word
# Swap the preceding two arguments (control + alt + t)
"\e\C-t": "\e\C-b\eh\C-y"
# Swap the preceding argument with the next (control + alt + p)
"\e\C-p": "\e\C-b\e\C-f\C-y"

In case of shell-* versions of these functions words are delimited by non-quoted shell metacharacters.

metacharacter

A character that, when unquoted, separates words. A metacharacter is a space, tab, newline, or one of the following characters: ‘|’, ‘&’, ‘;’, ‘(’, ‘)’, ‘<’, or ‘>’.

Note: The cursor has to be after the second argument before pressing Ctrl+Alt+t, so it effectively pushes the argument before the cursor towards the beginning of the line.

$ true foo/bar.xyz even/without\ quotes.ok "too/too far.away"
                                                             ^
$ true foo/bar.xyz "too/too far.away" even/without\ quotes.ok
                                     ^
$ true "too/too far.away" foo/bar.xyz even/without\ quotes.ok
                         ^

Note: The cursor has to be after the first argument before pressing Ctrl+Alt+p, so it effectively pulls the argument before the cursor towards the end of the line.

$ true "too/too far.away" foo/bar.xyz even/without\ quotes.ok
                         ^
$ true foo/bar.xyz "too/too far.away" even/without\ quotes.ok
                                     ^
$ true foo/bar.xyz even/without\ quotes.ok "too/too far.away"
                                                             ^
Reinis
  • 11
0

You need to press BTTBBT instead of a single T.

choroba
  • 47,233
  • Thanks, but if I can't have a command that handles whole arguments, then it'll actually be a lot more complicated than that. I updated the examples to be closer to real life, with punctuation. – iconoclast Oct 17 '14 at 16:04