├── LICENSE ├── README.md └── autoload └── window.vim /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Scott Pierce 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | This aims to make Vim window layouts a easier. 3 | The default mappings and commands make managing more than a couple windows 4 | difficult: 5 | 6 | - Rotating windows `r` only works on linear layouts. 7 | - Exchanging windows `x` only works on linear layouts. 8 | - No concept of moving a buffer from one window to another. 9 | - This is achieved by going into a window, `:b 1`, then moving to another 10 | buffer and `:b 2`. `:b#` can't be used because windows remember previous 11 | buffer independently. 12 | - Joining windows together is not possible as a single command. 13 | 14 | This plugin provides functions which can be mapped to anything you want to 15 | overcome these difficulties. Happy vimming! 16 | 17 | # Buffer Rotation 18 | Same as the default `nmap r`, but works across any window layout. 19 | 20 | `function! window#rotate(direction * v:count1)` 21 | 22 | ``` 23 | Rotate Clockwise: ]r 24 | 25 | +-----------+ +-----------+ 26 | | | B | | | A | 27 | | |---| | |---| 28 | | A | C | --> | D | B | 29 | | |---| | |---| 30 | | | D | | | C | 31 | +-----------+ +-----------+ 32 | ``` 33 | 34 | ``` 35 | Rotate Counter-clockwise: [r 36 | 37 | +-----------+ +-----------+ 38 | | | B | | | C | 39 | | |---| | |---| 40 | | A | C | --> | B | D | 41 | | |---| | |---| 42 | | | D | | | A | 43 | +-----------+ +-----------+ 44 | ``` 45 | 46 | # Exchange Window Buffers 47 | 48 | The default is to exchange with the previous window as defined by 49 | `p`/`winnr('#')`. If a count is specified before the mapping 50 | the current window will exchange with the `v:count` window. 51 | 52 | The following examples shows feature. If the current window is `A` 53 | go to window `C`, then `x` and the 2 buffers will swap. If you don't want 54 | to first move to `C`, we can do `3x`. I show my `winnr` in the status line 55 | for exactly this reason. It's also useful to jump directly to window numbers by 56 | using `{n}` 57 | 58 | ``` 59 | Exchange: x 60 | 61 | +-----------+ +-----------+ 62 | | | B | | | B | 63 | | |---| | |---| 64 | | A | C | --> | C | A | 65 | | |---| | |---| 66 | | | D | | | D | 67 | +-----------+ +-----------+ 68 | ``` 69 | 70 | # Glue Windows Together 71 | Similar to iTerms "Move Session to Split Pane". Can also be thought of as 72 | "join", but `j` and `J` are taken by Vim defaults. There are `g` 73 | mappings, too, but none that chain out to `hjkl`. So "[g]lue" becomes are new 74 | pneumonic. 75 | 76 | Here's the effect of some window glueing. All diagrams assume the previous 77 | window is `D` and the current window is `A`. 78 | 79 | ``` 80 | Glue to right: gl 81 | 82 | +---------------+ +-------------+ 83 | | | B | | | | | 84 | | |------| | | | B | 85 | | A | C | --> | A | D#|----| 86 | | ^ |------| | | | | 87 | | | D# | | | | C | 88 | +---------------+ +-------------+ 89 | ``` 90 | 91 | ``` 92 | Glue to left: gh 93 | 94 | +---------------+ +-------------+ 95 | | | B | | | | | 96 | | |------| | | | B | 97 | | A | C | --> | D# | A |----| 98 | | ^ |------| | | | | 99 | | | D# | | | | C | 100 | +---------------+ +-------------+ 101 | ``` 102 | 103 | ``` 104 | Glue to above: gk 105 | 106 | +---------------+ +-------------+ 107 | | | B | | | | 108 | | |------| | D# | B | 109 | | A | C | --> |--------|----| 110 | | ^ |------| | | | 111 | | | D# | | A | C | 112 | +---------------+ +-------------+ 113 | ``` 114 | 115 | ``` 116 | Glue to below: gj 117 | 118 | +---------------+ +-------------+ 119 | | | B | | | | 120 | | |------| | A | B | 121 | | A | C | --> |--------|----| 122 | | ^ |------| | | | 123 | | | D# | | D# | C | 124 | +---------------+ +-------------+ 125 | ``` 126 | 127 | # Window Layouts 128 | I'm pretty picky about window layout when I have more than a couple buffers. 129 | I like to have one primary window and the rest of the buffers/windows in a 130 | linear layout pinned to the right. 131 | 132 | ``` 133 | +----------------+ 134 | | | B | 135 | | |------| 136 | | A | C | 137 | | |------| 138 | | | D | 139 | +----------------+ 140 | ``` 141 | 142 | There are many ways to achieve this but they all require multiple steps. `:ball 143 | | wincmd H` is probably the easiest one or `:vert ball | wincmd J`. The problem 144 | with these naive approaches is knowing which buffer will be the main one. After 145 | each `ball` command, it is hard to know which window will come into focus for 146 | the `wincmd H` split. The `window#layout` function aims to keep the current 147 | window or `v:count` window as the main after executing the new layout. 148 | 149 | Here's a few interesting commands: 150 | 151 | ```vim 152 | " This is my preferred layout 153 | command! -nargs=* BallH call window#layout('ball', 'H', ) 154 | 155 | " Use it with where the number is optional. 156 | :BallH 3 157 | 158 | " Same layout but only effect current windows instead of all buffers. 159 | command! -nargs=* WinH call window#layout('windo wincmd J', 'H', ) 160 | 161 | " Example use is, here I've omitted the optional arg which 162 | " which keeps the current window as primary. 163 | :WinH 164 | ``` 165 | 166 | Here's of starting Vim with your new layout command: 167 | 168 | ```sh 169 | # How about from shell 170 | vim +BallH $(git diff --name-only) 171 | ``` 172 | 173 | # Window Isolation 174 | An improved `o`. I hit this all the time by accident when trying to a 175 | previous window using `p`. But never disabled it because sometimes, that's 176 | what I actually want. We have a function which can be mapped to `o` which 177 | will simply performs a `:tab sp` when there is more than 1 window. This will 178 | make a copy of the buffer and put it into it's own tab while maintaining the 179 | original layout if the previous tab. 180 | 181 | ```vim 182 | " Improve window only, to split to new tab instead 183 | nnoremap o :call window#only() 184 | nnoremap :call window#only() 185 | ``` 186 | 187 | # All Recommended Mappings 188 | Here's all the recommended mapping in one place. Feel free to copy them into 189 | your own `$MYVIMRC`. Make adjustments as needed or make more sweet command 190 | combos. 191 | 192 | 193 | ```vim 194 | " Unimpaired mapping 195 | nnoremap ]r :call window#rotate(-1 * v:count1) 196 | nnoremap [r :call window#rotate(1 * v:count1) 197 | 198 | " Improved window rotate to work with all layouts 199 | nmap r ]r 200 | nmap ]r 201 | 202 | " Improve window exchange to work with all layouts 203 | nnoremap x :call window#exchange(v:count) 204 | nnoremap :call window#exchange(v:count) 205 | 206 | " [g]lue windows together. 207 | " l = glue to right side 208 | " h = glue to left side 209 | " j = glue to bottom 210 | " k = glue to top 211 | " 212 | " `normal! 100zh` scrolls window contents into view since it gets messy when 213 | " narrower window tries refocuses its cursor. 214 | nnoremap gl :call window#join('rightbelow vsplit', v:count) normal! 100zh 215 | nnoremap gh :call window#join('leftabove vsplit', v:count) normal! 100zh 216 | nnoremap gj :call window#join('belowright split', v:count) normal! 100zh 217 | nnoremap gk :call window#join('aboveleft split', v:count) normal! 100zh 218 | 219 | " Force a primary window layout. 220 | " The capital HJKL forces the primary window to a specific direction. 221 | command! -nargs=* LayoutH call window#layout('ball', 'H', ) 222 | command! -nargs=* LayoutJ call window#layout('vertical ball', 'J', ) 223 | command! -nargs=* LayoutK call window#layout('vertical ball', 'K', ) 224 | command! -nargs=* LayoutL call window#layout('ball', 'L', ) 225 | 226 | " Map the layout commands to something if that's your style. 227 | nnoremap gH :LayoutH v:count 228 | nnoremap gJ :LayoutJ v:count 229 | nnoremap gK :LayoutK v:count 230 | nnoremap gL :LayoutL v:count 231 | 232 | " Improve window only, to split to new tab instead 233 | nnoremap o :call window#only() 234 | nnoremap :call window#only() 235 | ``` 236 | -------------------------------------------------------------------------------- /autoload/window.vim: -------------------------------------------------------------------------------- 1 | " Sets window's buffer number to next/previous windows buffer number 2 | " This effective rotates the contents of the windows instead of the 3 | " actual window. 4 | " It has a similar effect window#rotate, but handles all sorts of window 5 | " layouts. 6 | function! window#rotate(dir) abort 7 | let current = winnr() 8 | " assume left is main window 9 | let winnr_to_bufnr = s:winnr_bufnr_dict() 10 | " collect all the buffer numbers 11 | let max = winnr('$') 12 | let i = max 13 | while i > 0 14 | let dst = (a:dir + i) - 1 15 | let mod = s:mod(dst,max) + 1 16 | call window#set_winnr_to_bufnr(i, winnr_to_bufnr[mod]) 17 | let i -= 1 18 | endwhile 19 | exec 'keepjumps '.current.'wincmd w' 20 | endfunction 21 | 22 | function! window#winnr_by_area() 23 | let largest = 0 24 | let size = 0 25 | 26 | let i = winnr('$') 27 | while i > 0 28 | let area = winheight(i) * winwidth(i) 29 | if area >= size 30 | let largest = i 31 | let size = area 32 | endif 33 | let i -= 1 34 | endwhile 35 | return largest 36 | endfunction 37 | 38 | function! window#set_winnr_to_bufnr(window_num, buffer_num) 39 | exec 'keepjumps '.a:window_num.'wincmd w' 40 | exec 'silent keepjumps buffer '.a:buffer_num 41 | endfunction 42 | 43 | " Similar to `wincmd x`, but works regardless of layout. 44 | " When other is < 1, will exchange with previous window according to 45 | " `wincmd p`. 46 | function! window#exchange(other) abort 47 | let current = winnr() 48 | let winnr_to_bufnr = s:winnr_bufnr_dict() 49 | let other_winnr = s:other_winnr(a:other) 50 | if current == other_winnr 51 | return 52 | endif 53 | let other_bufnr = winnr_to_bufnr[other_winnr] 54 | if other_bufnr 55 | call window#set_winnr_to_bufnr(other_winnr, winnr_to_bufnr[current]) 56 | call window#set_winnr_to_bufnr(current, winnr_to_bufnr[other_winnr]) 57 | endif 58 | endfunction 59 | 60 | " Tab Split Current Window 61 | " Mapping: 62 | " nnoremap o :call window#only() 63 | " nnoremap :call window#only() 64 | function! window#only() 65 | if winnr('$') > 1 66 | tab split 67 | endif 68 | endfunction 69 | 70 | function! window#join(splitter, other) abort 71 | let current = winnr() 72 | let other_winnr = s:other_winnr(a:other) 73 | if current == other_winnr 74 | return 75 | endif 76 | let winnr_to_bufnr = s:winnr_bufnr_dict() 77 | let other_bufnr = winnr_to_bufnr[other_winnr] 78 | wincmd p 79 | exec other_winnr.'quit' 80 | wincmd p 81 | exec a:splitter 82 | exec 'buffer '.other_bufnr 83 | endfunction 84 | 85 | " Based on current window and previous window 86 | " Usage: :call window#layout('ball', 'H', winnr()) 87 | function! window#layout(split_all, cmd, ...) abort 88 | " figure out the winnr before splitting, otherwise 89 | " original layouts winnr() could change. 90 | let main_winnr = a:0 > 0 ? (0 + a:1) : 0 91 | let main_winnr = main_winnr < 1 ? winnr() : main_winnr 92 | 93 | let winnr_to_bufnr = s:winnr_bufnr_dict() 94 | exec a:split_all 95 | let bufnr_to_winnr = s:bufnr_winnr_dict() 96 | exec bufnr_to_winnr[winnr_to_bufnr[main_winnr]].'wincmd w' 97 | exec 'wincmd '. a:cmd 98 | endfunction 99 | 100 | " privates 101 | 102 | " returns `other` or winnr('#') 103 | function! s:other_winnr(other) 104 | let other_winnr = a:other 105 | if other_winnr < 1 106 | let other_winnr = winnr('#') 107 | endif 108 | return other_winnr 109 | endfunction 110 | 111 | " ensures a positive modulo 112 | function! s:mod(n,m) 113 | return ((a:n % a:m) + a:m) % a:m 114 | endfunction 115 | 116 | " return dicitionay of winnr to bufnr mappings 117 | function! s:winnr_bufnr_dict() 118 | let result = {} 119 | " collect all the buffer numbers 120 | let i = winnr('$') 121 | while i > 0 122 | let result[i] = winbufnr(i) 123 | let i -= 1 124 | endwhile 125 | return result 126 | endfunction 127 | 128 | " return dicitionay of winnr to bufnr mappings 129 | function! s:bufnr_winnr_dict() 130 | let result = {} 131 | " collect all the buffer numbers 132 | let i = winnr('$') 133 | while i > 0 134 | let result[winbufnr(i)] = i 135 | let i -= 1 136 | endwhile 137 | return result 138 | endfunction 139 | --------------------------------------------------------------------------------