How can I swap positions of two open files (in splits) in vim?

LayoutEditorSplitVim

Layout Problem Overview


Assume I've got some arbitrary layout of splits in vim.

____________________
| one       | two  |
|           |      |
|           |______|
|           | three|
|           |      |
|___________|______|

Is there a way to swap one and two and maintain the same layout? It's simple in this example, but I'm looking for a solution that will help for more complex layouts.

UPDATE:

I guess I should be more clear. My previous example was a simplification of the actual use-case. With an actual instance: ![alt text][1]

How could I swap any two of those splits, maintaining the same layout?

Update! 3+ years later...

I put sgriffin's solution in a Vim plugin you can install with ease! Install it with your favorite plugin manager and give it a try: [WindowSwap.vim][2]

![a little demo][3]

[1]: http://i.imgur.com/Xvj9R.png "IRL example" [2]: https://github.com/wesQ3/vim-windowswap [3]: http://i.stack.imgur.com/NGIww.gif

Layout Solutions


Solution 1 - Layout

Starting with this:

____________________
| one       | two  |
|           |      |
|           |______|
|           | three|
|           |      |
|___________|______|

Make 'three' the active window, then issue the command ctrl+w J. This moves the current window to fill the bottom of the screen, leaving you with:

____________________
| one       | two  |
|           |      |
|___________|______|
| three            |
|                  |
|__________________|

Now make either 'one' or 'two' the active window, then issue the command ctrl+w r. This 'rotates' the windows in the current row, leaving you with:

____________________
| two       | one  |
|           |      |
|___________|______|
| three            |
|                  |
|__________________|

Now make 'two' the active window, and issue the command ctrl+w H. This moves the current window to fill the left of the screen, leaving you with:

____________________
| two       | one  |
|           |      |
|           |______|
|           | three|
|           |      |
|___________|______|

As you can see, the manouevre is a bit of a shuffle. With 3 windows, it's a bit like one of those 'tile game' puzzles. I don't recommand trying this if you have 4 or more windows - you'd be better off closing them then opening them again in the desired positions.

I made a screencast demonstrating how to work with split windows in Vim.

Solution 2 - Layout

A bit late to the post, but came across this searching for something else. I wrote two functions awhile back to mark a window and then swap buffers between windows. This seems to be what you're asking for.

Just slap these in your .vimrc and map the functions how you see fit:

function! MarkWindowSwap()
	let g:markedWinNum = winnr()
endfunction

function! DoWindowSwap()
	"Mark destination
	let curNum = winnr()
	let curBuf = bufnr( "%" )
 	exe g:markedWinNum . "wincmd w"
 	"Switch to source and shuffle dest->source
	let markedBuf = bufnr( "%" )
	"Hide and open so that we aren't prompted and keep history
	exe 'hide buf' curBuf
	"Switch to dest and shuffle source->dest
 	exe curNum . "wincmd w"
	"Hide and open so that we aren't prompted and keep history
	exe 'hide buf' markedBuf 
endfunction

nmap <silent> <leader>mw :call MarkWindowSwap()<CR>
nmap <silent> <leader>pw :call DoWindowSwap()<CR>

To use (assuming your mapleader is set to \) you would:

  1. Move to the window to mark for the swap via ctrl-w movement
  2. Type \mw
  3. Move to the window you want to swap
  4. Type \pw

Voila! Swapped buffers without screwing up your window layout!

Solution 3 - Layout

Take a look at :h ctrl-w_ctrl-x and/or :h ctrl-w_ctrl-r. These commands allow you to exchange or rotate windows in the current layout.

Edit: Actually, this will not work in this situation because it only will swap in the current column or row. You could instead go to each of the windows and select the target buffer, but that's pretty verbose.

Solution 4 - Layout

Randy's correct in that CTRL-W x doesn't want to swap windows that aren't in the same column/row.

I've found that the CTRL-W HJKL keys are most useful when manipulating windows. They will force your current window out of its current location and tell it to occupy the entire edge indicated by the direction of the key you press. See :help window-moving for more details.

For your example above, if you start in window "one", this does what you want:

CTRL-W K   # moves window "one" to be topmost,
           #   stacking "one", "two", "three" top to bottom
CTRL-W j   # moves cursor to window "two"
CTRL-W H   # moves window "two" to be leftmost,
           #   leaving "one" and "three" split at right

For convenience, you can assign the sequences you need to key mappings (see :help mapping).

Solution 5 - Layout

I have a slightly enhanced version from sgriffin's solution, you can swap windows without using two commands, but with intuitive HJKL commands.

So here is how it goes:

function! MarkWindowSwap()
    " marked window number
    let g:markedWinNum = winnr()
    let g:markedBufNum = bufnr("%")
endfunction

function! DoWindowSwap()
    let curWinNum = winnr()
    let curBufNum = bufnr("%")
    " Switch focus to marked window
    exe g:markedWinNum . "wincmd w"

    " Load current buffer on marked window
    exe 'hide buf' curBufNum

    " Switch focus to current window
    exe curWinNum . "wincmd w"

    " Load marked buffer on current window
    exe 'hide buf' g:markedBufNum
endfunction

nnoremap H :call MarkWindowSwap()<CR> <C-w>h :call DoWindowSwap()<CR>
nnoremap J :call MarkWindowSwap()<CR> <C-w>j :call DoWindowSwap()<CR>
nnoremap K :call MarkWindowSwap()<CR> <C-w>k :call DoWindowSwap()<CR>
nnoremap L :call MarkWindowSwap()<CR> <C-w>l :call DoWindowSwap()<CR>

Try to move your window by using capital HJKL in normal node, it is really cool :)

Solution 6 - Layout

Building heavily on @sgriffin's answer, here's something even closer to what you're asking for:

function! MarkWindow()
        let g:markedWinNum = winnr()
endfunction

function! SwapBufferWithMarkedWindow()
        " Capture current window and buffer
        let curWinNum = winnr()
        let curBufNum = bufnr("%")

        " Switch to marked window, mark buffer, and open current buffer
        execute g:markedWinNum . "wincmd w"
        let markedBufNum = bufnr("%")
        execute "hide buf" curBufNum

        " Switch back to current window and open marked buffer
        execute curWinNum . "wincmd w"
        execute "hide buf" markedBufNum
endfunction

function! CloseMarkedWindow()
        " Capture current window
        let curWinNum = winnr()

        " Switch to marked window and close it, then switch back to current window
        execute g:markedWinNum . "wincmd w"
        execute "hide close"
        execute "wincmd p"
endfunction

function! MoveWindowLeft()
        call MarkWindow()
        execute "wincmd h"
        if winnr() == g:markedWinNum
                execute "wincmd H"
        else
                let g:markedWinNum += 1
                execute "wincmd s"
                execute g:markedWinNum . "wincmd w"
                execute "wincmd h"
                call SwapBufferWithMarkedWindow()
                call CloseMarkedWindow()
        endif
endfunction

function! MoveWindowDown()
        call MarkWindow()
        execute "wincmd j"
        if winnr() == g:markedWinNum
                execute "wincmd J"
        else
                execute "wincmd v"
                execute g:markedWinNum . "wincmd w"
                execute "wincmd j"
                call SwapBufferWithMarkedWindow()
                call CloseMarkedWindow()
        endif
endfunction

function! MoveWindowUp()
        call MarkWindow()
        execute "wincmd k"
        if winnr() == g:markedWinNum
                execute "wincmd K"
        else
                let g:markedWinNum += 1
                execute "wincmd v"
                execute g:markedWinNum . "wincmd w"
                execute "wincmd k"
                call SwapBufferWithMarkedWindow()
                call CloseMarkedWindow()
        endif
endfunction

function! MoveWindowRight()
        call MarkWindow()
        execute "wincmd l"
        if winnr() == g:markedWinNum
                execute "wincmd L"
        else
                execute "wincmd s"
                execute g:markedWinNum . "wincmd w"
                execute "wincmd l"
                call SwapBufferWithMarkedWindow()
                call CloseMarkedWindow()
        endif
endfunction

nnoremap <silent> <Leader>wm :call MarkWindow()<CR>
nnoremap <silent> <Leader>ws :call SwapBufferWithMarkedWindow()<CR>
nnoremap <silent> <Leader>wh :call MoveWindowLeft()<CR>
nnoremap <silent> <Leader>wj :call MoveWindowDown()<CR>
nnoremap <silent> <Leader>wk :call MoveWindowUp()<CR>
nnoremap <silent> <Leader>wl :call MoveWindowRight()<CR>

Please let me know if the behavior doesn't match your expectations.

Solution 7 - Layout

Also based on sgriffin's solution, go to the window you want to swap, press CTRL-w m, go to the window you want to swap with and press CTRL-w m again.

CTRL-w m is a poor mnemonic choice, so if anybody comes up with a better one, please edit this.

Also, I'd like to receive a feedback from the script aka "Window marked. Please repeat on target", however being a vimscript noob, I do not know how to do that.

All that said, the script works well as is

" <CTRL>-w m : mark first window
" <CTRL>-w m : swap with that window
let s:markedWinNum = -1

function! MarkWindowSwap()
    let s:markedWinNum = winnr()
endfunction

function! DoWindowSwap()
    "Mark destination
    let curNum = winnr()
    let curBuf = bufnr( "%" )
    exe s:markedWinNum . "wincmd w"
    "Switch to source and shuffle dest->source
    let markedBuf = bufnr( "%" )
    "Hide and open so that we aren't prompted and keep history
    exe 'hide buf' curBuf
    "Switch to dest and shuffle source->dest
    exe curNum . "wincmd w"
    "Hide and open so that we aren't prompted and keep history
    exe 'hide buf' markedBuf
endfunction

function! WindowSwapping()
    if s:markedWinNum == -1
        call MarkWindowSwap()
    else
        call DoWindowSwap()
        let s:markedWinNum = -1
    endif
endfunction

nnoremap <C-w>m :call WindowSwapping()<CR>

Solution 8 - Layout

The following approach may be convenient if functions are not available for some reason (f.e. it's not your vim).

Use :buffers command to find out id's of open buffers, navigate to desired window and use command like :b 5 to open a buffer (buffer number 5 in this case). Repeate two times and contents of windows are swapped.

I "invented" this method after several attempts to memorise ctrl-w-something sequences even for very simple layouts like one-two-three in original question.

Solution 9 - Layout

Really cool, but my proposal for the mapping is to use ^W^J instead of J (because all of H J K L already have meanings), plus also I'd pull in the new buffer, because by the time you want to swap around you probably don't want to continue editing the buffer you are already on. Here goes:

function! MarkSwapAway()
    " marked window number
    let g:markedOldWinNum = winnr()
    let g:markedOldBufNum = bufnr("%")
endfunction
function! DoWindowToss()
    let newWinNum = winnr()
    let newBufNum = bufnr("%")
    " Switch focus to marked window
    exe g:markedOldWinNum . "wincmd w"
    " Load current buffer on marked window
    exe 'hide buf' newBufNum
    " Switch focus to current window
    exe newWinNum . "wincmd w"
    " Load marked buffer on current window
    exe 'hide buf' g:markedOldBufNum
    " …and come back to the new one
    exe g:markedOldWinNum . "wincmd w"
endfunction
nnoremap <C-w><C-h> :call MarkSwapAway()<CR> <C-w>h :call DoWindowToss()<CR>
nnoremap <C-w><C-j> :call MarkSwapAway()<CR> <C-w>j :call DoWindowToss()<CR>
nnoremap <C-w><C-k> :call MarkSwapAway()<CR> <C-w>k :call DoWindowToss()<CR>
nnoremap <C-w><C-l> :call MarkSwapAway()<CR> <C-w>l :call DoWindowToss()<CR>

Solution 10 - Layout

All of the above answers are great, unfortunately these solutions do not work well in combination with QuickFix or LocationList windows (I ran in this problem while trying to get the Ale error message buffer to work with this).

Solution

Therefore I added an extra line of code to close all these windows before doing the swap.

exe ':windo if &buftype == "quickfix" || &buftype == "locationlist" | lclose | endif'

The total code looking like;

" Making swapping windows easy
function! SwapWindowBuffers()
    exe ':windo if &buftype == "quickfix" || &buftype == "locationlist" | lclose | endif'
    if !exists("g:markedWinNum")
        " set window marked for swap
        let g:markedWinNum = winnr()
        :echo "window marked for swap"
    else
        " mark destination
        let curNum = winnr()
        let curBuf = bufnr( "%" )
        if g:markedWinNum == curNum
            :echo "window unmarked for swap"
        else
            exe g:markedWinNum . "wincmd w"
            " switch to source and shuffle dest->source
            let markedBuf = bufnr( "%" )
            " hide and open so that we aren't prompted and keep history
            exe 'hide buf' curBuf
            " switch to dest and shuffle source->dest
            exe curNum . "wincmd w"
            " hide and open so that we aren't prompted and keep history
            exe 'hide buf' markedBuf
            :echo "windows swapped"
        endif
        " unset window marked for swap
        unlet g:markedWinNum
    endif
endfunction

nmap <silent> <leader>mw :call SwapWindowBuffers()<CR>

Credits for the swap function to Brandon Orther

Why it is needed

The reason the swap functions don't work properly without removing all the QuickFix (QF) and LocationList(LL) windows first is because if the parent of the QF/LL buffer the get's hidden (and nowhere shown in a window), the QF/LL window coupled to it is removed. This isn't a problem in itself but when the window hides all the window numbers are reassigned and the swap is messed up since the saved number of the first marked window is does (potentially) not exist any more.

To put this inperspective:

First window mark

____________________
| one              | -> winnr = 1    marked first    g:markedWinNum=1
|                  | -> bufnr = 1
|__________________|
| two (QF window   | -> winnr = 2
| coupled to one   |
|__________________|
| three            | -> winnr = 3
|                  | -> bufnr = 2
|__________________|

Second window mark

____________________
| one              | -> winnr = 1                    g:markedWinNum=1
|                  | -> bufnr = 1
|__________________|
| two (QF window   | -> winnr = 2
| coupled to one)  |
|__________________|
| three            | -> winnr = 3    marked second    curNum=3
|                  | -> bufnr = 2                     curBuf=2
|__________________|

First buffer switch, window one is filled with the buffer of window three. Thus the QF window is removed since it has no parent window any more. This rearranges the windows numbers. Note that curNum (the number of the secondly selected window) is pointing to a window that does not exist any more.

____________________
| three            | -> winnr = 1                    g:markedWinNum=1
|                  | -> bufnr = 2
|__________________|
| three            | -> winnr = 2                     curNum=3
|                  | -> bufnr = 2                     curBuf=2
|__________________|

So when switching the second buffer, it tries to select the curNum window, which does not exist any more. So it creates it and switches the buffer, resulting in one unwanted window to be open still.

____________________
| three            | -> winnr = 1                    g:markedWinNum=1
|                  | -> bufnr = 2
|__________________|
| three            | -> winnr = 2
|                  | -> bufnr = 2
|__________________|
| one              | -> winnr = 3                     curNum=3
|                  | -> bufnr = 1                     curBuf=2
|__________________|

Solution 11 - Layout

Similar mark-window-then-swap-buffer approach, but also let you reuse last swapping.

function! MarkWindowSwap()
    unlet! g:markedWin1
    unlet! g:markedWin2
    let g:markedWin1 = winnr()
endfunction

function! DoWindowSwap()
    if exists('g:markedWin1')
        if !exists('g:markedWin2')
            let g:markedWin2 = winnr()
        endif
        let l:curWin = winnr()
        let l:bufWin1 = winbufnr(g:markedWin1)
        let l:bufWin2 = winbufnr(g:markedWin2)
        exec g:markedWin2 . 'wincmd w'
        exec ':b '.l:bufWin1
        exec g:markedWin1 . 'wincmd w'
        exec ':b '.l:bufWin2
        exec l:curWin . 'wincmd w'
    endif
endfunction

nnoremap ,v :call DoWindowSwap()<CR>
nnoremap ,z :call MarkWindowSwap()<CR>

Solution 12 - Layout

You could also use a tiling window manager like X-monad

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionwesView Question on Stackoverflow
Solution 1 - LayoutnelstromView Answer on Stackoverflow
Solution 2 - LayoutsgriffinView Answer on Stackoverflow
Solution 3 - LayoutRandy MorrisView Answer on Stackoverflow
Solution 4 - LayoutMike SeplowitzView Answer on Stackoverflow
Solution 5 - LayoutPencilcheckView Answer on Stackoverflow
Solution 6 - LayoutGeoff CatlinView Answer on Stackoverflow
Solution 7 - LayouttpoView Answer on Stackoverflow
Solution 8 - LayoutlesnikView Answer on Stackoverflow
Solution 9 - LayoutrkingView Answer on Stackoverflow
Solution 10 - LayoutTom StockView Answer on Stackoverflow
Solution 11 - LayoutqeatzyView Answer on Stackoverflow
Solution 12 - LayoutWilliamView Answer on Stackoverflow