├── LICENSE ├── README.md ├── autoload └── promiscuous │ ├── autocomplete.vim │ ├── git.vim │ ├── helpers.vim │ ├── session.vim │ ├── tmux.vim │ └── undo.vim └── plugin └── promiscuous.vim /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Sean Huber - github@shuber.io 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [![Sean Huber](https://cloud.githubusercontent.com/assets/2419/6550752/832d9a64-c5ea-11e4-9717-6f9aa6e023b5.png)](https://github.com/shuber) vim-promiscuous 2 | 3 | Instant **context switching** using git and vim sessions. 4 | 5 | 6 | ## What does it do? 7 | 8 | It basically takes a snapshot of the following: 9 | 10 | * All of your vim tabs, buffers, splits, and folds along with their sizes and positions 11 | * The location of your cursor for each buffer 12 | * The actively selected tab/buffer 13 | * Your undo history (each branch's undo history is saved separately) 14 | * Your git stage with all tracked/untracked files and staged/unstaged hunks 15 | 16 | When you switch to different branches using `:Promiscuous your-branch-name`, it takes a snapshot of the current branch and working directory, then checks out the new branch, and loads its corresponding snapshot if one exists. 17 | 18 | If no snapshot exists, you are presented with a "fresh" vim instance that only has one tab and an empty buffer. 19 | 20 | When `:Promiscuous` is called with no arguments, an `:FZF` fuzzy finder window is presented with a list of existing branches. From there we can either select an existing branch, or type out a new branch to checkout. 21 | 22 | If you're using [tmux](https://tmux.github.io/) then your status line will automatically refresh when `:Promiscuous` checks out a branch. This is very convenient when you display [git information in your status line](https://github.com/shuber/tmux-git). 23 | 24 | Similar projects: 25 | 26 | * http://www.eclipse.org/mylyn/ 27 | * https://github.com/szw/vim-ctrlspace 28 | 29 | 30 | ## Installation 31 | 32 | Load `shuber/vim-promiscuous` using your favorite plugin manager e.g. [Vundle](https://github.com/VundleVim/Vundle.vim) 33 | 34 | It currently depends on `:FZF`, but this dependency will be optional in the future. 35 | 36 | 37 | ## Usage 38 | 39 | ```vim 40 | :Promiscuous [branch] 41 | ``` 42 | 43 | It's recommended to make a custom key binding for this. I've been using the following mapping: 44 | 45 | ```vim 46 | nmap gb :Promiscuous 47 | ``` 48 | 49 | I also use an additional mapping to checkout the previous branch (usually `master`): 50 | 51 | ```vim 52 | nmap gg :Promiscuous - 53 | ``` 54 | 55 | 56 | ## Configuration 57 | 58 | These are the defaults. Feel free to override them. 59 | 60 | ```vim 61 | " The directory to store all sessions and undo history 62 | let g:promiscuous_dir = $HOME . '/.vim/promiscuous' 63 | 64 | " The callback used to load a session (receives branch name) 65 | let g:promiscuous_load = 'promiscuous#session#load' 66 | 67 | " The prefix prepended to all commit, stash, and log messages 68 | let g:promiscuous_prefix = '[Promiscuous]' 69 | 70 | " The callback used to save a session (receives branch name) 71 | let g:promiscuous_save = 'promiscuous#session#save' 72 | 73 | " Log all executed commands with echom 74 | let g:promiscuous_verbose = 0 75 | ``` 76 | 77 | ```vim 78 | set sessionoptions=blank,buffers,curdir,folds,help,tabpages,winsize 79 | set undolevels=1000 80 | set undoreload=10000 81 | ``` 82 | 83 | 84 | ## How does it work? 85 | 86 | ```vim 87 | call promiscuous#helpers#clear() 88 | call promiscuous#git#stash() 89 | call promiscuous#git#commit() 90 | 91 | let l:branch_was = promiscuous#git#branch() 92 | call call(g:promiscuous_save, [l:branch_was], {}) 93 | 94 | call promiscuous#session#clean() 95 | call promiscuous#git#checkout(l:branch) 96 | 97 | let l:branch = promiscuous#git#branch() 98 | call call(g:promiscuous_load, [l:branch], {}) 99 | 100 | call promiscuous#git#commit_pop() 101 | call promiscuous#git#stash_pop() 102 | call promiscuous#tmux#refresh() 103 | ``` 104 | 105 | The output below occurred when switching from `master` to `something-new` then back to `master`. 106 | 107 | ``` 108 | [Promiscuous] !clear 109 | [Promiscuous] !(git diff --quiet && git diff --cached --quiet) || (git stash save Code_vim_promiscuous_master && git stash apply) 110 | [Promiscuous] !git add . && git commit -am '[Promiscuous]' 111 | [Promiscuous] mksession! /Users/Sean/.vim/promiscuous/Code_vim_promiscuous_master.vim 112 | [Promiscuous] bufdo bd 113 | [Promiscuous] !git checkout - || git checkout -b - 114 | [Promiscuous] source /Users/Sean/.vim/promiscuous/Code_vim_promiscuous_something_new.vim 115 | [Promiscuous] Checkout something-new 116 | 117 | [Promiscuous] !clear 118 | [Promiscuous] !(git diff --quiet && git diff --cached --quiet) || (git stash save Code_vim_promiscuous_something_new && git stash apply) 119 | [Promiscuous] !git add . && git commit -am '[Promiscuous]' 120 | [Promiscuous] mksession! /Users/Sean/.vim/promiscuous/Code_vim_promiscuous_something_new.vim 121 | [Promiscuous] bufdo bd 122 | [Promiscuous] !git checkout - || git checkout -b - 123 | [Promiscuous] source /Users/Sean/.vim/promiscuous/Code_vim_promiscuous_master.vim 124 | [Promiscuous] Checkout master 125 | ``` 126 | 127 | 128 | ## Contributing 129 | 130 | * Fork the project. 131 | * Make your feature addition or bug fix. 132 | * Commit, do not mess with the version or history. 133 | * Send me a pull request. Bonus points for topic branches. 134 | 135 | 136 | ## License 137 | 138 | [MIT](https://github.com/shuber/vim-promiscuous/blob/master/LICENSE) - Copyright © 2015 Sean Huber 139 | -------------------------------------------------------------------------------- /autoload/promiscuous/autocomplete.vim: -------------------------------------------------------------------------------- 1 | function! promiscuous#autocomplete#branches() 2 | call fzf#run({ 3 | \ 'options': '--print-query', 4 | \ 'sink*': function('Promiscuous'), 5 | \ 'source': 'git branch -a' 6 | \ }) 7 | endfunction 8 | -------------------------------------------------------------------------------- /autoload/promiscuous/git.vim: -------------------------------------------------------------------------------- 1 | function! promiscuous#git#branch() 2 | return systemlist('git symbolic-ref --short HEAD')[0] 3 | endfunction 4 | 5 | function! promiscuous#git#checkout(unsanitized_branch) 6 | let l:branch = substitute(a:unsanitized_branch, '^\s*\(.\{-}\)\s*$', '\1', '') 7 | let l:checkout = 'git checkout ' 8 | let l:checkout_old = l:checkout . l:branch 9 | let l:checkout_new = l:checkout . '-b ' . l:branch 10 | let l:checkout_command = '!' . l:checkout_old . ' || ' . l:checkout_new 11 | call promiscuous#helpers#exec(l:checkout_command) 12 | endfunction 13 | 14 | function! promiscuous#git#commit() 15 | let l:commit = 'git commit --no-gpg-sign -anm ' . shellescape(g:promiscuous_prefix) 16 | call promiscuous#helpers#exec('!git add . && ' . l:commit) 17 | endfunction 18 | 19 | function! promiscuous#git#commit_pop() 20 | let l:commit = systemlist('git log -1 --pretty=format:%s')[0] 21 | 22 | if l:commit == g:promiscuous_prefix 23 | call promiscuous#helpers#exec('!git reset --soft HEAD~1 && git reset') 24 | endif 25 | endfunction 26 | 27 | function! promiscuous#git#stash() 28 | let l:name = promiscuous#session#name() 29 | let l:clean = '(git diff --quiet && git diff --cached --quiet)' 30 | let l:stash = '(git stash save ' . l:name . ' && git stash apply)' 31 | call promiscuous#helpers#exec('!' . l:clean . ' || ' . l:stash) 32 | endfunction 33 | 34 | function! promiscuous#git#stash_pop() 35 | let l:name = promiscuous#session#name() 36 | let l:stash = systemlist('git stash list | grep ' . l:name . ' | cut -d ":" -f1') 37 | 38 | if type(l:stash) == type([]) && len(l:stash) > 0 39 | call promiscuous#helpers#exec('!git reset --hard && git stash pop --index ' . l:stash[0]) 40 | endif 41 | endfunction 42 | -------------------------------------------------------------------------------- /autoload/promiscuous/helpers.vim: -------------------------------------------------------------------------------- 1 | function! promiscuous#helpers#clear() 2 | call promiscuous#helpers#exec('!clear') 3 | endfunction 4 | 5 | function! promiscuous#helpers#dasherize(path) 6 | return substitute(a:path, '\W', '_', 'g') 7 | endfunction 8 | 9 | function! promiscuous#helpers#exec(command, ...) 10 | if a:0 > 0 11 | let s:verbose = a:1 12 | else 13 | let s:verbose = g:promiscuous_verbose 14 | endif 15 | 16 | if s:verbose 17 | call promiscuous#helpers#log(a:command) 18 | endif 19 | 20 | silent exec a:command 21 | endfunction 22 | 23 | function! promiscuous#helpers#log(message) 24 | echom g:promiscuous_prefix a:message 25 | endfunction 26 | 27 | function! promiscuous#helpers#mkdir(dir) 28 | if filewritable(a:dir) != 2 29 | call promiscuous#helpers#exec('!mkdir -p ' . a:dir) 30 | endif 31 | endfunction 32 | -------------------------------------------------------------------------------- /autoload/promiscuous/session.vim: -------------------------------------------------------------------------------- 1 | function! promiscuous#session#clean() 2 | call promiscuous#helpers#exec('bufdo bd') 3 | endfunction 4 | 5 | function! promiscuous#session#file() 6 | let l:session_name = promiscuous#session#name() 7 | return g:promiscuous_dir . '/' . l:session_name . '.vim' 8 | endfunction 9 | 10 | function! promiscuous#session#load(branch) 11 | let l:session_file = promiscuous#session#file() 12 | 13 | if filereadable(l:session_file) 14 | call promiscuous#helpers#exec('source ' . l:session_file, 1) 15 | call promiscuous#undo#save() 16 | else 17 | call promiscuous#session#save(a:branch) 18 | endif 19 | endfunction 20 | 21 | function! promiscuous#session#name() 22 | let l:git_branch = promiscuous#git#branch() 23 | let l:session_name = getcwd() . '/' . l:git_branch 24 | let l:stripped = substitute(l:session_name, $HOME . '/', '', '') 25 | return promiscuous#helpers#dasherize(l:stripped) 26 | endfunction 27 | 28 | function! promiscuous#session#save(branch) 29 | let l:session_file = promiscuous#session#file() 30 | call promiscuous#helpers#mkdir(g:promiscuous_dir) 31 | call promiscuous#undo#save() 32 | call promiscuous#helpers#exec('mksession! ' . l:session_file, 1) 33 | endfunction 34 | -------------------------------------------------------------------------------- /autoload/promiscuous/tmux.vim: -------------------------------------------------------------------------------- 1 | function! promiscuous#tmux#refresh() 2 | call promiscuous#helpers#exec('![ -n "$TMUX" ] && tmux refresh-client -S') 3 | endfunction 4 | -------------------------------------------------------------------------------- /autoload/promiscuous/undo.vim: -------------------------------------------------------------------------------- 1 | function! promiscuous#undo#save() 2 | let l:undo_name = promiscuous#session#name() 3 | let l:undo_dir = g:promiscuous_dir . '/' . l:undo_name 4 | call promiscuous#helpers#mkdir(l:undo_dir) 5 | 6 | set noundofile 7 | let &undodir = l:undo_dir 8 | set undofile 9 | endfunction 10 | -------------------------------------------------------------------------------- /plugin/promiscuous.vim: -------------------------------------------------------------------------------- 1 | set sessionoptions=blank,buffers,curdir,folds,help,tabpages,winsize 2 | set undolevels=1000 3 | set undoreload=10000 4 | 5 | if !exists('g:promiscuous_dir') 6 | " The directory to store all sessions and undo history 7 | let g:promiscuous_dir = $HOME . '/.vim/promiscuous' 8 | endif 9 | 10 | if !exists('g:promiscuous_load') 11 | " The callback used to load a session 12 | let g:promiscuous_load = 'promiscuous#session#load' 13 | endif 14 | 15 | if !exists('g:promiscuous_prefix') 16 | " The prefix prepended to all commit, stash, and log messages 17 | let g:promiscuous_prefix = '[Promiscuous]' 18 | endif 19 | 20 | if !exists('g:promiscuous_save') 21 | " The callback used to save a session 22 | let g:promiscuous_save = 'promiscuous#session#save' 23 | endif 24 | 25 | if !exists('g:promiscuous_verbose') 26 | " Log all executed commands with echom 27 | let g:promiscuous_verbose = 0 28 | endif 29 | 30 | command! -nargs=? Promiscuous :call Promiscuous() 31 | 32 | function! Promiscuous(...) 33 | if a:0 > 0 34 | if type(a:1) == type([]) 35 | if len(a:1) == 0 " Handles pressing ESC in fzf 36 | return 1 37 | else 38 | let l:branch = a:1[-1] 39 | end 40 | else 41 | let l:branch = a:1 42 | endif 43 | 44 | call promiscuous#helpers#clear() 45 | call promiscuous#git#stash() 46 | call promiscuous#git#commit() 47 | 48 | let l:branch_was = promiscuous#git#branch() 49 | call call(g:promiscuous_save, [l:branch_was], {}) 50 | 51 | call promiscuous#session#clean() 52 | call promiscuous#git#checkout(l:branch) 53 | 54 | let l:branch = promiscuous#git#branch() 55 | call call(g:promiscuous_load, [l:branch], {}) 56 | 57 | call promiscuous#git#commit_pop() 58 | call promiscuous#git#stash_pop() 59 | call promiscuous#tmux#refresh() 60 | 61 | call promiscuous#helpers#log(l:branch_was . ' to ' . l:branch) 62 | 63 | redraw! 64 | else 65 | call promiscuous#autocomplete#branches() 66 | endif 67 | endfunction 68 | --------------------------------------------------------------------------------