How can I swap positions of two open files (in splits) in vim?
LayoutEditorSplitVimLayout 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:
- Move to the window to mark for the swap via ctrl-w movement
- Type \mw
- Move to the window you want to swap
- 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