354

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

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

a little demo

wes
  • 7,795
  • 6
  • 31
  • 41
  • 22
    If you're like me two minutes ago wondering "do I *really* need a plugin for this?", stop hesitating and install it. There is basically just one command: ww which you press twice, once in each window to swap. This is super-easy and you'll be running in 30 sec. – mdup Jan 15 '16 at 12:19
  • in case it helps others: the default `` key in vim is the backslash `\\` – Miron Veryanskiy Jul 21 '23 at 03:11

12 Answers12

334

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.

Kenny Evitt
  • 9,291
  • 5
  • 65
  • 93
nelstrom
  • 18,802
  • 13
  • 54
  • 70
  • 2
    You went the extra mile making a screencast, nelstrom, but it's not really what I was looking for. I can work with splits with the basic movement commands, but what I'm curious about is if there's a way to swap split locations in a layout of arbitrary complexity. – wes Apr 12 '10 at 23:52
  • 122
    For people who like me, just want to learn how to swap two windows: `ctrl-w r` works like a charm. Thanks to you for the tip ! Here is my +1. – ereOn Apr 17 '13 at 08:52
  • I upvoted both the `\mw` / `\pw` and this one and tried using both for a week each. I found using this "native" solution works best, as I don't have to keep installing plugins across the upteen dozen vim installs I have on servers and remote machines and desktops, laptops, tablets and all other devices. IOW, learning these Native commands (e.g. `ctrl-w r`) is really all you need to commit to memory muscle and be done. – eduncan911 Feb 12 '16 at 16:04
  • Great, great, answer! Very useful tips, not need for plugins!! – mljrg Jun 02 '21 at 10:52
235

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!

sgriffin
  • 2,533
  • 1
  • 16
  • 8
  • 18
    I wish I could upvote you ten times! I did have to use `noremap` in the mappings to make it work. Not sure why, but hopefully that helps anyone who finds this later. :D – wes Feb 05 '11 at 23:28
  • 9
    I put your solution into my first Vim plugin: [WindowSwap.vim](https://github.com/wesQ3/vim-windowswap). I linked this question and your answer in the readme :D – wes Sep 26 '13 at 00:06
  • I put sgriffin's solution into my .vimrc a few years ago, and I'm cleaning up currently, and decided to move it all to a plugin. I did the extraction, and to test that it still worked as a bundle, I split the window many times, and ran some `0r!figlet one` [two, three, etc], then tested it out. Before going further I checked github, found your (wes') plugin, with animated figlet window swaps, and a link to this same answer (which I had as a comment in my .vimrc). I felt like I'd already made and uploaded it, and then forgot about it. Anyway, nice job! Saves me some work :) – Gary Fixler Jun 22 '14 at 08:10
110

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.

Randy Morris
  • 39,631
  • 8
  • 69
  • 76
40

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).

Community
  • 1
  • 1
Mike Seplowitz
  • 9,785
  • 1
  • 24
  • 23
11

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 :)

Pencilcheck
  • 2,664
  • 3
  • 25
  • 14
3

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.

Geoff Catlin
  • 33
  • 1
  • 5
3

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>
tpo
  • 31
  • 1
1

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.

lesnik
  • 2,507
  • 2
  • 25
  • 24
1

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>
rking
  • 1,602
  • 1
  • 10
  • 8
1

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
|__________________|
Tom Stock
  • 1,098
  • 1
  • 12
  • 26
0

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>
qeatzy
  • 1,363
  • 14
  • 21
-5

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

  • While true this answer is unrelated to the OP's question. Could be using vim on a Mac or Windows machine. Vim is available on tablets and even phones none of which afford the ability to swap out your window manager. – nsfyn55 Apr 03 '14 at 20:48