Search code examples
vim

Swap selection around a pivot


(NOT a possible duplicate of Swap text around equal sign :)

Very often I find myself swapping things around. Which is a pain in the ass.

Let say just after I write this piece of code

tmp = realloc (words, sizeof (char **) * (*count + 1));

I notice there are just too many asterisks in a row, don't like it, and want to swap the two factors around the multiplication asterisk.

Or, I write

#if !defined(_CONSOLE_H_) && defined (__MINGW32__)

but I suddenly realize that defined (__MINGW32__) must come first for some reason.

I think it would be cool if I could do something like this:

(On a character x, [x] indicate cursor position. <S> stands for this hypothetic "swap around" command)

#if [!]defined(_CONSOLE_H_) && defined (__MINGW32__)

command: vf&<S>$ => select from cursor to pivot (the word &&), and swap the selection around with the text till end of line.

Or, for the former example:

tmp = realloc (words, [s]izeof (char **) * (*count + 1));

command: v3f*<S>f) => select from here to third *, swap with text forward to ).

For me, it would be incredibly useful. Is there something like this out there, or I have to write my own plug-in?

Thanks!

EDIT -

As @ib. says in the comments to his answer, I need to be more specific as to what is the pivot.

The pivot could also be a character, for example here:

static char ** tokenize_input (char * input, PDWORD count);

I may want to swap the two arguments around the ",". More precisely, the ", ".

So maybe I'll need two commands:

<s> - char-wise - the pivot is the last character of the selection;
<S> - word-wise - the pivot is the last word of the selection.

Thanks! (ps. what about the last Word? :)


Solution

  • This seems to work here, let me know how it does:

    function! Swap(...) "{{{
      let start_v = col("'<")
      let end_v = col("'>")
      let mv = ''
      let isMv = 0
      while !isMv
        let char = s:GetChar()
        if char == '<Esc>'
          return ''
        endif
        let mv .= char
        let isMv = s:IsMovement(mv)
        echon mv."\r"
      endwhile
      if isMv == 2
        return ''
      endif
      exec "normal! ".end_v.'|'.mv
      let lhs = '\%'.start_v.'c\(.\{-}\S\)'
      if !a:0
        let pivot = '\(\s*\%'.(end_v).'c.\s*\)'
      else
        let pivot = '\(\s*'.a:1.'*\%'.(end_v).'c'.a:1.'\+\s*\)'
      endif
      let rhs = '\(.*\%#.\)'
      exec 's/'.lhs.pivot.rhs.'/\3\2\1/'
    endfunction "Swap }}}
    
    function! s:GetChar() "{{{
      let char = getchar()
      if type(char) == type(0) && char < 33
        return '<Esc>'
      elseif char
        let char = nr2char(char)
      endif
      return char
    endfunction "GetChar }}}
    
    function! s:IsMovement(mv) "{{{
      let ft = a:mv =~ '^\C\d*[fFtT].$'
      let ft_partial = a:mv =~ '^\C\d*\%([fFtT].\?\)\?$'
      let right = a:mv =~ '^\d*[l$|;,]\|g[m$]$'
      let right_partial = a:mv =~ '^\d*\%([l$|;,]\|g[m$]\?\)\?$'
      if !right_partial && !ft_partial
        return 2
      endif
      return ft || right
    endfunction "IsMovement2Right }}}
    
    " Use last char as pivot. e.g.: the comma in the given example.
    vmap <silent> <leader>s1 :<C-U>call Swap()<CR>
    " Use \S\+ (WORD) as pivot. e.g.: &&
    vmap <silent> <leader>ss :<C-U>call Swap('\S')<CR>
    " Use \w\+ as pivot.
    vmap <silent> <leader>sw :<C-U>call Swap('\w')<CR>
    

    No need to press enter to specify the movement now. The 'pivot' always includes any surrounding whitespace and it can be defined with a single character class given as argument to Swap() in the mapping.

    If you want to see it highlighted, check https://gist.github.com/1921196