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?

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…

2

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

  • 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
0

You need to press BTTBBT instead of a single T.

  • 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
0

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"
                                                             ^

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.