├── rspec ├── .gitignore ├── vim ├── backup │ └── .gitignore ├── .gitignore ├── ftplugin │ ├── make.vim │ ├── help.vim │ ├── markdown.vim │ ├── crontab.vim │ ├── python.vim │ ├── coffee.vim │ └── html.vim ├── coc-settings.json ├── plugin │ ├── detect_jpbuilder.vim │ ├── write_as_root.vim │ ├── detect_slimbars.vim │ ├── execute_as_vimscript.vim │ ├── README.markdown │ ├── always_edit_if_swap_exists.vim │ ├── bubbling.vim │ ├── strip_trailing_whitespace.vim │ ├── slimify.vim │ ├── tmux_fixes.vim │ ├── not_rocket.vim │ ├── remember_last_location_in_file.vim │ ├── show_vim_syntax_scope.vim │ ├── quick_filetypes.vim │ ├── elixir_runner.vim │ ├── remove.vim │ ├── rspec_should_to_expect.vim │ ├── iterm2_cursors.vim │ ├── highlight_nbsp_and_trailing_whitespace.vim │ ├── exercism.vim │ ├── nerdtree_find_if_findable.vim │ ├── lab.vim │ ├── promote_to_let.vim │ ├── related_file_for_phoenix_live_view.vim │ ├── pasteboard.vim │ ├── octopress.vim │ ├── saveas_improved.vim │ └── squirrel.vim ├── config │ ├── statusline.vim │ ├── snippets.vim │ ├── plugins │ │ └── coc.vim │ ├── mappings.vim │ ├── settings.vim │ └── plugins.vim ├── colors │ └── blackboard.vim └── autoload │ └── plug.vim ├── gemrc ├── tmp └── devbox ├── bin ├── dock_autohide_on.sh ├── dock_autohide_off.sh ├── tmux-shell-wrapper ├── tmux-vim-select-pane ├── auctionet_icon ├── chrome_reload ├── note_to_self.rb └── imgcat ├── bash_profile ├── bash ├── completion.sh ├── path.sh ├── env.sh ├── lib │ ├── rake-completion.rb │ └── git-prompt.sh ├── prompt.sh ├── functions.sh └── aliases.sh ├── gdbinit ├── default-gems ├── vimrc ├── git_template └── hooks │ ├── samples │ ├── post-commit.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── applypatch-msg.sample │ ├── post-receive.sample │ ├── commit-msg.sample │ ├── prepare-commit-msg.sample │ ├── pre-commit.sample │ ├── update.sample │ └── pre-rebase.sample │ ├── pre-commit-trailing-whitespace.sh │ ├── pre-commit │ ├── pre-commit-keywords.rb │ └── commit-msg ├── gitattributes ├── railsrc ├── irbrc ├── gitignore ├── inputrc ├── Rakefile ├── extras ├── Brewfile ├── mac_os_defaults.sh └── VibrantInk.itermcolors ├── gitconfig ├── bashrc ├── tmux.conf └── README.markdown /rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .squirrel 2 | -------------------------------------------------------------------------------- /vim/backup/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /vim/.gitignore: -------------------------------------------------------------------------------- 1 | .netrwhist 2 | -------------------------------------------------------------------------------- /gemrc: -------------------------------------------------------------------------------- 1 | --- 2 | gem: --no-document 3 | -------------------------------------------------------------------------------- /tmp/devbox: -------------------------------------------------------------------------------- 1 | /home/henrik/.devbox/code/.dotfiles -------------------------------------------------------------------------------- /vim/ftplugin/make.vim: -------------------------------------------------------------------------------- 1 | setlocal noexpandtab 2 | -------------------------------------------------------------------------------- /vim/coc-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "suggest.noselect": true 3 | } 4 | -------------------------------------------------------------------------------- /vim/ftplugin/help.vim: -------------------------------------------------------------------------------- 1 | " Close with just "q". 2 | map q :q 3 | -------------------------------------------------------------------------------- /vim/plugin/detect_jpbuilder.vim: -------------------------------------------------------------------------------- 1 | au BufRead,BufNewFile *.{jpbuilder} set ft=ruby 2 | -------------------------------------------------------------------------------- /vim/plugin/write_as_root.vim: -------------------------------------------------------------------------------- 1 | " Save a file as root. 2 | cabbrev w!! w !sudo tee % > /dev/null:e! 3 | -------------------------------------------------------------------------------- /vim/plugin/detect_slimbars.vim: -------------------------------------------------------------------------------- 1 | " These files are also Slim. 2 | au BufRead,BufNewFile *.{slimbars} set ft=slim 3 | -------------------------------------------------------------------------------- /bin/dock_autohide_on.sh: -------------------------------------------------------------------------------- 1 | osascript -e 'tell application "System Events" to set the autohide of the dock preferences to true' 2 | -------------------------------------------------------------------------------- /bin/dock_autohide_off.sh: -------------------------------------------------------------------------------- 1 | osascript -e 'tell application "System Events" to set the autohide of the dock preferences to false' 2 | -------------------------------------------------------------------------------- /vim/ftplugin/markdown.vim: -------------------------------------------------------------------------------- 1 | " r to render Markdown in browser. 2 | " Relies on the vim-markdown-preview plugin. 3 | map r :Mm 4 | -------------------------------------------------------------------------------- /vim/plugin/execute_as_vimscript.vim: -------------------------------------------------------------------------------- 1 | " Execute selection or current line as Vimscript. 2 | vnoremap x y:@" 3 | nnoremap x yy:@" 4 | -------------------------------------------------------------------------------- /vim/plugin/README.markdown: -------------------------------------------------------------------------------- 1 | This directory is where I put quick and dirty plugins that I've yet to extract. 2 | 3 | Full plugins go in .vim/config/plugins.vim. 4 | -------------------------------------------------------------------------------- /vim/ftplugin/crontab.vim: -------------------------------------------------------------------------------- 1 | " Unbreak 'crontab -e' with Vim: http://drawohara.com/post/6344279/crontab-temp-file-must-be-edited-in-place 2 | setlocal nobackup nowritebackup 3 | -------------------------------------------------------------------------------- /vim/plugin/always_edit_if_swap_exists.vim: -------------------------------------------------------------------------------- 1 | " 'Edit anyway' if swap file exists. 2 | " Less annoying, as long as your Vim is stable. 3 | au SwapExists * let v:swapchoice = 'e' 4 | -------------------------------------------------------------------------------- /vim/ftplugin/python.vim: -------------------------------------------------------------------------------- 1 | setlocal shiftwidth=4 " Spaces used for autoindent and commands like >>. 2 | setlocal softtabstop=4 " Spaces inserted by . 3 | -------------------------------------------------------------------------------- /bash_profile: -------------------------------------------------------------------------------- 1 | # .bash_profile is loaded in login shells. 2 | # .bashrc is loaded in non-login shells. It includes all the good stuff, so we source it. 3 | 4 | [[ -s ~/.bashrc ]] && source ~/.bashrc 5 | -------------------------------------------------------------------------------- /vim/plugin/bubbling.vim: -------------------------------------------------------------------------------- 1 | " These rely on the vim-unimpaired plugin. 2 | 3 | " Move single lines. 4 | nmap [e 5 | nmap ]e 6 | " Move multiple lines. 7 | vmap [egv 8 | vmap ]egv 9 | -------------------------------------------------------------------------------- /vim/plugin/strip_trailing_whitespace.vim: -------------------------------------------------------------------------------- 1 | " Strip trailing whitespace. 2 | " http://vim.wikia.com/wiki/Remove_unwanted_spaces 3 | command! Strip let _s=@/:%s/\s\+$//e:let @/=_s:nohl 4 | -------------------------------------------------------------------------------- /bash/completion.sh: -------------------------------------------------------------------------------- 1 | # Bash completion for git commands and branch names. 2 | . ~/.dotfiles/bash/lib/git-completion.sh 3 | 4 | # Rake task completion. 5 | complete -C ~/.bash/lib/rake-completion.rb -o default rake 6 | -------------------------------------------------------------------------------- /gdbinit: -------------------------------------------------------------------------------- 1 | # Breakpoints to enable the "backtrace" ("bt") command in the gdb console after various Xcode errors. 2 | fb -[NSException raise] 3 | fb -[_NSZombie release] 4 | fb szone_error 5 | fb objc_exception_throw 6 | -------------------------------------------------------------------------------- /default-gems: -------------------------------------------------------------------------------- 1 | # https://github.com/asdf-vm/asdf-ruby#default-gems 2 | # Note that this file can have line comments but not end-of-line comments. 3 | 4 | bundler 5 | 6 | # Ruby language server used in Vim. 7 | solargraph 8 | -------------------------------------------------------------------------------- /vim/plugin/slimify.vim: -------------------------------------------------------------------------------- 1 | function! s:SlimifyHaml() 2 | call system("haml2slim " . shellescape(expand("%"))) 3 | vsp 4 | exe "e " . expand("%:r") . ".slim" 5 | endfunction 6 | 7 | command! SlimifyHaml call SlimifyHaml() 8 | -------------------------------------------------------------------------------- /vimrc: -------------------------------------------------------------------------------- 1 | let mapleader = "," 2 | 3 | source ~/.vim/config/plugins.vim 4 | 5 | source ~/.vim/config/settings.vim 6 | source ~/.vim/config/statusline.vim 7 | source ~/.vim/config/mappings.vim 8 | source ~/.vim/config/snippets.vim 9 | -------------------------------------------------------------------------------- /git_template/hooks/samples/post-commit.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script that is called after a successful 4 | # commit is made. 5 | # 6 | # To enable this hook, rename this file to "post-commit". 7 | 8 | : Nothing 9 | -------------------------------------------------------------------------------- /git_template/hooks/samples/post-update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare a packed repository for use over 4 | # dumb transports. 5 | # 6 | # To enable this hook, rename this file to "post-update". 7 | 8 | exec git update-server-info 9 | -------------------------------------------------------------------------------- /vim/plugin/tmux_fixes.vim: -------------------------------------------------------------------------------- 1 | " http://snk.tuxfamily.org/log/vim-256color-bce.html 2 | " Disable Background Color Erase (BCE) so that color schemes 3 | " work properly when Vim is used inside tmux and GNU screen. 4 | if &term =~ '256color' 5 | set t_ut= 6 | endif 7 | -------------------------------------------------------------------------------- /vim/ftplugin/coffee.vim: -------------------------------------------------------------------------------- 1 | " r to run CoffeeScript. 2 | " Relies on the vim-coffee-script plugin. 3 | map r :CoffeeRun 4 | 5 | " R to see CoffeeScript compiled. 6 | " Relies on the vim-coffee-script plugin. 7 | map R :CoffeeCompile 8 | -------------------------------------------------------------------------------- /vim/plugin/not_rocket.vim: -------------------------------------------------------------------------------- 1 | " Convert Ruby 1.8 hash rockets to 1.9 JSON style hashes. 2 | " Based on https://github.com/hashrocket/dotmatrix/commit/6c77175adc19e94594e8f2d6ec29371f5539ceeb 3 | command! -bar -range=% NotRocket execute ',s/:\(\w\+[?!]\?\)\s*=>/\1:/e' . (&gdefault ? '' : 'g') 4 | -------------------------------------------------------------------------------- /gitattributes: -------------------------------------------------------------------------------- 1 | # Make git's diff context more language-aware. 2 | # https://tekin.co.uk/2020/10/better-git-diff-output-for-ruby-python-elixir-and-more 3 | *.css diff=css 4 | *.html diff=html 5 | *.ex diff=elixir 6 | *.exs diff=elixir 7 | *.md diff=markdown 8 | *.rb diff=ruby 9 | *.rake diff=ruby 10 | -------------------------------------------------------------------------------- /vim/plugin/remember_last_location_in_file.vim: -------------------------------------------------------------------------------- 1 | " Remember last location in file, but not for commit messages. 2 | augroup remember_last_file_loc 3 | autocmd! 4 | autocmd BufReadPost * 5 | \ if &filetype !~ 'commit\c' && line("'\"") > 0 && line("'\"") <= line("$") | 6 | \ exe "normal g'\"" | 7 | \ endif 8 | augroup END 9 | -------------------------------------------------------------------------------- /railsrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # Ruby on Rails console. 3 | 4 | # Turn ActiveRecord's logging off and on. 5 | 6 | def log_off 7 | puts "== Logging to log file." 8 | ActiveRecord::Base.logger.level = 1 # warn (?) 9 | nil 10 | end 11 | 12 | def log_on 13 | puts "== Logging to console." 14 | ActiveRecord::Base.logger.level = 0 # debug (?) 15 | nil 16 | end 17 | -------------------------------------------------------------------------------- /vim/plugin/show_vim_syntax_scope.vim: -------------------------------------------------------------------------------- 1 | " Print highlighting scope at the current position. 2 | " http://vim.wikia.com/wiki/Identify_the_syntax_highlighting_group_used_at_the_cursor 3 | map S :echo "hi<" . synIDattr(synID(line("."),col("."),1),"name") . '> trans<' 4 | \ . synIDattr(synID(line("."),col("."),0),"name") . "> lo<" 5 | \ . synIDattr(synIDtrans(synID(line("."),col("."),1)),"name") . ">" 6 | -------------------------------------------------------------------------------- /bin/tmux-shell-wrapper: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Integrate with OS X pasteboard. Do: 4 | # brew install reattach-to-user-namespace 5 | # https://github.com/ChrisJohnsen/tmux-MacOSX-pasteboard 6 | # 7 | # If not installed (perhaps in an Ubuntu VM), just use bash. 8 | 9 | if command -v reattach-to-user-namespace >/dev/null; then 10 | reattach-to-user-namespace -l $SHELL 11 | else 12 | $SHELL -l 13 | fi 14 | -------------------------------------------------------------------------------- /vim/ftplugin/html.vim: -------------------------------------------------------------------------------- 1 | " r to open HTML file in browser while keeping focus in Vim. 2 | map r :call system("open -g " . shellescape(expand("%"))):echo "Opened in background browser." 3 | 4 | " R to reload the active Chrome tab. 5 | map R :call system("osascript -e 'tell app \"Google Chrome\" to tell the active tab of the first window to reload'"):echo "Reloaded in background browser." 6 | -------------------------------------------------------------------------------- /bash/path.sh: -------------------------------------------------------------------------------- 1 | # Dotfile bins. 2 | export PATH=~/.bin:$PATH 3 | 4 | # Homebrew. 5 | [ -f /opt/homebrew/bin/brew ] && eval "$(/opt/homebrew/bin/brew shellenv)" 6 | 7 | # Ubuntu Ruby gem binaries. 8 | export PATH=/var/lib/gems/1.8/bin:$PATH 9 | 10 | # MySQL. 11 | export PATH=/usr/local/mysql/bin:$PATH 12 | 13 | # Make autojump's "j" available. 14 | # brew install autojump 15 | [ -f /opt/homebrew/etc/profile.d/autojump.sh ] && . /opt/homebrew/etc/profile.d/autojump.sh 16 | -------------------------------------------------------------------------------- /bash/env.sh: -------------------------------------------------------------------------------- 1 | export EDITOR='vim' 2 | export OCTOPRESS_EDITOR='vim +' # Go to last line. 3 | # Run Rubinius in 1.9 mode. 4 | export RBXOPT=-X19 5 | # No duplicates in history. 6 | export HISTCONTROL=ignoredups 7 | 8 | # Don't update the whole world when "brew install"-ing (https://github.com/Homebrew/brew/issues/1670). 9 | export HOMEBREW_NO_AUTO_UPDATE=1 10 | 11 | # Colors in Vim. 12 | export TERM='xterm-256color' 13 | 14 | # Auctionet Devbox 15 | export DEV_HOST='devbox' 16 | -------------------------------------------------------------------------------- /git_template/hooks/samples/pre-applypatch.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed 4 | # by applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. 8 | # 9 | # To enable this hook, rename this file to "pre-applypatch". 10 | 11 | . git-sh-setup 12 | test -x "$GIT_DIR/hooks/pre-commit" && 13 | exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} 14 | : 15 | -------------------------------------------------------------------------------- /vim/plugin/quick_filetypes.vim: -------------------------------------------------------------------------------- 1 | " Quicker filetype setting: 2 | " :F html 3 | " instead of 4 | " :setf html 5 | " Can tab-complete filetype. 6 | command! -nargs=1 -complete=filetype F set filetype= 7 | 8 | " Even quicker setting often-used filetypes. 9 | command! FC set filetype=coffee 10 | command! FH set filetype=haml 11 | command! FS set filetype=slim 12 | command! FR set filetype=ruby 13 | command! FV set filetype=vim 14 | command! FM set filetype=markdown 15 | command! FE set filetype=elixir 16 | -------------------------------------------------------------------------------- /vim/plugin/elixir_runner.vim: -------------------------------------------------------------------------------- 1 | " ,r to run the current buffer as Elixir (even if it's not written to a file). 2 | " Only enabled when the filetype is 'elixir'. 3 | " 4 | " By Henrik Nyh 2015-06-24 under the MIT license. 5 | 6 | augroup run_elixir 7 | autocmd! 8 | autocmd FileType elixir map r :RunElixir 9 | augroup END 10 | 11 | command! RunElixir call RunElixir() 12 | 13 | function! s:RunElixir() 14 | exe "! elixir -e " . shellescape(join(getline(1, "$"), "\n"), 1) 15 | endfunction 16 | -------------------------------------------------------------------------------- /vim/plugin/remove.vim: -------------------------------------------------------------------------------- 1 | " http://vim.wikia.com/wiki/Delete_files_with_a_Vim_command 2 | function! s:DeleteFile(...) 3 | if(exists('a:1')) 4 | let theFile=a:1 5 | else 6 | let theFile=expand('%:p') 7 | endif 8 | let delStatus=delete(theFile) 9 | if(delStatus == 0) 10 | bw! 11 | echo "Deleted " . theFile 12 | else 13 | echohl WarningMsg 14 | echo "Failed to delete " . theFile 15 | echohl None 16 | endif 17 | return delStatus 18 | endfunction 19 | 20 | com! Remove call DeleteFile() 21 | -------------------------------------------------------------------------------- /vim/plugin/rspec_should_to_expect.vim: -------------------------------------------------------------------------------- 1 | " Turn RSpec 'should' into the new 'expect' style. 2 | " Also turns 'should ==' into 'expect(…).to eq'. 3 | " The command acts on the current line or in a given range (e.g. visual range). 4 | 5 | function! s:ShouldToExpect() 6 | .s/^\(\s\+\)\(.\+\)\.should\>/\1expect(\2).to/e 7 | .s/^\(\s\+\)\(.\+\)\.should_not\>/\1expect(\2).not_to/e 8 | .s/\(expect(.\+)\.\(to\|not_to\)\) ==/\1 eq/e 9 | endfunction 10 | 11 | command! -range Expect execute ',call ShouldToExpect()' 12 | -------------------------------------------------------------------------------- /vim/config/statusline.vim: -------------------------------------------------------------------------------- 1 | " %< truncation point 2 | " \ space 3 | " %f relative path to file 4 | " %m modified flag [+] (modified), [-] (unmodifiable) or nothing 5 | " %r readonly flag [RO] 6 | " %y filetype [ruby] 7 | " %= split point for left and right justification 8 | " %-14.( %) block of fixed width 14 characters 9 | " %l current line 10 | " %c current column 11 | " %V current virtual column as -{num} if different from %c 12 | " %P percentage through buffer 13 | set statusline=%#warningmsg#%*%<\ %f\ %m%r%y\ %=%-14.(%l,%c%V%)\ %P\ 14 | -------------------------------------------------------------------------------- /vim/config/snippets.vim: -------------------------------------------------------------------------------- 1 | iabbrev ddate =strftime("%F") 2 | iabbrev dnc DO NOT COMMIT 3 | iabbrev lorem Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 4 | -------------------------------------------------------------------------------- /git_template/hooks/samples/applypatch-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message taken by 4 | # applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. The hook is 8 | # allowed to edit the commit message file. 9 | # 10 | # To enable this hook, rename this file to "applypatch-msg". 11 | 12 | . git-sh-setup 13 | test -x "$GIT_DIR/hooks/commit-msg" && 14 | exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} 15 | : 16 | -------------------------------------------------------------------------------- /vim/plugin/iterm2_cursors.vim: -------------------------------------------------------------------------------- 1 | " Different cursor for insert mode in iTerm2, with tmux workaround. 2 | " https://gist.github.com/1195581 3 | " http://sourceforge.net/mailarchive/forum.php?thread_name=AANLkTinkbdoZ8eNR1X2UobLTeww1jFrvfJxTMfKSq-L%2B%40mail.gmail.com&forum_name=tmux-users 4 | 5 | if exists('$TMUX') 6 | let &t_SI = "\Ptmux;\\]50;CursorShape=1\x7\\\" 7 | let &t_EI = "\Ptmux;\\]50;CursorShape=0\x7\\\" 8 | else 9 | let &t_SI = "\]50;CursorShape=1\x7" 10 | let &t_EI = "\]50;CursorShape=0\x7" 11 | endif 12 | -------------------------------------------------------------------------------- /vim/plugin/highlight_nbsp_and_trailing_whitespace.vim: -------------------------------------------------------------------------------- 1 | " https://github.com/aalin/dotfiles/blob/master/vimrc 2 | " 3 | " NOTE: Can only have three of these, using match/2match/3match: 4 | " http://stackoverflow.com/a/18167529/6962 5 | 6 | augroup highlight_nbsp_and_trailing_ws 7 | autocmd! 8 | 9 | autocmd BufNewFile,BufRead * highlight nbsp ctermbg=Red 10 | autocmd BufNewFile,BufRead * match nbsp "[\xc2\xa0]" 11 | 12 | autocmd BufNewFile,BufRead * highlight trailing_spaces ctermbg=Red 13 | autocmd BufNewFile,BufRead * 2match trailing_spaces "\s\+$" 14 | augroup END 15 | -------------------------------------------------------------------------------- /bash/lib/rake-completion.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Complete rake tasks script for bash 4 | # Save it somewhere and then add 5 | # complete -C path/to/script -o default rake 6 | # to your ~/.bashrc 7 | # Nicholas Seckar 8 | 9 | exit 0 unless File.file?(File.join(Dir.pwd, 'Rakefile')) 10 | exit 0 unless /^rake(?:\s+([-\w]+))?\s*$/ =~ ENV["COMP_LINE"] 11 | 12 | task_prefix = $1 13 | 14 | tasks = `rake --tasks`.split("\n")[1..-1].collect {|line| line.split[1]} 15 | tasks = tasks.select {|t| /^#{Regexp.escape task_prefix}/ =~ t} if task_prefix 16 | puts tasks 17 | exit 0 18 | 19 | -------------------------------------------------------------------------------- /vim/plugin/exercism.vim: -------------------------------------------------------------------------------- 1 | " Run exercism.io tests. 2 | " 3 | " By Henrik Nyh under the MIT license. 4 | 5 | function! s:ExercismTest() 6 | if &filetype == "ruby" 7 | ! ruby %:h/*_test.rb 8 | elseif &filetype == "javascript" 9 | ! jasmine-node %:h 10 | elseif &filetype == "coffee" 11 | ! jasmine-node --coffee %:h 12 | elseif &filetype == "python" 13 | ! python %:h/*_test.py 14 | elseif &filetype == "elixir" 15 | ! elixir %:h/*_test.exs 16 | endif 17 | endfunction 18 | 19 | command! ExercismTest call ExercismTest() 20 | command! ExercismSubmit ! exercism submit % 21 | -------------------------------------------------------------------------------- /vim/plugin/nerdtree_find_if_findable.vim: -------------------------------------------------------------------------------- 1 | " I too often trigger NERDTreeFind after opening a project, which reveals 2 | " the project dir and thus shows the parent dir, when I wanted to just 3 | " show the tree. This plugin fixes that. 4 | " 5 | " By Henrik Nyh under the MIT license. 6 | 7 | function! s:NERDTreeFindIfFindable() 8 | if len(expand("%")) 9 | " If current buffer has name, find it. 10 | NERDTreeFind 11 | else 12 | " If current buffer is unsaved, just show tree. 13 | NERDTree 14 | endif 15 | endfunction 16 | 17 | command! NERDTreeFindIfFindable call NERDTreeFindIfFindable() 18 | -------------------------------------------------------------------------------- /git_template/hooks/samples/post-receive.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script for the "post-receive" event. 4 | # 5 | # The "post-receive" script is run after receive-pack has accepted a pack 6 | # and the repository has been updated. It is passed arguments in through 7 | # stdin in the form 8 | # 9 | # For example: 10 | # aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master 11 | # 12 | # see contrib/hooks/ for a sample, or uncomment the next line and 13 | # rename the file to "post-receive". 14 | 15 | #. /usr/share/doc/git-core/contrib/hooks/post-receive-email 16 | -------------------------------------------------------------------------------- /irbrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Pre-defined hash and array to experiment with. 4 | # http://stackoverflow.com/questions/123494/whats-your-favourite-irb-trick/123847#123847 5 | HASH = { :one => 1, :two => 2, :three => 3, :four => 4, :five => 5 } 6 | ARRAY = %w[one two three four five] 7 | 8 | # Quick benchmarking 9 | # http://stackoverflow.com/questions/123494/whats-your-favourite-irb-trick/123834#123834 10 | def bench(repetitions=100, &block) 11 | require "benchmark" 12 | 13 | Benchmark.bmbm do |b| 14 | b.report {repetitions.times &block} 15 | end 16 | nil 17 | end 18 | 19 | # Load .railsrc for Rails. 20 | load File.dirname(__FILE__) + "/.railsrc" if defined?(Rails) 21 | -------------------------------------------------------------------------------- /vim/config/plugins/coc.vim: -------------------------------------------------------------------------------- 1 | " Ruby: https://github.com/neoclide/coc-solargraph 2 | " ESLint: https://github.com/neoclide/coc-eslint 3 | " TypeScript: https://github.com/neoclide/coc-tsserver 4 | let g:coc_global_extensions = ['coc-solargraph', 'coc-eslint', 'coc-tsserver'] 5 | 6 | " Fix buggy behaviour where cursor disappears: https://github.com/neoclide/coc.nvim/issues/1775#issuecomment-757764053 7 | let g:coc_disable_transparent_cursor = 1 8 | 9 | " In Devbox, use the global node, since per-project nodes may be too old for coc.nvim. 10 | if exists('$DEVBOX') 11 | let g:coc_node_path = '/devbox/tmp/devbox/binwrappers/node' 12 | endif 13 | 14 | " Rename symbol in project. 15 | nmap s (coc-rename) 16 | -------------------------------------------------------------------------------- /gitignore: -------------------------------------------------------------------------------- 1 | # OS X. 2 | .DS_Store 3 | 4 | # Bundler crap. 5 | bundle/install.log 6 | 7 | # Because Vim :( 8 | :w 9 | 10 | # iPhone development. 11 | # http://www.adminmyserver.com/articles/gitignore-for-iphone-xcode-development 12 | 13 | # Build products. 14 | # TODO: Make "build" more specific. Collided with CI build scripts. 15 | #build 16 | *.[oa] 17 | 18 | # Temp nibs and swap files. 19 | *~.nib 20 | *.swp 21 | *~ 22 | 23 | # User-specific Xcode stuff. 24 | *.mode1v3 25 | *.mode2v3 26 | *.pbxuser 27 | *.perspectivev3 28 | 29 | # https://github.com/henrik/dotfiles/blob/master/vim/plugin/squirrel.vim 30 | .squirrel 31 | 32 | # https://serverfault.com/questions/201294/nfsxxxx-files-appearing-what-are-those 33 | .nfs* 34 | -------------------------------------------------------------------------------- /git_template/hooks/pre-commit-trailing-whitespace.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Git pre-commit hook that prevents committing with trailing 4 | # whitespace or a blank line at EOL. 5 | # 6 | # Based on 7 | # http://madebyted.com/blog/fight-whitespace-with-git/ 8 | 9 | if git rev-parse --verify HEAD >/dev/null 2>&1 10 | then 11 | against=HEAD 12 | else 13 | # Initial commit: diff against an empty tree object 14 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 15 | fi 16 | 17 | # If there are whitespace errors, print the offending file names and fail. 18 | git diff-index --check --cached $against -- 19 | 20 | exitcode=$? 21 | if [ $exitcode != 0 ]; then 22 | echo "To commit anyway, use --no-verify" 23 | exit $exitcode 24 | fi 25 | -------------------------------------------------------------------------------- /git_template/hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Runs all executable pre-commit-* hooks and exits after, 4 | # if any of them was not successful. 5 | # 6 | # Based on 7 | # http://osdir.com/ml/git/2009-01/msg00308.html 8 | 9 | data=$(cat) 10 | exitcodes=() 11 | hookname=`basename $0` 12 | 13 | # Run each hook, passing through STDIN and storing the exit code. 14 | # We don't want to bail at the first failure, as the user might 15 | # then bypass the hooks without knowing about additional issues. 16 | 17 | for hook in $GIT_DIR/hooks/$hookname-*; do 18 | test -x "$hook" || continue 19 | echo "$data" | "$hook" 20 | exitcodes+=($?) 21 | done 22 | 23 | # If any exit code isn't 0, bail. 24 | 25 | for i in "${exitcodes[@]}"; do 26 | [ "$i" == 0 ] || exit $i 27 | done 28 | -------------------------------------------------------------------------------- /vim/plugin/lab.vim: -------------------------------------------------------------------------------- 1 | " :Lab to open an Elixir buffer with some boilerplate to experiment with stuff. 2 | " By Henrik Nyh under the MIT license. 3 | 4 | command! Lab call Lab() 5 | 6 | function! s:Lab() 7 | tabe 8 | set filetype=elixir 9 | 10 | " Make it a scratch (temporary) buffer. 11 | setlocal buftype=nofile bufhidden=wipe noswapfile 12 | 13 | " Close on q. 14 | "map q ZZ 15 | 16 | " Some boilerplate please. 17 | " Lab + Run so you can e.g. implement a macro in Lab and require it in Run. 18 | call append(0, ["defmodule Lab do", "end", "", "defmodule Run do", " def run do", " end", "end", "", "Run.run"]) 19 | 20 | " Delete blank line at end. 21 | $d 22 | 23 | " Jump to first line. 24 | 1 25 | endfunction 26 | -------------------------------------------------------------------------------- /vim/plugin/promote_to_let.vim: -------------------------------------------------------------------------------- 1 | " Promote variable to RSpec let. 2 | " Based on 3 | " https://github.com/myronmarston/vim_files/commit/ed60919f1857359da617491a7d7c14e8d4befae0 4 | " 5 | " Given either of 6 | " 7 | " foo = x 8 | " @foo = x 9 | " 10 | " on the current line or in a given range (e.g. visual range), 11 | " this command moves the assignments out to an RSpec let: 12 | " 13 | " let(:foo) { x } 14 | " 15 | " If you run it on a line that already does `let(:foo)`, it goes the other 16 | " way, making it a local variable. 17 | 18 | function! s:PromoteToLet() 19 | if getline('.') =~ 'let(:' 20 | .s/let(:\(\w\+\)) { \(.*\) }/\1 = \2/ 21 | else 22 | .s/@\?\(\w\+\) = \(.*\)$/let(:\1) { \2 }/ 23 | endif 24 | endfunction 25 | 26 | command! -range Let execute ',call PromoteToLet()' 27 | -------------------------------------------------------------------------------- /bin/tmux-vim-select-pane: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Like `tmux select-pane`, but if Vim is running in the current pane it sends a 4 | # keystroke to Vim instead to let it decide whether it's going to switch windows 5 | # internally or switch tmux panes. 6 | # 7 | # https://gist.github.com/mislav/5189704 8 | 9 | set -e 10 | 11 | # gets the tty of active tmux pane 12 | active_tty="$(tmux list-panes -F '#{pane_active}#{pane_tty}' | grep '^1')" 13 | 14 | # checks if there's a foreground Vim process in attached to that tty 15 | if ps c -o 'state=,command=' -t "${active_tty#1}" | grep '+' | grep -iE '\bvim?\b' >/dev/null ; then 16 | direction="$(echo "${1#-}" | sed -e 's/D/Down/' -e 's/U/Up/' -e 's/L/Left/' -e 's/R/Right/')"; 17 | # forward the keystroke to Vim 18 | tmux send-keys C-$direction 19 | else 20 | tmux select-pane "$@" 21 | fi 22 | -------------------------------------------------------------------------------- /vim/plugin/related_file_for_phoenix_live_view.vim: -------------------------------------------------------------------------------- 1 | " Lets you use the :R command to jump between e.g. foo_live.ex and foo_live.html.leex in Phoenix LiveView. 2 | " Inspired by corresponding functionality in vim-rails. 3 | 4 | function! s:RelatedFileForPhoenixLiveView() 5 | let l:path = expand("%") 6 | if l:path =~ "/live/.*\\.ex$" 7 | let l:rel = substitute(l:path, "\\.ex$", ".html.leex", "") 8 | elseif l:path =~ "\\.html\\.leex$" 9 | let l:rel = substitute(l:path, "\\.html\\.leex$", ".ex", "") 10 | else 11 | return 12 | end 13 | 14 | if filereadable(l:rel) 15 | execute "edit" l:rel 16 | else 17 | echoerr "No such related file: " l:rel 18 | endif 19 | endfunction 20 | 21 | augroup related_file_for_phoenix_live_view 22 | autocmd! 23 | autocmd BufNewFile,BufRead */live/*.ex,*.html.leex command! -buffer R call RelatedFileForPhoenixLiveView() 24 | augroup END 25 | -------------------------------------------------------------------------------- /inputrc: -------------------------------------------------------------------------------- 1 | set show-all-if-ambiguous on 2 | set completion-ignore-case on 3 | 4 | # http://www.reddit.com/r/commandline/comments/kbeoe/you_can_make_readline_and_bash_much_more_user/ 5 | Control-j: menu-complete 6 | Control-k: menu-complete-backward 7 | 8 | # Map modifiers (xterm defaults) 9 | # 10 | # Figure out the code in Vim: 11 | # * go into insert mode: 12 | # * type ctrl+v 13 | # * type the shortcut 14 | # 15 | # opt+left, opt+right outside tmux 16 | "\e[1;9D": backward-word 17 | "\e[1;9C": forward-word 18 | # opt+left, opt+right in tmux 19 | "\e[1;3D": backward-word 20 | "\e[1;3C": forward-word 21 | # shift+left, shift+right 22 | "\e[1;2D": backward-word 23 | "\e[1;2C": forward-word 24 | 25 | # https://coderwall.com/p/oqtj8w/the-single-most-useful-thing-in-bash 26 | # E.g. type "cd" and hit up arrow to search through history for that prefix. 27 | "\e[A": history-search-backward 28 | "\e[B": history-search-forward 29 | -------------------------------------------------------------------------------- /git_template/hooks/samples/commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message. 4 | # Called by "git commit" with one argument, the name of the file 5 | # that has the commit message. The hook should exit with non-zero 6 | # status after issuing an appropriate message if it wants to stop the 7 | # commit. The hook is allowed to edit the commit message file. 8 | # 9 | # To enable this hook, rename this file to "commit-msg". 10 | 11 | # Uncomment the below to add a Signed-off-by line to the message. 12 | # Doing this in a hook is a bad idea in general, but the prepare-commit-msg 13 | # hook is more suited to it. 14 | # 15 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 16 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 17 | 18 | # This example catches duplicate Signed-off-by lines. 19 | 20 | test "" = "$(grep '^Signed-off-by: ' "$1" | 21 | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { 22 | echo >&2 Duplicate Signed-off-by lines. 23 | exit 1 24 | } 25 | -------------------------------------------------------------------------------- /bin/auctionet_icon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Usage: 4 | # 5 | # auctionet_icon ☕ 6 | 7 | # Generates an Auctionet icon with the given emoji, suitable as a Slack workspace icon. 8 | # (Not suitable for public-facing use since Apple emojis likely aren't free to use.) 9 | # 10 | # I started with this funky Cloudinary setup mostly for fun, but found that rendering Apple emoji is tricky in e.g. RMagick, too, so might as well stick with this 🤷 11 | 12 | require "base64" 13 | require "open-uri" 14 | 15 | emoji = ARGV.first 16 | 17 | unless emoji.match?(/\A\p{Emoji_presentation}\z/) 18 | abort "Please provide a single emoji as the first argument." 19 | end 20 | 21 | codepoints = emoji.codepoints.map { _1.to_s(16) }.join("-") 22 | emoji_url = "https://github.com/iamcal/emoji-data/blob/master/img-apple-160/#{codepoints}.png?raw=true" 23 | base64 = Base64.strict_encode64(emoji_url).sub("/", "_") 24 | 25 | image_url = "https://res.cloudinary.com/pairicon/image/upload/l_fetch:#{base64}/_auctionet_base_icon.png" 26 | 27 | File.write(File.expand_path("~/Downloads/auctionet_#{emoji}.png"), URI.open(image_url).read) 28 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | DO_NOT_SYMLINK = %w[ 2 | .gitignore 3 | extras 4 | Rakefile 5 | README.markdown 6 | ] 7 | 8 | 9 | def error(text) 10 | STDERR.puts "! #{text}" 11 | end 12 | 13 | def info(text) 14 | STDOUT.puts "* #{text}" 15 | end 16 | 17 | def info_rm(text) 18 | STDOUT.puts "x #{text}" 19 | end 20 | 21 | 22 | task :default => :install 23 | 24 | desc "Install dotfiles." 25 | task :install do 26 | Dir["*"].each do |file| 27 | source = File.join(Dir.pwd, file) 28 | basename = File.basename(source) 29 | next if DO_NOT_SYMLINK.include?(basename) 30 | 31 | target = File.expand_path("~/.#{basename}") 32 | 33 | if File.symlink?(target) 34 | symlink_to = File.readlink(target) 35 | info_rm "Removing symlink #{target} --> #{symlink_to}" if symlink_to != source 36 | FileUtils.rm(target) 37 | elsif File.exist?(target) 38 | error "#{target} exists. Will not automatically overwrite a non-symlink. Overwrite (y/n)?" 39 | print "? " 40 | if STDIN.gets.match(/^y/i) 41 | info_rm "Removing #{target}." 42 | FileUtils.rm_rf(target) 43 | else 44 | next 45 | end 46 | end 47 | 48 | FileUtils.ln_s(source, target) 49 | info "Creating symlink: #{target} --> #{source}" 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /extras/Brewfile: -------------------------------------------------------------------------------- 1 | # https://github.com/Homebrew/homebrew-bundle 2 | 3 | tap "homebrew/bundle" 4 | tap "homebrew/core" 5 | 6 | brew "git" 7 | brew "autojump" 8 | 9 | # Version manager used for Ruby and Elixir. 10 | brew "asdf" 11 | 12 | # May need to re-run `brew install vim` after installing Ruby with e.g. `asdf install ruby 2.6.5; asdf global ruby 2.6.5`, so it's compiled against the right Ruby. 13 | # If https://github.com/asdf-vm/asdf/pull/575 is merged, try using that in the command above (to get latest Ruby without hard-coding a number into this file). 14 | brew "vim" 15 | 16 | # Find more with `brew search my_guess_at_the_name`. 17 | cask "google-chrome" 18 | cask "iterm2" 19 | cask "spotify" 20 | cask "zoomus" # https://zoom.us 21 | cask "mailplane" 22 | cask "fantastical" 23 | cask "alfred" 24 | cask "istat-menus" 25 | cask "backblaze" 26 | cask "imageoptim" 27 | cask "licecap" 28 | cask "dash" 29 | cask "vlc" 30 | cask "firefox" 31 | 32 | # Mac App Store 33 | # "Copy Link" from the App Store UI to get the ID. 34 | mas "Divvy", id: 413857545 35 | mas "1Password", id: 1333542190 36 | mas "Slack", id: 803453959 37 | mas "Shush", id: 496437906 38 | mas "The Unarchiver", id: 425424353 39 | mas "Pages", id: 409201541 40 | mas "Numbers", id: 409203825 41 | mas "Xcode", id: 497799835 42 | -------------------------------------------------------------------------------- /vim/plugin/pasteboard.vim: -------------------------------------------------------------------------------- 1 | function! s:is_over_ssh() 2 | return !empty($SSH_CLIENT) || !empty($SSH_TTY) 3 | endfunction 4 | 5 | " Much stolen from 6 | " https://github.com/tpope/vim-unimpaired/commit/cabf4b79b1c0cd9c9a8beec303c2e6172c82d19f 7 | 8 | function! s:setup_paste() 9 | let s:paste = &paste 10 | set paste 11 | endfunction 12 | 13 | augroup pasteboard 14 | autocmd! 15 | autocmd InsertLeave * 16 | \ if exists('s:paste') | 17 | \ let &paste = s:paste | 18 | \ unlet s:paste | 19 | \ endif 20 | augroup END 21 | 22 | if is_over_ssh() 23 | nnoremap y :echoerr "Can't yank to OS X pasteboard :(" 24 | vnoremap y :echoerr "Can't yank to OS X pasteboard :(" 25 | 26 | " Paste from OS X pasteboard without messing up indent. 27 | " Stores away current paste/nopaste, sets "paste", goes into insert mode. 28 | " After you leave insert mode, it restores that old paste/nopaste state. 29 | noremap p :call setup_paste()o 30 | noremap P :call setup_paste()O 31 | else 32 | " Yank to OS X pasteboard. 33 | nnoremap y "*yy 34 | vnoremap y "*y 35 | 36 | " Paste from OS X pasteboard without messing up indent. 37 | noremap p :set paste:put *:set nopaste 38 | noremap P :set paste:put! *:set nopaste 39 | endif 40 | -------------------------------------------------------------------------------- /git_template/hooks/pre-commit-keywords.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Git pre-commit hook that prevents accidentally committing things that shouldn't be, like: 4 | # 5 | # * ":focus", used with RSpec/Guard 6 | # * "show_page", used to debug request specs 7 | # * "console.log" or "console.debug", used in JavaScript debugging 8 | # * "DO NOT COMMIT!" comments 9 | # 10 | # Modify the regexps to suit your needs. The error message shows the full regexp match, or just the first capture group, if there is one. 11 | # 12 | # To bypass this commit hook (and others), perhaps when defining ":focus" or "show_page" for the first time, commit with the "--no-verify" option. 13 | # 14 | # By Henrik Nyh 2011-10-08 under the MIT License. 15 | 16 | FORBIDDEN = [ 17 | /[\s,](:focus)\b/, 18 | /\bshow_page\b/, 19 | /\bconsole\.log\b/, 20 | /\bconsole\.debug\b/, 21 | /\bbinding\.pry\b/, 22 | /\bdo not commit\b/i 23 | ] 24 | 25 | full_diff = `git diff --cached --`.force_encoding("ASCII-8BIT") 26 | 27 | full_diff.scan(%r{^\+\+\+ b/(.+)\n@@.*\n([\s\S]*?)(?:^diff|\z)}).each do |file, diff| 28 | added = diff.split("\n").select { |x| x.start_with?("+") }.join("\n") 29 | if FORBIDDEN.any? { |re| added.match(re) } 30 | puts %{Git hook forbids adding "#{$1 || $&}" to #{file}} 31 | puts "To commit anyway, use --no-verify" 32 | exit 1 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /git_template/hooks/samples/prepare-commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare the commit log message. 4 | # Called by "git commit" with the name of the file that has the 5 | # commit message, followed by the description of the commit 6 | # message's source. The hook's purpose is to edit the commit 7 | # message file. If the hook fails with a non-zero status, 8 | # the commit is aborted. 9 | # 10 | # To enable this hook, rename this file to "prepare-commit-msg". 11 | 12 | # This hook includes three examples. The first comments out the 13 | # "Conflicts:" part of a merge commit. 14 | # 15 | # The second includes the output of "git diff --name-status -r" 16 | # into the message, just before the "git status" output. It is 17 | # commented because it doesn't cope with --amend or with squashed 18 | # commits. 19 | # 20 | # The third example adds a Signed-off-by line to the message, that can 21 | # still be edited. This is rarely a good idea. 22 | 23 | case "$2,$3" in 24 | merge,) 25 | /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; 26 | 27 | # ,|template,) 28 | # /usr/bin/perl -i.bak -pe ' 29 | # print "\n" . `git diff --cached --name-status -r` 30 | # if /^#/ && $first++ == 0' "$1" ;; 31 | 32 | *) ;; 33 | esac 34 | 35 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 36 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 37 | -------------------------------------------------------------------------------- /gitconfig: -------------------------------------------------------------------------------- 1 | [user] 2 | name = Henrik Nyh 3 | email = henrik@nyh.se 4 | [core] 5 | excludesfile = /Users/henrik/.gitignore 6 | ignorecase = false 7 | pager = less -R 8 | hooksPath = /devbox/support/default_githooks 9 | [color] 10 | status = auto 11 | diff = auto 12 | branch = auto 13 | [branch "master"] 14 | merge = master 15 | [alias] 16 | who = shortlog -s -n --no-merges 17 | standup = shortlog --since=yesterday 18 | today = shortlog --since=midnight 19 | uncommit = reset HEAD^ 20 | unamend = reset --soft HEAD@{1} 21 | hist = for-each-ref --count=20 --sort=-committerdate --format='%(refname:short)' refs/heads/ 22 | redate = commit --amend --date=now --no-edit 23 | [gist] 24 | private = yes 25 | extension = rb 26 | [init] 27 | templatedir = ~/.git_template 28 | defaultBranch = main 29 | [push] 30 | default = current 31 | [web] 32 | browser = open 33 | [merge] 34 | tool = vimdiff 35 | 36 | [mergetool "vimdiff"] 37 | cmd = vim -d "$LOCAL" "$MERGED" "$REMOTE" 38 | trustExitCode = true 39 | [branch] 40 | autosetuprebase = always 41 | [diff] 42 | compactionHeuristic = true 43 | noprefix = true 44 | [filter "lfs"] 45 | clean = git-lfs clean -- %f 46 | smudge = git-lfs smudge -- %f 47 | process = git-lfs filter-process 48 | required = true 49 | 50 | # This next lines include Netlify's Git Credential Helper configuration in your Git configuration. 51 | [include] 52 | path = /Users/henrik/.netlify/helper/git-config 53 | [pull] 54 | rebase = true 55 | [rebase] 56 | autoStash = true 57 | -------------------------------------------------------------------------------- /bash/prompt.sh: -------------------------------------------------------------------------------- 1 | # http://blog.bitfluent.com/post/27983389/git-utilities-you-cant-live-without 2 | # http://superuser.com/questions/31744/how-to-get-git-completion-bash-to-work-on-mac-os-x 3 | 4 | # 17:39:15 henrik@Nyx project_dir master*$ 5 | 6 | . ~/.dotfiles/bash/lib/git-prompt.sh 7 | 8 | function __git_prompt { 9 | GIT_PS1_SHOWDIRTYSTATE=1 10 | [ `git config user.pair` ] && GIT_PS1_PAIR="`git config user.pair`@" 11 | __git_ps1 " $GIT_PS1_PAIR%s" | sed 's/ \([+*]\{1,\}\)$/\1/' 12 | } 13 | 14 | # Only show username@server over SSH. 15 | function __name_and_server { 16 | if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ]; then 17 | echo "`whoami`@`hostname -s` " 18 | fi 19 | } 20 | 21 | bash_prompt() { 22 | 23 | # regular colors 24 | local K="\[\033[0;30m\]" # black 25 | local R="\[\033[0;31m\]" # red 26 | local G="\[\033[0;32m\]" # green 27 | local Y="\[\033[0;33m\]" # yellow 28 | local B="\[\033[0;34m\]" # blue 29 | local M="\[\033[0;35m\]" # magenta 30 | local C="\[\033[0;36m\]" # cyan 31 | local W="\[\033[0;37m\]" # white 32 | 33 | # emphasized (bolded) colors 34 | local BK="\[\033[1;30m\]" 35 | local BR="\[\033[1;31m\]" 36 | local BG="\[\033[1;32m\]" 37 | local BY="\[\033[1;33m\]" 38 | local BB="\[\033[1;34m\]" 39 | local BM="\[\033[1;35m\]" 40 | local BC="\[\033[1;36m\]" 41 | local BW="\[\033[1;37m\]" 42 | 43 | # reset 44 | local RESET="\[\033[0;37m\]" 45 | 46 | PS1="\t $BY\$(__name_and_server)$Y\W $G\$(__git_prompt)$RESET$ " 47 | 48 | } 49 | 50 | bash_prompt 51 | unset bash_prompt 52 | -------------------------------------------------------------------------------- /bashrc: -------------------------------------------------------------------------------- 1 | # .bashrc is loaded in non-login shells. 2 | # It's also loaded by .bash_profile (which is loaded in login shells). 3 | 4 | # Devbox (https://github.com/barsoom/devbox) provides a ~/.profile that we want both in login and non-login shells. 5 | [[ -s ~/.profile ]] && source ~/.profile 6 | 7 | # Expected by https://github.com/barsoom/devbox: https://auctionet.slack.com/archives/CF9SAN79V/p1552987988719300, https://auctionet.slack.com/archives/CF9SAN79V/p1553006327787000?thread_ts=1552987988.719300&cid=CF9SAN79V 8 | [[ -s "$HOME/.shell_local" ]] && source "$HOME/.shell_local" 9 | 10 | # Fuzzy file finder used by Vim: https://github.com/junegunn/fzf 11 | [ -f ~/.fzf.bash ] && source ~/.fzf.bash 12 | 13 | source ~/.bash/path.sh 14 | source ~/.bash/env.sh 15 | source ~/.bash/completion.sh 16 | 17 | source ~/.bash/aliases.sh 18 | source ~/.bash/functions.sh 19 | source ~/.bash/prompt.sh 20 | 21 | if [[ "$DEVBOX" ]]; then 22 | # Auctionet's "Devbox" tool is quite coupled to RVM currently. 23 | [[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm" 24 | else 25 | # For the "asdf" extendable version manager (https://github.com/asdf-vm/asdf). 26 | . $(brew --prefix asdf)/asdf.sh 27 | fi 28 | 29 | export WMSJAVA_HOME="/Library/WowzaStreamingEngine-4.7.0/java" 30 | 31 | # Stop macOS from saying zsh is the new default. 32 | export BASH_SILENCE_DEPRECATION_WARNING=1 33 | 34 | # nvm (Node Version Manager) 35 | export NVM_DIR="$HOME/.nvm" 36 | [ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh" # This loads nvm 37 | [ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm bash_completion 38 | -------------------------------------------------------------------------------- /bin/chrome_reload: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # watch.rb by Brett Terpstra, 2011 3 | # with credit to Carlo Zottmann 4 | 5 | def run 6 | trap("SIGINT") { exit } 7 | 8 | if ARGV.length < 2 9 | puts "Usage: #{$0} watch_folder keyword" 10 | puts "Example: #{$0} . mywebproject" 11 | exit 12 | end 13 | 14 | dev_extension = 'dev' 15 | filetypes = ['scss','slim','htm','php','rb','erb','less','js', 'coffee'] 16 | watch_folder = ARGV[0] 17 | keyword = ARGV[1] 18 | puts "Watching #{watch_folder} and subfolders for changes in project files..." 19 | 20 | while true do 21 | files = [] 22 | filetypes.each {|type| 23 | files += Dir.glob( File.join( watch_folder, "**", "*.#{type}" ) ) 24 | } 25 | new_hash = files.collect {|f| [ f, File.stat(f).mtime.to_i ] } 26 | hash ||= new_hash 27 | diff_hash = new_hash - hash 28 | 29 | unless diff_hash.empty? 30 | hash = new_hash 31 | 32 | diff_hash.each do |df| 33 | puts "Detected change in #{df[0]}, refreshing" 34 | %x{osascript</dev/null 2>&1 11 | then 12 | against=HEAD 13 | else 14 | # Initial commit: diff against an empty tree object 15 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 16 | fi 17 | 18 | # If you want to allow non-ascii filenames set this variable to true. 19 | allownonascii=$(git config hooks.allownonascii) 20 | 21 | # Cross platform projects tend to avoid non-ascii filenames; prevent 22 | # them from being added to the repository. We exploit the fact that the 23 | # printable range starts at the space character and ends with tilde. 24 | if [ "$allownonascii" != "true" ] && 25 | # Note that the use of brackets around a tr range is ok here, (it's 26 | # even required, for portability to Solaris 10's /usr/bin/tr), since 27 | # the square bracket bytes happen to fall in the designated range. 28 | test "$(git diff --cached --name-only --diff-filter=A -z $against | 29 | LC_ALL=C tr -d '[ -~]\0')" 30 | then 31 | echo "Error: Attempt to add a non-ascii file name." 32 | echo 33 | echo "This can cause problems if you want to work" 34 | echo "with people on other platforms." 35 | echo 36 | echo "To be portable it is advisable to rename the file ..." 37 | echo 38 | echo "If you know what you are doing you can disable this" 39 | echo "check using:" 40 | echo 41 | echo " git config hooks.allownonascii true" 42 | echo 43 | exit 1 44 | fi 45 | 46 | exec git diff-index --check --cached $against -- 47 | -------------------------------------------------------------------------------- /vim/plugin/octopress.vim: -------------------------------------------------------------------------------- 1 | " Octopress blog engine (http://octopress.org/) stuff for Vim. 2 | " Some commands assume you have Vimux: https://github.com/benmills/vimux/ 3 | " 4 | " By Henrik Nyh under the MIT license. 5 | 6 | " Override in ~/.vimrc if you like. 7 | " But you'll need to modify s:OPbrowse if the path format is different from mine. 8 | if !exists("g:octopress_url") 9 | let g:octopress_url = "http://octopress.dev" 10 | endif 11 | 12 | " :OPonly to generate only the current post. 13 | command! OPonly call VimuxRunCommand("rake generate_only[".expand("%:t:r")."]") 14 | nmap oo :OPonly 15 | 16 | " :OPgenerate to generate everything. 17 | command! OPgenerate call VimuxRunCommand("rake generate") 18 | nmap ogg :OPgenerate 19 | 20 | " :OPdeploy to deploy. 21 | command! OPdeploy call VimuxRunCommand("rake deploy") 22 | nmap od :OPdeploy 23 | 24 | " :OPgendep to generate everything and deploy. 25 | command! OPgendep call VimuxRunCommand("rake gen_deploy") 26 | nmap ogd :OPgendep 27 | 28 | 29 | " :OPwatch to regenerate changed CSS. 30 | command! OPwatch call VimuxRunCommand("compass watch") 31 | nmap ow :OPwatch 32 | 33 | " If you need to ctrl+c from OPwatch. 34 | nmap oc :VimuxInterruptRunner 35 | 36 | " :OPbrowse to open in a browser. 37 | " Opens the current post if you're editing one, otherwise the root. 38 | " Uses s:base_url, defined above. 39 | command! OPbrowse call OPbrowse() 40 | nmap ob :OPbrowse 41 | 42 | 43 | function! s:OPbrowse() 44 | let l:url = g:octopress_url."/" 45 | if match(expand("%"), "source/_posts/") == 0 46 | let l:url .= substitute(expand("%:t:r"), "\\v(\\d{4})-(\\d{2})-\\d{2}-(.+)", "\\1/\\2/\\3/", "") 47 | endif 48 | 49 | exe "silent! ! open '".l:url."'" 50 | redraw! 51 | endfunction 52 | -------------------------------------------------------------------------------- /tmux.conf: -------------------------------------------------------------------------------- 1 | set-option -g default-command "tmux-shell-wrapper" 2 | 3 | # Make shift+arrows, ctrl+arrows etc work in Vim. 4 | set -g xterm-keys on 5 | 6 | # See if this fixes slow ESC issues. 7 | # http://unix.stackexchange.com/questions/23138/esc-key-causes-a-small-delay-in-terminal-due-to-its-alt-behavior 8 | set -s escape-time 0 9 | 10 | # Start window and pane indices at 1. 11 | set -g base-index 1 12 | set -g pane-base-index 1 13 | 14 | # Status bar styling and content. 15 | set -g status-bg black 16 | set -g status-fg white 17 | set -g status-left '#S ' 18 | 19 | # Show keyboard layout in prompt, assuming some script writes it to that path; 20 | # it's "/User/…" instead of "~" so VMs can read from their symlinked OS X host home directory. 21 | set -g status-right '#(cat /Users/$USER/.keyboard_layout 2> /dev/null) | #22T | %H:%M %d-%b-%y' 22 | set -g status-right-length 60 23 | set -g status-interval 2 24 | 25 | # Highlight the active window in the status bar. 26 | set-window-option -g window-status-current-style bg=yellow,fg=black 27 | 28 | # Don't prompt to kill panes/windows. 29 | bind-key x kill-pane 30 | bind-key & kill-window 31 | 32 | # Cycle panes. 33 | bind b select-pane -t :.+ 34 | bind C-b select-pane -t :.+ 35 | 36 | # More intuitive split-window mappings. 37 | bind "'" split-window -h 38 | bind - split-window -v 39 | 40 | # Maximize pane, e.g. for copying. 41 | bind-key z resize-pane -Z 42 | 43 | # Switch pane and zoom 44 | # https://twitter.com/tskogberg/status/792025881573199872 45 | bind C-z select-pane -t :.+ \; resize-pane -Z 46 | 47 | # Reload tmux conf. 48 | unbind r 49 | bind r source-file ~/.tmux.conf\; display "Reloaded conf." 50 | 51 | # http://robots.thoughtbot.com/tmux-copy-paste-on-os-x-a-better-future 52 | 53 | # Use vim keybindings in copy mode 54 | setw -g mode-keys vi 55 | 56 | # Unbork my iTerm ctrl+1 etc mappings in tmux 2.1 57 | # https://github.com/tmux/tmux/issues/159 58 | set -g assume-paste-time 0 59 | 60 | set -g history-limit 9999 61 | -------------------------------------------------------------------------------- /bin/note_to_self.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Command-line tool to mail notes to self with Gmail. 4 | # 5 | # Configure: 6 | 7 | TO = "henrik@nyh.se" 8 | LOGIN = "henrik@nyh.se" 9 | 10 | # This needs to be a "Generated app password" to avoid the need for 2FA. 11 | PW = File.read(File.expand_path("~/.gmailpw")).chop 12 | 13 | # Intended to be used with an Alfred.app extension like: 14 | # 15 | # ESCAPING: [ ] Spaces [x] Backquotes [x] Double quotes [x] Backslashes 16 | # [ ] Brackets [ ] Semicolons [x] Dollars 17 | # 18 | # source ~/.rvm/scripts/rvm # If you use RVM 19 | # source ~/.asdf/asdf.sh # If you use asdf installed in .asdf 20 | # source $(/opt/homebrew/bin/brew --prefix asdf)/asdf.sh # If you use asdf installed via newer Homebrew 21 | # source $(/usr/local/bin/brew --prefix asdf)/asdf.sh # If you use asdf installed via older Homebrew 22 | # ~/.bin/note_to_self.rb "{query}" 23 | 24 | require "net/imap" 25 | require "net/smtp" 26 | 27 | require "bundler/inline" 28 | gemfile do 29 | source "https://rubygems.org" 30 | gem "gmail" 31 | end 32 | 33 | def init 34 | message = ARGV.first 35 | 36 | # It's UTF-8, not ASCII-8BIT. Without this, it will look borked. 37 | message = message.dup 38 | message.force_encoding("UTF-8") 39 | 40 | # Alfred passes decomposed characters which cause the mail to silently fail. 41 | message = message.unicode_normalize 42 | 43 | note_to_self(message) 44 | end 45 | 46 | def note_to_self(message) 47 | log(message) # So we have it even if it errors out. 48 | subject = "[note to self] #{message}" 49 | send_mail(LOGIN, PW, TO, subject, message) 50 | puts "Sent: #{message}" 51 | rescue StandardError => e 52 | puts "Error sending: #{e.class.name}: #{e.message}" 53 | end 54 | 55 | def log(message) 56 | File.open("/tmp/notes_to_self.txt", "a+") { |f| f.puts message } 57 | end 58 | 59 | def send_mail(login, pw, _to, _subject, _body) 60 | Gmail.connect!(login, pw) do |gmail| 61 | gmail.deliver do 62 | to _to 63 | subject _subject 64 | text_part do 65 | content_type "text/plain; charset=UTF-8" 66 | body _body 67 | end 68 | end 69 | end 70 | end 71 | 72 | init 73 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # My dotfiles 2 | 3 | ## Install 4 | 5 | On a Mac, switch the shell to bash: 6 | 7 | chsh -s /bin/bash 8 | 9 | Clone this repo into `~/.dotfiles`: 10 | 11 | git clone git@github.com:henrik/dotfiles.git ~/.dotfiles 12 | 13 | Then install the dotfiles: 14 | 15 | cd .dotfiles 16 | rake 17 | 18 | This rake task will not replace existing files, but it will replace existing symlinks. 19 | 20 | The dotfiles will be symlinked, e.g. `~/.bash_profile` symlinked to `~/.dotfiles/bash_profile`. 21 | 22 | To use [fzf](https://github.com/junegunn/fzf) in Vim (or the shell), install it with: 23 | 24 | git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf 25 | ~/.fzf/install 26 | 27 | I intentionally do not install fzf via Vim, because I couldn't get it working well when sharing dotfiles beteeen a macOS host and a Linux guest. 28 | 29 | You are expected to have Node installed with a new-enough version to make [coc.nvim](https://github.com/neoclide/coc.nvim) happy. 30 | 31 | You are expected to have the `solargraph` gem installed to use coc.nvim with Ruby. If you use [asdf](https://asdf-vm.com/), you should get this automatically when you install a new Ruby version via [.default-gems](https://github.com/asdf-vm/asdf-ruby#default-gems). Otherwise use `gem install solargraph`. 32 | 33 | In Vim, run this to install plugins: 34 | 35 | :PlugInstall 36 | 37 | Don't forget to **replace the name and email in gitconfig** if you're not Henrik :p 38 | 39 | 40 | ## Vim 41 | 42 | Uses [vim-plug](https://github.com/junegunn/vim-plug) for plugins. 43 | 44 | To add plugins: 45 | 46 | * Edit `~/.vim/config/plugins.vim` 47 | * `:so %` 48 | * `:PlugInstall` 49 | 50 | To remove plugins: 51 | 52 | * Edit `~/.vim/config/plugins.vim` 53 | * `:so %` 54 | * `:PlugClean` 55 | 56 | 57 | ## tmux 58 | 59 | Make it integrate with the OS X system clipboard: 60 | 61 | brew install reattach-to-user-namespace 62 | 63 | 64 | ## Extras 65 | 66 | The `extras` directory contains additional configuration files that are not dotfiles: 67 | 68 | * `VibrantInk.itermcolors` is a colorscheme for [iTerm2](http://www.iterm2.com/) ([source](https://github.com/asanghi/vibrantinklion)). 69 | 70 | * On a new Mac: 71 | * Run `brew bundle --file ~/.dotfiles/extras/Brewfile` to install the apps I want. 72 | * Run `~/.dotfiles/extras/mac_os_defaults.sh` in the Terminal to change some silly defaults. 73 | -------------------------------------------------------------------------------- /bin/imgcat: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # tmux requires unrecognized OSC sequences to be wrapped with DCS tmux; 4 | # ST, and for all ESCs in to be replaced with ESC ESC. It 5 | # only accepts ESC backslash for ST. 6 | function print_osc() { 7 | if [[ $TERM == screen* ]] ; then 8 | printf "\033Ptmux;\033\033]" 9 | else 10 | printf "\033]" 11 | fi 12 | } 13 | 14 | # More of the tmux workaround described above. 15 | function print_st() { 16 | if [[ $TERM == screen* ]] ; then 17 | printf "\a\033\\" 18 | else 19 | printf "\a" 20 | fi 21 | } 22 | 23 | # print_image filename inline base64contents 24 | # filename: Filename to convey to client 25 | # inline: 0 or 1 26 | # base64contents: Base64-encoded contents 27 | function print_image() { 28 | print_osc 29 | printf '1337;File=' 30 | if [[ -n "$1" ]]; then 31 | printf 'name='`echo -n "$1" | base64`";" 32 | fi 33 | if $(base64 --version 2>&1 | egrep 'fourmilab|GNU' > /dev/null) 34 | then 35 | BASE64ARG=-d 36 | else 37 | BASE64ARG=-D 38 | fi 39 | echo -n "$3" | base64 $BASE64ARG | wc -c | awk '{printf "size=%d",$1}' 40 | printf ";inline=$2" 41 | printf ":" 42 | echo -n "$3" 43 | print_st 44 | printf '\n' 45 | } 46 | 47 | function error() { 48 | echo "ERROR: $*" 1>&2 49 | } 50 | 51 | function show_help() { 52 | echo "Usage: imgcat filename ..." 1>& 2 53 | echo " or: cat filename | imgcat" 1>& 2 54 | } 55 | 56 | ## Main 57 | 58 | if [ -t 0 ]; then 59 | has_stdin=f 60 | else 61 | has_stdin=t 62 | fi 63 | 64 | # Show help if no arguments and no stdin. 65 | if [ $has_stdin = f -a $# -eq 0 ]; then 66 | show_help 67 | exit 68 | fi 69 | 70 | # Look for command line flags. 71 | while [ $# -gt 0 ]; do 72 | case "$1" in 73 | -h|--h|--help) 74 | show_help 75 | exit 76 | ;; 77 | -*) 78 | error "Unknown option flag: $1" 79 | show_help 80 | exit 1 81 | ;; 82 | *) 83 | if [ -r "$1" ] ; then 84 | print_image "$1" 1 "$(base64 < "$1")" 85 | else 86 | error "imgcat: $1: No such file or directory" 87 | exit 2 88 | fi 89 | ;; 90 | esac 91 | shift 92 | done 93 | 94 | # Read and print stdin 95 | if [ $has_stdin = t ]; then 96 | print_image "" 1 "$(cat | base64)" 97 | fi 98 | 99 | exit 0 100 | -------------------------------------------------------------------------------- /git_template/hooks/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # Git commit-msg hook. If your branch name is in the form "t123", automatically 4 | # adds "Refs #123." to commit messages unless they mention "#123" already. 5 | # Include "#close" or "#finish" to add "Closes #123." 6 | # 7 | # For Pivotal Tracker, branch names like "s123" adds "[#123]". 8 | # Include "#close" or "#finish" to add "[Finishes #123]". 9 | # 10 | # If you include "#noref" in the commit message, nothing will be added to 11 | # the commit message, and the "#noref" itself will be stripped. 12 | # 13 | # By Henrik Nyh 2009-09-10 under the MIT License. 14 | # 15 | # 16 | # Install: 17 | # 18 | # cd your_project 19 | # curl https://raw.github.com/henrik/dotfiles/master/git_template/hooks/commit-msg -o .git/hooks/commit-msg && chmod u+x .git/hooks/commit-msg 20 | # 21 | # Or store it centrally and symlink in your projects: 22 | # 23 | # curl --create-dirs -https://raw.github.com/henrik/dotfiles/master/git_template/hooks/commit-msg -o ~/.githooks/commit-msg && chmod u+x ~/.githooks/commit-msg 24 | # cd your_project 25 | # ln -s ~/.githooks/commit-msg .git/hooks 26 | # 27 | # Or set up a template for all your Git repos: 28 | # http://www.kernel.org/pub/software/scm/git/docs/git-init.html#_template_directory 29 | 30 | PREFIXES = { 31 | # RedMine, Trac etc. 32 | "t" => { :reference => "Refs #%s.", :finish => "Closes #%s." }, 33 | # Pivotal Tracker. 34 | "s" => { :reference => "[#%s]", :finish => "[Finishes #%s]" }, 35 | } 36 | 37 | FLAGS = [ 38 | NOREF = "noref", 39 | CLOSE = "close", 40 | FINISH = "finish" 41 | ] 42 | 43 | CLOSING_FLAGS = [ CLOSE, FINISH ] 44 | 45 | branchname = `git branch --no-color 2> /dev/null`[/^\* (.+)/, 1].to_s 46 | 47 | if m = branchname.match(/\A(#{Regexp.union(*PREFIXES.keys)})-?(\d+)/) 48 | prefix, ticket_number = m.captures 49 | finish = PREFIXES[prefix][:finish] % ticket_number 50 | reference = PREFIXES[prefix][:reference] % ticket_number 51 | else 52 | exit 53 | end 54 | 55 | message_file = ARGV[0] 56 | message = File.read(message_file).strip 57 | exit if message.include?("##{ticket_number}") 58 | 59 | # Determine if any of the flags are included. Make a note of which and then remove it. 60 | 61 | message.sub!(/(?:^|\s)#(#{Regexp.union(*FLAGS)})\b/, '') 62 | flag = $1 63 | 64 | message = 65 | case flag 66 | when NOREF 67 | message 68 | when *CLOSING_FLAGS 69 | [ message, finish ].join(" ") 70 | else 71 | [ message, reference ].join(" ") 72 | end 73 | 74 | File.open(message_file, 'w') { |f| f.write message } 75 | -------------------------------------------------------------------------------- /bash/functions.sh: -------------------------------------------------------------------------------- 1 | # OS X only: 2 | # "o file.txt" = open file in default app. 3 | # "o http://example.com" = open URL in default browser. 4 | # "o" = open pwd in Finder. 5 | 6 | function o { 7 | open ${@:-'.'} 8 | } 9 | 10 | 11 | # "git commit only" 12 | # Commits only what's in the index (what's been "git add"ed). 13 | # When given an argument, uses that for a message. 14 | # With no argument, opens an editor that also shows the diff (-v). 15 | 16 | function gco { 17 | if [ -z "$1" ]; then 18 | git commit -v 19 | else 20 | git commit -m "$1" 21 | fi 22 | } 23 | 24 | 25 | # "git commit all" 26 | # Commits all changes, deletions and additions. 27 | # When given an argument, uses that for a message. 28 | # With no argument, opens an editor that also shows the diff (-v). 29 | 30 | function gca { 31 | git add --all && gco "$1" 32 | } 33 | 34 | 35 | # "git get" 36 | # Clones the given repo and then cd:s into that directory. 37 | function gget { 38 | git clone $1 && cd $(basename $1 .git) 39 | } 40 | 41 | 42 | # Print working file. 43 | # 44 | # henrik@Henrik ~/.dotfiles[master]$ pwf ackrc 45 | # /Users/henrik/.dotfiles/ackrc 46 | 47 | function pwf { 48 | echo "$PWD/$1" 49 | } 50 | 51 | 52 | # Create directory and cd to it. 53 | # 54 | # henrik@Nyx /tmp$ mcd foo/bar/baz 55 | # henrik@Nyx /tmp/foo/bar/baz$ 56 | 57 | function mcd { 58 | mkdir -p "$1" && cd "$1" 59 | } 60 | 61 | 62 | # SSH to the given machine and add your id_rsa.pub or id_dsa.pub to authorized_keys. 63 | # 64 | # henrik@Nyx ~$ sshkey hyper 65 | # Password: 66 | # sshkey done. 67 | 68 | function sshkey { 69 | ssh $1 "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys" < ~/.ssh/id_?sa.pub # '?sa' is a glob, not a typo! 70 | echo "sshkey done." 71 | } 72 | 73 | 74 | # Attach or create a tmux session. 75 | # 76 | # You can provide a name as the first argument, otherwise it defaults to the current directory name. 77 | # The argument tab completes among existing tmux session names. 78 | # 79 | # Example usage: 80 | # 81 | # tat some-project 82 | # 83 | # tat s 84 | # 85 | # cd some-project 86 | # tat 87 | # 88 | # Based on https://github.com/thoughtbot/dotfiles/blob/master/bin/tat 89 | # and http://krauspe.eu/r/tmux/comments/25mnr7/how_to_switch_sessions_faster_preferably_with/ 90 | 91 | function tat() { 92 | session_name=`basename ${1:-$PWD}` 93 | session_name=${session_name/auctionet_/an_} 94 | session_name=${session_name//\./_} 95 | tmux new-session -As "$session_name" 96 | } 97 | 98 | function _tmux_complete_session() { 99 | local IFS=$'\n' 100 | local cur=${COMP_WORDS[COMP_CWORD]} 101 | COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$(tmux -q list-sessions | cut -f 1 -d ':')" -- "${cur}") ) 102 | } 103 | complete -F _tmux_complete_session tat 104 | -------------------------------------------------------------------------------- /vim/config/mappings.vim: -------------------------------------------------------------------------------- 1 | " Hit S in command mode to save, as :w is a mouthful and MacVim 2 | " Command-S is a bad habit when using terminal Vim. 3 | " We overload a command, but use 'cc' for that anyway. 4 | noremap S :w 5 | 6 | " Make Q useful and avoid the confusing Ex mode. 7 | noremap Q 8 | " Close window. 9 | noremap QQ :q 10 | " Close a full tab page. 11 | noremap QW :windo bd 12 | " Close all. 13 | noremap QA :qa 14 | 15 | " Repeat last substitution, including flags, with &. 16 | nnoremap & :&& 17 | xnoremap & :&& 18 | 19 | " In command-line mode, C-a jumps to beginning (to match C-e). 20 | cnoremap 21 | 22 | " Select the text that was last edited/pasted. 23 | " http://vimcasts.org/episodes/bubbling-text/ 24 | nmap gV `[v`] 25 | 26 | " Move by screen lines instead of file lines. 27 | " http://vim.wikia.com/wiki/Moving_by_screen_lines_instead_of_file_lines 28 | noremap gk 29 | noremap gj 30 | noremap k gk 31 | noremap j gj 32 | inoremap gj 33 | inoremap gk 34 | 35 | " Shift + left/right to switch tabs. 36 | " You may need to map these in iTerm2 prefs to escape 37 | " sequences [1;2C (right) and D (left). 38 | noremap :tabp 39 | noremap :tabn 40 | 41 | " Un-highlight search matches 42 | nnoremap :noh 43 | 44 | " Open URL from this line (OS X only). 45 | map u :call OpenURI() 46 | 47 | " Ack/Quickfix windows 48 | map q :cclose 49 | " Center line on previous/next fix. 50 | map - :cprev zz 51 | map + :cnext zz 52 | " Center line in previous/next file. 53 | map g- :cpfile zz 54 | map g+ :cnfile zz 55 | 56 | " Create a split on the given side. 57 | " From http://technotales.wordpress.com/2010/04/29/vim-splits-a-guide-to-doing-exactly-what-you-want/ via joakimk. 58 | nmap :leftabove vsp 59 | nmap :rightbelow vsp 60 | nmap :leftabove sp 61 | nmap :rightbelow sp 62 | 63 | " Tab/shift-tab to indent/outdent in visual mode. 64 | vnoremap >gv 65 | vnoremap >gv 69 | vnoremap < 73 | vnoremap gu u 74 | 75 | " Used to have this mapped to ":w" because I often accidentally :W when I mean to :w. 76 | " Disabled to see if I can learn to do better. 77 | " It remains as a no-op since I will otherwise trigger fzf.vim's ":Windows" " all the time. 78 | command! W echoerr "Use :w, not :W." 79 | 80 | " In command mode, type %% to insert the path of the currently edited file, as a shortcut for %:h. 81 | cmap %% =expand("%:h") . "/" 82 | 83 | " Run tests in tmux. 84 | nmap § :call VimuxRunCommand("rake") 85 | autocmd BufNewFile,BufRead /Users/henrik/Dropbox/Code/exercism/* nmap § :ExercismTest 86 | 87 | " Tell other tmux pane to ctrl+c. 88 | nmap § :VimuxInterruptRunner 89 | -------------------------------------------------------------------------------- /vim/plugin/saveas_improved.vim: -------------------------------------------------------------------------------- 1 | " By Henrik Nyh under the MIT license. 2 | " 3 | " :SaveAs works mostly like the built-in :saveas, but improves on some things. 4 | " 5 | " (You can actually keep typing ':saveas'; when you hit the spacebar, it will 6 | " be transformed into a ':SaveAs '.) 7 | " 8 | " # BANG AFTER FILENAME 9 | " 10 | " ':SaveAs foo/bar.txt' without any exclamation marks works like ':saveas'. 11 | " If the 'foo' directory doesn't exist, it will complain. 12 | " If a 'foo/bar.txt' file already exists, it will complain. 13 | " 14 | " ':SaveAs foo/bar.txt!' with a trailing bang creates directories if they don't 15 | " exist, and overwrites any existing file. 16 | " 17 | " The bang goes AFTER the filename so you can add it with a simple ':!'. 18 | " 19 | " # SPLIT-BUFFER FRIENDLY 20 | " 21 | " I often create new files based on old files by splitting a buffer into two 22 | " windows, then doing ':saveas new_name' in one of them. With regular 23 | " ':saveas', both windows will then show the new file. With ':SaveAs', 24 | " it only updates one of the windows. 25 | 26 | command! -nargs=1 -complete=file SaveAs :call SaveAs() 27 | 28 | function! s:SaveAs(filename) 29 | if a:filename =~ "!$" 30 | let l:filename = substitute(a:filename, "!$", "", "") 31 | let l:bang = 1 32 | else 33 | let l:filename = a:filename 34 | let l:bang = 0 35 | endif 36 | 37 | let l:dir = fnamemodify(l:filename, ":p:h") 38 | if !isdirectory(l:dir) 39 | if l:bang 40 | call mkdir(l:dir, "p") 41 | else 42 | call EchoError("The directory does not exist (use trailing ! to create).") 43 | return 0 44 | endif 45 | end 46 | 47 | if !l:bang && filereadable(l:filename) 48 | call EchoError("File exists (use trailing ! to override).") 49 | return 0 50 | end 51 | 52 | " Instead of :saveas, we do :write and :edit. 53 | " If you split a buffer and :saveas one of the two, both windows change. 54 | " If you :write and :edit, only the window you are in will change. 55 | 56 | " If there's any error with :write, don't :edit. 57 | try 58 | if l:bang 59 | " 'silent!' so we can write even if there's a swapfile. 60 | " http://vimdoc.sourceforge.net/htmldoc/message.html#E768 61 | exe "silent! write! " . l:filename 62 | else 63 | exe "write " . l:filename 64 | endif 65 | catch 66 | call EchoError(v:exception) 67 | return 0 68 | endtry 69 | 70 | exe "edit " . l:filename 71 | endfunction 72 | 73 | function! s:EchoError(message) 74 | echohl ErrorMsg 75 | echomsg a:message 76 | echohl None 77 | endfunction 78 | 79 | 80 | " http://vim.wikia.com/wiki/Replace_a_builtin_command_using_cabbrev 81 | 82 | function! s:CommandCabbr(abbreviation, expansion) 83 | execute 'cabbr ' . a:abbreviation . ' =getcmdpos() == 1 && getcmdtype() == ":" ? "' . a:expansion . '" : "' . a:abbreviation . '"' 84 | endfunction 85 | 86 | command! -nargs=+ CommandCabbr call CommandCabbr() 87 | 88 | " :saveas calls :SaveAs 89 | CommandCabbr saveas SaveAs 90 | -------------------------------------------------------------------------------- /vim/config/settings.vim: -------------------------------------------------------------------------------- 1 | set encoding=utf-8 " In case $LANG doesn't have a sensible value. 2 | 3 | syntax on " Syntax highlighting. 4 | set background=dark " Inquiring plugins want to know. 5 | color blackboard " Default color scheme. 6 | 7 | set backspace=indent,eol,start " Allow backspacing over everything in insert mode. 8 | set fillchars=vert:\ " No pipes in vertical split separators. 9 | set laststatus=2 " Always show status bar. 10 | set listchars=nbsp:·,tab:▸\ ,trail:· " Configure how invisibles appear. 11 | set list " Show invisibles. 12 | set nomodeline " Modelines have had serious security issues. If you turn this on, make sure you know what you're doing. 13 | set nojoinspaces " 1 space, not 2, when joining sentences. 14 | set number " Show gutter with line numbers. 15 | set scrolloff=1 " Minimum number of lines to always show above/below the caret. 16 | set showcmd " Show partially typed command sequences. 17 | set visualbell " Don't beep. 18 | set wildmode=longest,list " Autocompleting files: prompt, don't autopick. 19 | set wrap " Soft wrap. 20 | 21 | " Indentation. 22 | set expandtab " Replace tabs with spaces. 23 | set shiftwidth=2 " Spaces used for autoindent and commands like >>. 24 | set softtabstop=2 " Spaces inserted by . 25 | 26 | " Searching. 27 | set gdefault " Global search by default (/g turns it off). 28 | set hlsearch " Highlight results. 29 | set incsearch " Search-as-you-type. 30 | set ignorecase smartcase " Case-insensitive unless we include uppercase. 31 | set shortmess-=S " Show result counts like '[1/4]'. Vim 8.1.1270+. 32 | 33 | " Can autocomplete CSS classes etc with dashes. 34 | " Also changes the "w" small word motion not to stop at dashes, 35 | " but use https://github.com/henrik/CamelCaseMotion for that. 36 | set iskeyword+=- 37 | 38 | " More persistent command history. 39 | set history=10000 40 | 41 | " Hide away backup and swap files. 42 | set backupdir=~/.vim/backup 43 | set directory=~/.vim/backup 44 | 45 | " Reload file without prompting if it has changed on disk. 46 | " Will still prompt if there is unsaved text in the buffer. 47 | set autoread 48 | " MacVim checks for autoread when it gains focus; terminal Vim 49 | " must trigger checks. Do so when switching buffers, or after 50 | " 2 secs (the value of updatetime) of pressing nothing. 51 | set updatetime=2000 52 | au WinEnter,BufWinEnter,CursorHold * silent! checktime 53 | 54 | " Automatically save changes before switching buffer with some 55 | " commands, like :cnfile. 56 | set autowrite 57 | 58 | " Autosave. 59 | " http://vim.wikia.com/wiki/Auto_save_files_when_focus_is_lost 60 | au BufLeave,FocusLost * silent! wa 61 | 62 | " Treat Nunjucks as HTML. (Templates used with e.g. the Eleventy blog engine.) 63 | au BufRead,BufNewFile *.njk setfiletype html 64 | 65 | set exrc " Enable per-directory .vimrc files. 66 | set secure " Disable unsafe commands in per-directory .vimrc files. 67 | -------------------------------------------------------------------------------- /vim/plugin/squirrel.vim: -------------------------------------------------------------------------------- 1 | " A :Squirrel command to quickly make note of things you want to fix, but you 2 | " should probably finish up the current task first. 3 | " 4 | " As in "squirrel it away" or avoiding a "Squirrel!" distraction to maintain focus. 5 | " 6 | " Notes are stored in a .squirrel file in Vim's current directory, with a 7 | " timestamp and a link to the file and line. 8 | " 9 | " By the easily distracted Henrik Nyh under the MIT license. 10 | " 11 | " # Adding to the list 12 | " 13 | " :Squirrel Rename this to Bar 14 | " 15 | " This will store a reference to the current file and line, with the comment you 16 | " provided and the current timestamp. 17 | " 18 | " To squirrel something away without a comment, do 19 | " 20 | " :Squirrel! 21 | " 22 | " # Opening the list 23 | " 24 | " All squirreled-away data is stored in a .squirrel file in Vim's current directory. 25 | " You may want to ignore that file in your VCS, e.g. in .gitignore. 26 | " 27 | " Run 28 | " 29 | " :Squirrel 30 | " 31 | " to open the .squirrel file in a new tab. 32 | " 33 | " It also sets things up so you can use the "gf" command on a "file/path:123" 34 | " line in that buffer to jump straight to that file and line. (Instead of 35 | " having to type "gF".) 36 | " 37 | " Keep in mind that lines may have shifted since. 38 | " 39 | " If you want to remove or change something, just edit the .squirrel file. 40 | " You can remove an entire paragraph with "dap" (delete all paragraph). 41 | " 42 | " Unless you have other commands that start with "Sq" you can probably get 43 | " away with abbreviating them: 44 | " 45 | " :Sq Rename this to Bar 46 | " :Sq! 47 | " :Sq 48 | " 49 | " 50 | " # TODO 51 | " 52 | " - Also store any selected text. README copy for that: 53 | " 54 | " If you've got text highlighted when you run the command, that text will be 55 | " squirreled away as well. This means you can write your comment in the buffer 56 | " instead, or you can make a quick sketch of the change you want. 57 | " 58 | " - Some way to get it in a quickfix list? 59 | " - Don't have an extra trailing newline at end of .squirrel file 60 | 61 | command! -nargs=? -bang Squirrel call Squirrel(0, "") 62 | 63 | function! s:Squirrel(bang, comment) 64 | if a:comment != "" || a:bang 65 | call WriteSquirrel(a:comment) 66 | else 67 | call ViewSquirrel() 68 | endif 69 | endfunction 70 | 71 | function! s:WriteSquirrel(comment) 72 | let file_exists = filereadable(".squirrel") 73 | 74 | if a:comment == "" 75 | let comment = "(no comment)" 76 | else 77 | let comment = a:comment 78 | end 79 | 80 | let time = strftime("%F %H:%M:%S") 81 | let path_and_line = @% . ":" . line(".") 82 | 83 | let prev_lines = file_exists ? readfile(".squirrel") : [] 84 | call writefile([time, path_and_line, comment, ""] + prev_lines, ".squirrel") 85 | 86 | echo "Squirreled away: " . comment 87 | endfunction 88 | 89 | function! s:ViewSquirrel() 90 | let file_exists = filereadable(".squirrel") 91 | 92 | if file_exists 93 | tabe .squirrel 94 | 95 | " Make regular gf also follow line numbers, as in example.txt:123 96 | noremap gf gF 97 | else 98 | echo "No .squirrel file yet! Better start storing up for winter!" 99 | end 100 | endfunction 101 | -------------------------------------------------------------------------------- /extras/mac_os_defaults.sh: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/mathiasbynens/dotfiles/blob/master/.osx. 2 | 3 | echo "Enable full keyboard access for all controls (e.g. enable Tab in modal dialogs)." 4 | defaults write NSGlobalDomain AppleKeyboardUIMode -int 3 5 | 6 | echo "Disable menu bar transparency." 7 | defaults write NSGlobalDomain AppleEnableMenuBarTransparency -bool false 8 | 9 | echo "Expand save panel by default." 10 | defaults write NSGlobalDomain NSNavPanelExpandedStateForSaveMode -bool true 11 | 12 | echo "Disable the 'Are you sure you want to open this application?' dialog." 13 | defaults write com.apple.LaunchServices LSQuarantine -bool false 14 | 15 | echo "Disable window animations." 16 | defaults write NSGlobalDomain NSAutomaticWindowAnimationsEnabled -bool false 17 | 18 | # http://bengold.tv/post/21711266050 19 | echo "Speed up dock show/hide animation." 20 | defaults write com.apple.dock autohide-time-modifier -float 0.5 21 | killall Dock 22 | 23 | # http://osxdaily.com/2011/01/26/change-the-screenshot-save-file-location-in-mac-os-x/ 24 | echo "Change screenshot location." 25 | defaults write com.apple.screencapture location ~/Downloads 26 | 27 | # TODO: broken on macOS Sierra: https://github.com/mathiasbynens/dotfiles/issues/687 Set it manually in UI for now. 28 | echo "Set a blazingly fast keyboard repeat rate." 29 | defaults write NSGlobalDomain KeyRepeat -int 1 30 | 31 | # TODO: broken on macOS Sierra: https://github.com/mathiasbynens/dotfiles/issues/687 Set it manually in UI for now. 32 | echo "Set a shorter Delay until key repeat." 33 | defaults write NSGlobalDomain InitialKeyRepeat -int 10 34 | 35 | echo "Increase window resize speed for Cocoa applications." 36 | defaults write NSGlobalDomain NSWindowResizeTime -float 0.001 37 | 38 | echo "Show item info below desktop icons." 39 | /usr/libexec/PlistBuddy -c "Set :DesktopViewSettings:IconViewSettings:showItemInfo true" ~/Library/Preferences/com.apple.finder.plist 40 | 41 | echo "Enable snap-to-grid for desktop icons." 42 | /usr/libexec/PlistBuddy -c "Set :DesktopViewSettings:IconViewSettings:arrangeBy grid" ~/Library/Preferences/com.apple.finder.plist 43 | 44 | echo "Disable disk image verification." 45 | defaults write com.apple.frameworks.diskimages skip-verify -bool true 46 | defaults write com.apple.frameworks.diskimages skip-verify-locked -bool true 47 | defaults write com.apple.frameworks.diskimages skip-verify-remote -bool true 48 | 49 | echo "Automatically open a new Finder window when a volume is mounted." 50 | defaults write com.apple.frameworks.diskimages auto-open-ro-root -bool true 51 | defaults write com.apple.frameworks.diskimages auto-open-rw-root -bool true 52 | 53 | echo "Display full POSIX path as Finder window title." 54 | defaults write com.apple.finder _FXShowPosixPathInTitle -bool true 55 | 56 | echo "Show Path bar in Finder." 57 | defaults write com.apple.finder ShowPathbar -bool true 58 | 59 | echo "Show Status bar in Finder." 60 | defaults write com.apple.finder ShowStatusBar -bool true 61 | 62 | echo "Use current directory as default search scope in Finder." 63 | defaults write com.apple.finder FXDefaultSearchScope -string "SCcf" 64 | 65 | echo "Show the ~/Library folder." 66 | chflags nohidden ~/Library 67 | 68 | echo "Disable smart quotes as they’re annoying when typing code." 69 | defaults write NSGlobalDomain NSAutomaticQuoteSubstitutionEnabled -bool false 70 | 71 | echo "Disable smart dashes as they’re annoying when typing code." 72 | defaults write NSGlobalDomain NSAutomaticDashSubstitutionEnabled -bool false 73 | 74 | echo "Changed defaults. Restarting apps…" 75 | 76 | echo "Restart Finder." 77 | killall Finder 78 | 79 | echo "Please lock your account and log back in to make the keyboard repeat settings take on macOS Sierra." 80 | 81 | echo "All done." 82 | -------------------------------------------------------------------------------- /vim/colors/blackboard.vim: -------------------------------------------------------------------------------- 1 | " Vim color file 2 | " This file was generated by Palette 3 | " http://rubygems.org/gems/palette 4 | " 5 | " Author: Drew Neil 6 | " Notes: Based on the 'Blackboard' theme from TextMate. 7 | 8 | hi clear 9 | if version > 580 10 | if exists("syntax_on") 11 | syntax reset 12 | endif 13 | endif 14 | 15 | let colors_name="blackboard" 16 | 17 | if has("gui_running") 18 | set background=dark 19 | endif 20 | 21 | hi Normal guifg=#FFFFFF ctermfg=231 guibg=#0B1022 ctermbg=233 gui=NONE cterm=NONE 22 | hi Cursor guifg=#000000 ctermfg=16 guibg=#FFFF00 ctermbg=226 gui=NONE cterm=NONE 23 | hi CursorLine guibg=#191E2F ctermbg=234 gui=NONE cterm=NONE 24 | hi LineNr guifg=#888888 ctermfg=102 guibg=#323232 ctermbg=236 gui=NONE cterm=NONE 25 | hi Folded guifg=#1D2652 ctermfg=235 guibg=#070A15 ctermbg=232 gui=NONE cterm=NONE 26 | hi Pmenu guibg=#84A7C1 ctermbg=109 gui=NONE cterm=NONE 27 | hi Visual guibg=#283A76 ctermbg=237 gui=NONE cterm=NONE 28 | hi Comment guifg=#AEAEAE ctermfg=145 gui=NONE cterm=NONE 29 | hi Constant guifg=#D8FA3C ctermfg=191 gui=NONE cterm=NONE 30 | hi Keyword guifg=#FFDE00 ctermfg=220 gui=NONE cterm=NONE 31 | hi String guifg=#61CE3C ctermfg=77 gui=NONE cterm=NONE 32 | hi Type guifg=#84A7C1 ctermfg=109 gui=NONE cterm=NONE 33 | hi Identifier guifg=#61CE3C ctermfg=77 gui=NONE cterm=NONE 34 | hi Function guifg=#FF5600 ctermfg=202 gui=NONE cterm=NONE 35 | hi IncSearch guifg=#000000 ctermfg=16 guibg=#F0B016 ctermbg=214 gui=NONE cterm=NONE 36 | hi Search guifg=#000000 ctermfg=16 guibg=#FAD680 ctermbg=222 gui=NONE cterm=NONE 37 | hi PreProc guifg=#FF5600 ctermfg=202 gui=NONE cterm=NONE 38 | hi Special guifg=#F0B016 ctermfg=214 gui=NONE cterm=NONE 39 | hi StatusLine guifg=#FFFFFF ctermfg=231 guibg=#FF5600 ctermbg=202 gui=ITALIC cterm=NONE 40 | hi StatusLineNC guifg=#000000 ctermfg=16 guibg=#FFFFFF ctermbg=231 gui=NONE cterm=NONE 41 | hi NonText guifg=#4A4A59 ctermfg=239 gui=NONE cterm=NONE 42 | hi SpecialKey guifg=#4A4A59 ctermfg=239 gui=NONE cterm=NONE 43 | hi OL1 guifg=#FF5600 ctermfg=202 gui=NONE cterm=NONE 44 | hi OL2 guifg=#61CE3C ctermfg=77 gui=NONE cterm=NONE 45 | hi OL3 guifg=#84A7C1 ctermfg=109 gui=NONE cterm=NONE 46 | hi OL4 guifg=#D8FA3C ctermfg=191 gui=NONE cterm=NONE 47 | hi BT1 guifg=#AEAEAE ctermfg=145 gui=NONE cterm=NONE 48 | hi markdownCode guifg=#61CE3C ctermfg=77 guibg=#070A15 ctermbg=232 gui=NONE cterm=NONE 49 | hi gitcommitSelectedFile guifg=#61CE3C ctermfg=77 gui=NONE cterm=NONE 50 | hi gitcommitDiscardedFile guifg=#C23621 ctermfg=130 gui=NONE cterm=NONE 51 | hi gitcommitWarning guifg=#C23621 ctermfg=130 gui=NONE cterm=NONE 52 | hi gitcommitBranch guifg=#FFDE00 ctermfg=220 gui=NONE cterm=NONE 53 | hi gitcommitHeader guifg=#84A7C1 ctermfg=109 gui=NONE cterm=NONE 54 | 55 | " Make `defp` stand out from `def` in Elixir. 56 | hi elixirPrivateDefine ctermfg=172 57 | hi elixirPrivateFunctionDeclaration ctermfg=172 58 | 59 | hi link htmlTag Type 60 | hi link htmlEndTag Type 61 | hi link htmlTagName Type 62 | hi link rubyClass Keyword 63 | hi link rubyDefine Keyword 64 | hi link rubyInclude Keyword 65 | hi link rubyAttribute Keyword 66 | hi link rubyConstant Type 67 | hi link rubySymbol Constant 68 | hi link rubyInstanceVariable Normal 69 | hi link rubyString String 70 | hi link rubyStringDelimiter String 71 | hi link railsMethod Type 72 | hi link sassMixin Keyword 73 | hi link sassMixing Constant 74 | hi link BT2 BT1 75 | hi link BT3 BT1 76 | hi link BT4 BT1 77 | hi link markdownCodeBlock markdownCode 78 | -------------------------------------------------------------------------------- /vim/config/plugins.vim: -------------------------------------------------------------------------------- 1 | call plug#begin('~/.vim/vim-plug-plugins') 2 | " Make sure you use single quotes. 3 | 4 | Plug 'AndrewRadev/splitjoin.vim' " Language-aware toggle between single-line and multi-line versions. 5 | Plug 'barsoom/vim-turbux' " Lets you run the current test in a tmux split via Vimux. 6 | Plug 'preservim/vimux', { 'tag': '1.1.0' } " Lets you run commands in a tmux split. 7 | Plug 'bogado/file-line' " Can open myfile.xy:123 to jump to that line. 8 | Plug 'github/copilot.vim' " GitHub Copilot. 9 | Plug 'henrik/CamelCaseMotion' " Motions like ,w and ,b for camelCase and snake_case sub-words. 10 | Plug 'henrik/git-grep-vim' " Adds :GitGrep. 11 | Plug 'henrik/vim-open-url' " Open URLs. 12 | Plug 'henrik/vim-reveal-in-finder' " Adds :Reveal to reveal the current file in macOS Finder. 13 | Plug 'henrik/rename.vim' " 'Rename newname.txt' and 'Rename! newdir/newname.txt' 14 | Plug 'henrik/vim-ruby-runner' " Execute Ruby into an output buffer. 15 | Plug 'henrik/vim-yaml-helper' " YAML utilities like :YamlFullPath and :YAMLGoToKey. 16 | Plug 'henrik/vim-qargs' " https://thepugautomatic.com/2012/07/project-wide-search-and-replace-in-vim-with-qdo/ 17 | Plug 'junegunn/fzf' " Dependency of fzf.vim. 18 | Plug 'junegunn/fzf.vim' " File navigation. 19 | Plug 'preservim/nerdtree' " File tree drawer. 20 | Plug 'tpope/vim-commentary' " Toggle comments. 21 | Plug 'tpope/vim-endwise' " Add 'end' automatically to Ruby and Elixir blocks etc. 22 | Plug 'tpope/vim-fugitive' " Vim commands like :Gwrite and :Gblame. 23 | Plug 'tpope/vim-projectionist' " :A alternate file navigation. 24 | Plug 'tpope/vim-rake' " vim-projectionist support in non-Rails Ruby projects. 25 | Plug 'tpope/vim-repeat' " Lets you use '.' to repeat vim-surround commands. 26 | Plug 'tpope/vim-surround' " Change, add or remove surrounding characters like quotes or brackets. 27 | Plug 'vim-scripts/argtextobj.vim' " Argument text objects, like daa to delete a function argument. 28 | Plug 'ap/vim-css-color' " Color name highlighter. 29 | Plug 'neoclide/coc.nvim', {'branch': 'release'} " Supports VSCode features, language servers etc. 30 | 31 | " Languages/frameworks. 32 | Plug 'elixir-editors/vim-elixir' 33 | Plug 'elmcast/elm-vim' 34 | Plug 'kchmck/vim-coffee-script' 35 | Plug 'keith/swift.vim' 36 | Plug 'pangloss/vim-javascript' 37 | Plug 'tpope/vim-rails' 38 | Plug 'slim-template/vim-slim' 39 | Plug 'vim-ruby/vim-ruby' 40 | 41 | call plug#end() 42 | 43 | " fzf (fuzzy file finder) 44 | 45 | let g:fzf_history_dir = '~/.local/share/fzf-history' 46 | 47 | " Only files not excluded by .gitignore, for speed. This means it won't show non-version-controlled files. 48 | " Ideally we'd use wildignore. See: https://github.com/junegunn/fzf.vim/issues/133 49 | map :GFiles 50 | 51 | " NERDTree 52 | 53 | let NERDTreeIgnore=['\.rbc$', '\~$'] 54 | 55 | " Disable menu. 56 | let g:NERDMenuMode=0 57 | 58 | map n :NERDTreeToggle 59 | map N :NERDTreeFindIfFindable 60 | 61 | " Commentary 62 | xmap c Commentary 63 | nmap c Commentary 64 | nmap cc CommentaryLine 65 | nmap cu CommentaryUndo 66 | 67 | " Git grep 68 | " Using 'a' because it used to be :Ack. 69 | " We need the trailing space. 70 | exe "nnoremap a :GitGrep " 71 | 72 | " vimux 73 | let g:VimuxOrientation = "h" 74 | let g:VimuxUseNearestPane = 1 75 | 76 | " turbux 77 | let g:no_turbux_mappings = 1 78 | map M SendTestToTmux 79 | map m SendFocusedTestToTmux 80 | 81 | " CamelCaseMotion 82 | " Make c,w not include the trailing underscore: 83 | " https://github.com/bkad/CamelCaseMotion/issues/10 84 | nmap c,w c,e 85 | nmap ci,w ci,e 86 | 87 | " vim-coffee-script 88 | let g:coffee_compile_vert = 1 89 | 90 | " vim-markdown 91 | let g:markdown_fenced_languages = ['coffee', 'css', 'erb=eruby', 'javascript', 'js=javascript', 'json=javascript', 'ruby', 'sass', 'xml', 'html'] 92 | 93 | " elm-vim 94 | let g:elm_format_autosave = 1 95 | 96 | " coc.nvim 97 | source ~/.vim/config/plugins/coc.vim 98 | -------------------------------------------------------------------------------- /git_template/hooks/samples/update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to blocks unannotated tags from entering. 4 | # Called by "git receive-pack" with arguments: refname sha1-old sha1-new 5 | # 6 | # To enable this hook, rename this file to "update". 7 | # 8 | # Config 9 | # ------ 10 | # hooks.allowunannotated 11 | # This boolean sets whether unannotated tags will be allowed into the 12 | # repository. By default they won't be. 13 | # hooks.allowdeletetag 14 | # This boolean sets whether deleting tags will be allowed in the 15 | # repository. By default they won't be. 16 | # hooks.allowmodifytag 17 | # This boolean sets whether a tag may be modified after creation. By default 18 | # it won't be. 19 | # hooks.allowdeletebranch 20 | # This boolean sets whether deleting branches will be allowed in the 21 | # repository. By default they won't be. 22 | # hooks.denycreatebranch 23 | # This boolean sets whether remotely creating branches will be denied 24 | # in the repository. By default this is allowed. 25 | # 26 | 27 | # --- Command line 28 | refname="$1" 29 | oldrev="$2" 30 | newrev="$3" 31 | 32 | # --- Safety check 33 | if [ -z "$GIT_DIR" ]; then 34 | echo "Don't run this script from the command line." >&2 35 | echo " (if you want, you could supply GIT_DIR then run" >&2 36 | echo " $0 )" >&2 37 | exit 1 38 | fi 39 | 40 | if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then 41 | echo "Usage: $0 " >&2 42 | exit 1 43 | fi 44 | 45 | # --- Config 46 | allowunannotated=$(git config --bool hooks.allowunannotated) 47 | allowdeletebranch=$(git config --bool hooks.allowdeletebranch) 48 | denycreatebranch=$(git config --bool hooks.denycreatebranch) 49 | allowdeletetag=$(git config --bool hooks.allowdeletetag) 50 | allowmodifytag=$(git config --bool hooks.allowmodifytag) 51 | 52 | # check for no description 53 | projectdesc=$(sed -e '1q' "$GIT_DIR/description") 54 | case "$projectdesc" in 55 | "Unnamed repository"* | "") 56 | echo "*** Project description file hasn't been set" >&2 57 | exit 1 58 | ;; 59 | esac 60 | 61 | # --- Check types 62 | # if $newrev is 0000...0000, it's a commit to delete a ref. 63 | zero="0000000000000000000000000000000000000000" 64 | if [ "$newrev" = "$zero" ]; then 65 | newrev_type=delete 66 | else 67 | newrev_type=$(git cat-file -t $newrev) 68 | fi 69 | 70 | case "$refname","$newrev_type" in 71 | refs/tags/*,commit) 72 | # un-annotated tag 73 | short_refname=${refname##refs/tags/} 74 | if [ "$allowunannotated" != "true" ]; then 75 | echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 76 | echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 77 | exit 1 78 | fi 79 | ;; 80 | refs/tags/*,delete) 81 | # delete tag 82 | if [ "$allowdeletetag" != "true" ]; then 83 | echo "*** Deleting a tag is not allowed in this repository" >&2 84 | exit 1 85 | fi 86 | ;; 87 | refs/tags/*,tag) 88 | # annotated tag 89 | if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 90 | then 91 | echo "*** Tag '$refname' already exists." >&2 92 | echo "*** Modifying a tag is not allowed in this repository." >&2 93 | exit 1 94 | fi 95 | ;; 96 | refs/heads/*,commit) 97 | # branch 98 | if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then 99 | echo "*** Creating a branch is not allowed in this repository" >&2 100 | exit 1 101 | fi 102 | ;; 103 | refs/heads/*,delete) 104 | # delete branch 105 | if [ "$allowdeletebranch" != "true" ]; then 106 | echo "*** Deleting a branch is not allowed in this repository" >&2 107 | exit 1 108 | fi 109 | ;; 110 | refs/remotes/*,commit) 111 | # tracking branch 112 | ;; 113 | refs/remotes/*,delete) 114 | # delete tracking branch 115 | if [ "$allowdeletebranch" != "true" ]; then 116 | echo "*** Deleting a tracking branch is not allowed in this repository" >&2 117 | exit 1 118 | fi 119 | ;; 120 | *) 121 | # Anything else (is there anything else?) 122 | echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 123 | exit 1 124 | ;; 125 | esac 126 | 127 | # --- Finished 128 | exit 0 129 | -------------------------------------------------------------------------------- /bash/aliases.sh: -------------------------------------------------------------------------------- 1 | # Management 2 | alias dots="cd ~/.dotfiles && vim" 3 | alias reload='source ~/.bash_profile && echo "sourced ~/.bash_profile"' 4 | alias redot='cd ~/.dotfiles && gpp && rake install; cd -' 5 | 6 | alias hosts='sudo vim /etc/hosts' 7 | alias vrb='vim -c "setf ruby"' 8 | 9 | # http://www.hogbaysoftware.com/products/plaintext 10 | alias text='cd ~/Dropbox/PlainText && vim' 11 | 12 | # Shell 13 | alias la='ls -alh' 14 | alias cdd='cd -' # Back to last directory 15 | alias cdr='cd $(git rev-parse --show-toplevel)' # Back to git root - from https://twitter.com/thorstenball/status/1223218245592911878 16 | alias pg='ps aux | head -n1; ps aux | grep -i' 17 | alias tf='tail -F -n200' 18 | alias top='top -ocpu' 19 | alias ..='cd ..' 20 | alias ...='cd ../..' 21 | alias ....='cd ../../..' 22 | alias ip="ifconfig|grep broadcast" # List IPs 23 | 24 | # Portable ls with colors 25 | if ls --color -d . >/dev/null 2>&1; then 26 | alias ls='ls --color=auto' # Linux 27 | elif ls -G -d . >/dev/null 2>&1; then 28 | alias ls='ls -G' # BSD/OS X 29 | fi 30 | 31 | # Ruby on Rails 32 | alias sx="[ -f script/console ] && script/console --sandbox || bundle exec rails console --sandbox" 33 | alias sdb="[ -f script/dbconsole ] && script/console --include-password || bundle exec rails dbconsole --include-password" 34 | alias f="script/foreman_turbux" 35 | 36 | # Phoenix or Ruby on Rails 37 | alias mig="if [ -f mix.exs ]; then mix ecto.migrate; elif [ -f Rakefile ]; then bundle exec rake db:migrate; else echo 'Did not detect Phoenix or Rails.'; fi" 38 | alias ss="if [ -f mix.exs ]; then mix phx.server; elif [ -f Rakefile ]; then rails server; else echo 'Did not detect Phoenix or Rails.'; fi" 39 | alias sc="if [ -f mix.exs ]; then iex -S mix; elif [ -f Rakefile ]; then bundle exec rails console; else echo 'Did not detect Phoenix or Rails.'; fi" 40 | 41 | # Ruby 42 | alias be="bundle exec" 43 | 44 | # Elixir 45 | alias smix="iex -S mix" 46 | 47 | # Docker Compose (as seen e.g. in https://thoughtbot.com/blog/rails-on-docker) 48 | alias dew="docker-compose exec web" 49 | 50 | # Git 51 | 52 | # Determine if the main Git branch is "master" or "main". (Assumes it's one of those two.) 53 | # https://stackoverflow.com/a/66622363/6962 54 | function git_main_branch { 55 | [ -f "$(git rev-parse --show-toplevel)/.git/refs/heads/master" ] && echo "master" || echo "main" 56 | } 57 | 58 | alias g="git" 59 | alias gs="git status" 60 | alias gw="git show" 61 | alias gw^="git show HEAD^" 62 | alias gw^^="git show HEAD^^" 63 | alias gw^^^="git show HEAD^^^" 64 | alias gw^^^^="git show HEAD^^^^" 65 | alias gd="git diff HEAD" # What's changed? Both staged and unstaged. 66 | alias gdo="git diff --cached" # What's changed? Only staged (added) changes. 67 | # for gco ("git commit only") and gca ("git commit all"), see functions.sh. 68 | # for gget (git clone and cd), see functions.sh. 69 | alias gcaf="git add --all && gcof" 70 | alias gcof="git commit --no-verify -m" 71 | alias gcac="gca Cleanup" 72 | alias gcoc="gco Cleanup" 73 | alias gcaw="gca $'Whitespace\n\n[skip ci]'" 74 | alias gcow="gco $'Whitespace\n\n[skip ci]'" 75 | alias gl="git pull" 76 | alias gpp="git pull && git push" 77 | alias gppp="git push -u" # Can't pull because you forgot to track? Run this. 78 | alias go="git checkout" 79 | alias gb="git checkout -b" 80 | alias got="git checkout -" 81 | alias gom='git checkout `git_main_branch`' 82 | alias grb='git rebase -i @{push}' 83 | alias gr="git branch -d" 84 | alias grr="git branch -D" 85 | alias gcp="git cherry-pick" 86 | alias gcpp="git cherry-pick -" # "cherry-pick previous", e.g. the head of the branch we just left 87 | alias gam="git commit --amend --allow-empty" 88 | alias gamm="git add --all && git commit --amend --allow-empty --no-edit" 89 | alias gammf="gamm --no-verify" 90 | alias gba="git rebase --abort" 91 | alias gbc="git add -A && git rebase --continue" 92 | alias gbm='git fetch origin `git_main_branch` && git rebase origin/`git_main_branch`' 93 | alias gap="git add --intent-to-add . && git add -p" # Like "git add -p" but also ask about any newly added files. 94 | 95 | # Pairing (via https://github.com/barsoom/devbox/blob/master/bin/pair) 96 | alias pairo="pair olle" 97 | alias pairp="pair peter" 98 | 99 | # Heroku 100 | alias h="heroku" 101 | alias hdep="git push heroku" 102 | alias hmig="heroku run rake db:migrate" 103 | alias ho="heroku open" 104 | alias hsc="heroku run rails console -r heroku" # Explicit remote, for apps with a staging as well. 105 | alias hscs="heroku run rails console -r staging" 106 | alias hlog="heroku logs -t -r heroku" 107 | 108 | # The Stack 109 | alias ssc="dev console prod" 110 | alias sscs="dev console staging" 111 | 112 | # tmux 113 | alias ta="tmux attach" 114 | # With tmux mouse mode on, just select text in a pane to copy. 115 | # Then run tcopy to put it in the OS X clipboard (assuming reattach-to-user-namespace). 116 | alias tcopy="tmux show-buffer | pbcopy" 117 | 118 | # WTI (translation service) 119 | 120 | # Push to WTI until it sticks. 121 | # It tends to refuse pushes of a target language until it's processed the source language push. 122 | alias wpus="ruby -e 'loop { x = \`wti push; wti push --target\`; puts x; break unless x.include?(%{Locked}); puts; puts %{#{Time.now.strftime(%{%H:%M:%S})}: Retrying in a bit…}; puts; sleep 15 }'" 123 | 124 | # Work 125 | 126 | # Sometimes the camera in my Cinema Display stops working. This fixes it. 127 | alias fixcam="sudo killall VDCAssistant" 128 | 129 | # Sometimes the time in my VM is off. This fixes it. 130 | alias fixtime="sudo service ntp stop; sudo ntpd -gq; sudo service ntp start" 131 | -------------------------------------------------------------------------------- /git_template/hooks/samples/pre-rebase.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2006, 2008 Junio C Hamano 4 | # 5 | # The "pre-rebase" hook is run just before "git rebase" starts doing 6 | # its job, and can prevent the command from running by exiting with 7 | # non-zero status. 8 | # 9 | # The hook is called with the following parameters: 10 | # 11 | # $1 -- the upstream the series was forked from. 12 | # $2 -- the branch being rebased (or empty when rebasing the current branch). 13 | # 14 | # This sample shows how to prevent topic branches that are already 15 | # merged to 'next' branch from getting rebased, because allowing it 16 | # would result in rebasing already published history. 17 | 18 | publish=next 19 | basebranch="$1" 20 | if test "$#" = 2 21 | then 22 | topic="refs/heads/$2" 23 | else 24 | topic=`git symbolic-ref HEAD` || 25 | exit 0 ;# we do not interrupt rebasing detached HEAD 26 | fi 27 | 28 | case "$topic" in 29 | refs/heads/??/*) 30 | ;; 31 | *) 32 | exit 0 ;# we do not interrupt others. 33 | ;; 34 | esac 35 | 36 | # Now we are dealing with a topic branch being rebased 37 | # on top of master. Is it OK to rebase it? 38 | 39 | # Does the topic really exist? 40 | git show-ref -q "$topic" || { 41 | echo >&2 "No such branch $topic" 42 | exit 1 43 | } 44 | 45 | # Is topic fully merged to master? 46 | not_in_master=`git rev-list --pretty=oneline ^master "$topic"` 47 | if test -z "$not_in_master" 48 | then 49 | echo >&2 "$topic is fully merged to master; better remove it." 50 | exit 1 ;# we could allow it, but there is no point. 51 | fi 52 | 53 | # Is topic ever merged to next? If so you should not be rebasing it. 54 | only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` 55 | only_next_2=`git rev-list ^master ${publish} | sort` 56 | if test "$only_next_1" = "$only_next_2" 57 | then 58 | not_in_topic=`git rev-list "^$topic" master` 59 | if test -z "$not_in_topic" 60 | then 61 | echo >&2 "$topic is already up-to-date with master" 62 | exit 1 ;# we could allow it, but there is no point. 63 | else 64 | exit 0 65 | fi 66 | else 67 | not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` 68 | /usr/bin/perl -e ' 69 | my $topic = $ARGV[0]; 70 | my $msg = "* $topic has commits already merged to public branch:\n"; 71 | my (%not_in_next) = map { 72 | /^([0-9a-f]+) /; 73 | ($1 => 1); 74 | } split(/\n/, $ARGV[1]); 75 | for my $elem (map { 76 | /^([0-9a-f]+) (.*)$/; 77 | [$1 => $2]; 78 | } split(/\n/, $ARGV[2])) { 79 | if (!exists $not_in_next{$elem->[0]}) { 80 | if ($msg) { 81 | print STDERR $msg; 82 | undef $msg; 83 | } 84 | print STDERR " $elem->[1]\n"; 85 | } 86 | } 87 | ' "$topic" "$not_in_next" "$not_in_master" 88 | exit 1 89 | fi 90 | 91 | exit 0 92 | 93 | ################################################################ 94 | 95 | This sample hook safeguards topic branches that have been 96 | published from being rewound. 97 | 98 | The workflow assumed here is: 99 | 100 | * Once a topic branch forks from "master", "master" is never 101 | merged into it again (either directly or indirectly). 102 | 103 | * Once a topic branch is fully cooked and merged into "master", 104 | it is deleted. If you need to build on top of it to correct 105 | earlier mistakes, a new topic branch is created by forking at 106 | the tip of the "master". This is not strictly necessary, but 107 | it makes it easier to keep your history simple. 108 | 109 | * Whenever you need to test or publish your changes to topic 110 | branches, merge them into "next" branch. 111 | 112 | The script, being an example, hardcodes the publish branch name 113 | to be "next", but it is trivial to make it configurable via 114 | $GIT_DIR/config mechanism. 115 | 116 | With this workflow, you would want to know: 117 | 118 | (1) ... if a topic branch has ever been merged to "next". Young 119 | topic branches can have stupid mistakes you would rather 120 | clean up before publishing, and things that have not been 121 | merged into other branches can be easily rebased without 122 | affecting other people. But once it is published, you would 123 | not want to rewind it. 124 | 125 | (2) ... if a topic branch has been fully merged to "master". 126 | Then you can delete it. More importantly, you should not 127 | build on top of it -- other people may already want to 128 | change things related to the topic as patches against your 129 | "master", so if you need further changes, it is better to 130 | fork the topic (perhaps with the same name) afresh from the 131 | tip of "master". 132 | 133 | Let's look at this example: 134 | 135 | o---o---o---o---o---o---o---o---o---o "next" 136 | / / / / 137 | / a---a---b A / / 138 | / / / / 139 | / / c---c---c---c B / 140 | / / / \ / 141 | / / / b---b C \ / 142 | / / / / \ / 143 | ---o---o---o---o---o---o---o---o---o---o---o "master" 144 | 145 | 146 | A, B and C are topic branches. 147 | 148 | * A has one fix since it was merged up to "next". 149 | 150 | * B has finished. It has been fully merged up to "master" and "next", 151 | and is ready to be deleted. 152 | 153 | * C has not merged to "next" at all. 154 | 155 | We would want to allow C to be rebased, refuse A, and encourage 156 | B to be deleted. 157 | 158 | To compute (1): 159 | 160 | git rev-list ^master ^topic next 161 | git rev-list ^master next 162 | 163 | if these match, topic has not merged in next at all. 164 | 165 | To compute (2): 166 | 167 | git rev-list master..topic 168 | 169 | if this is empty, it is fully merged to "master". 170 | -------------------------------------------------------------------------------- /extras/VibrantInk.itermcolors: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ansi 0 Color 6 | 7 | Blue Component 8 | 0.022960265837104088 9 | Green Component 10 | 0.022960265837104088 11 | Red Component 12 | 0.022960265837104088 13 | 14 | Ansi 1 Color 15 | 16 | Blue Component 17 | 0.1069200336933136 18 | Green Component 19 | 0.18957415223121643 20 | Red Component 21 | 1 22 | 23 | Ansi 10 Color 24 | 25 | Blue Component 26 | 0.0 27 | Green Component 28 | 1 29 | Red Component 30 | 0.0 31 | 32 | Ansi 11 Color 33 | 34 | Blue Component 35 | 0.0 36 | Green Component 37 | 1 38 | Red Component 39 | 1 40 | 41 | Ansi 12 Color 42 | 43 | Blue Component 44 | 1 45 | Green Component 46 | 0.0 47 | Red Component 48 | 0.0 49 | 50 | Ansi 13 Color 51 | 52 | Blue Component 53 | 1 54 | Green Component 55 | 0.0 56 | Red Component 57 | 1 58 | 59 | Ansi 14 Color 60 | 61 | Blue Component 62 | 1 63 | Green Component 64 | 1 65 | Red Component 66 | 0.0 67 | 68 | Ansi 15 Color 69 | 70 | Blue Component 71 | 0.89803922176361084 72 | Green Component 73 | 0.89803922176361084 74 | Red Component 75 | 0.89803922176361084 76 | 77 | Ansi 2 Color 78 | 79 | Blue Component 80 | 0.32576107978820801 81 | Green Component 82 | 1 83 | Red Component 84 | 0.056544534862041473 85 | 86 | Ansi 3 Color 87 | 88 | Blue Component 89 | 0.0 90 | Green Component 91 | 0.80155110359191895 92 | Red Component 93 | 1 94 | 95 | Ansi 4 Color 96 | 97 | Blue Component 98 | 0.80000001192092896 99 | Green Component 100 | 0.70588237047195435 101 | Red Component 102 | 0.26666668057441711 103 | 104 | Ansi 5 Color 105 | 106 | Blue Component 107 | 0.80000001192092896 108 | Green Component 109 | 0.20000000298023224 110 | Red Component 111 | 0.60000002384185791 112 | 113 | Ansi 6 Color 114 | 115 | Blue Component 116 | 0.80000001192092896 117 | Green Component 118 | 0.70588237047195435 119 | Red Component 120 | 0.26666668057441711 121 | 122 | Ansi 7 Color 123 | 124 | Blue Component 125 | 0.96078431606292725 126 | Green Component 127 | 0.96078431606292725 128 | Red Component 129 | 0.96078431606292725 130 | 131 | Ansi 8 Color 132 | 133 | Blue Component 134 | 0.33333333333333331 135 | Green Component 136 | 0.33333333333333331 137 | Red Component 138 | 0.33333333333333331 139 | 140 | Ansi 9 Color 141 | 142 | Blue Component 143 | 0.0 144 | Green Component 145 | 0.0 146 | Red Component 147 | 1 148 | 149 | Background Color 150 | 151 | Blue Component 152 | 0.0 153 | Green Component 154 | 0.0 155 | Red Component 156 | 0.0 157 | 158 | Bold Color 159 | 160 | Blue Component 161 | 1 162 | Green Component 163 | 1 164 | Red Component 165 | 1 166 | 167 | Cursor Color 168 | 169 | Blue Component 170 | 1 171 | Green Component 172 | 1 173 | Red Component 174 | 1 175 | 176 | Cursor Text Color 177 | 178 | Blue Component 179 | 0.0 180 | Green Component 181 | 0.0 182 | Red Component 183 | 0.0 184 | 185 | Foreground Color 186 | 187 | Blue Component 188 | 1 189 | Green Component 190 | 1 191 | Red Component 192 | 1 193 | 194 | Selected Text Color 195 | 196 | Blue Component 197 | 0.0 198 | Green Component 199 | 0.0 200 | Red Component 201 | 0.0 202 | 203 | Selection Color 204 | 205 | Blue Component 206 | 1 207 | Green Component 208 | 0.8353000283241272 209 | Red Component 210 | 0.70980000495910645 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /bash/lib/git-prompt.sh: -------------------------------------------------------------------------------- 1 | # bash/zsh git prompt support 2 | # 3 | # Copyright (C) 2006,2007 Shawn O. Pearce 4 | # Distributed under the GNU General Public License, version 2.0. 5 | # 6 | # This script allows you to see repository status in your prompt. 7 | # 8 | # To enable: 9 | # 10 | # 1) Copy this file to somewhere (e.g. ~/.git-prompt.sh). 11 | # 2) Add the following line to your .bashrc/.zshrc: 12 | # source ~/.git-prompt.sh 13 | # 3a) Change your PS1 to call __git_ps1 as 14 | # command-substitution: 15 | # Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' 16 | # ZSH: setopt PROMPT_SUBST ; PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ ' 17 | # the optional argument will be used as format string. 18 | # 3b) Alternatively, for a slightly faster prompt, __git_ps1 can 19 | # be used for PROMPT_COMMAND in Bash or for precmd() in Zsh 20 | # with two parameters,
 and , which are strings
 21 | #        you would put in $PS1 before and after the status string
 22 | #        generated by the git-prompt machinery.  e.g.
 23 | #        Bash: PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "'
 24 | #          will show username, at-sign, host, colon, cwd, then
 25 | #          various status string, followed by dollar and SP, as
 26 | #          your prompt.
 27 | #        ZSH:  precmd () { __git_ps1 "%n" ":%~$ " "|%s" }
 28 | #          will show username, pipe, then various status string,
 29 | #          followed by colon, cwd, dollar and SP, as your prompt.
 30 | #        Optionally, you can supply a third argument with a printf
 31 | #        format string to finetune the output of the branch status
 32 | #
 33 | # The repository status will be displayed only if you are currently in a
 34 | # git repository. The %s token is the placeholder for the shown status.
 35 | #
 36 | # The prompt status always includes the current branch name.
 37 | #
 38 | # In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty value,
 39 | # unstaged (*) and staged (+) changes will be shown next to the branch
 40 | # name.  You can configure this per-repository with the
 41 | # bash.showDirtyState variable, which defaults to true once
 42 | # GIT_PS1_SHOWDIRTYSTATE is enabled.
 43 | #
 44 | # You can also see if currently something is stashed, by setting
 45 | # GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed,
 46 | # then a '$' will be shown next to the branch name.
 47 | #
 48 | # If you would like to see if there're untracked files, then you can set
 49 | # GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're untracked
 50 | # files, then a '%' will be shown next to the branch name.  You can
 51 | # configure this per-repository with the bash.showUntrackedFiles
 52 | # variable, which defaults to true once GIT_PS1_SHOWUNTRACKEDFILES is
 53 | # enabled.
 54 | #
 55 | # If you would like to see the difference between HEAD and its upstream,
 56 | # set GIT_PS1_SHOWUPSTREAM="auto".  A "<" indicates you are behind, ">"
 57 | # indicates you are ahead, "<>" indicates you have diverged and "="
 58 | # indicates that there is no difference. You can further control
 59 | # behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated list
 60 | # of values:
 61 | #
 62 | #     verbose       show number of commits ahead/behind (+/-) upstream
 63 | #     name          if verbose, then also show the upstream abbrev name
 64 | #     legacy        don't use the '--count' option available in recent
 65 | #                   versions of git-rev-list
 66 | #     git           always compare HEAD to @{upstream}
 67 | #     svn           always compare HEAD to your SVN upstream
 68 | #
 69 | # By default, __git_ps1 will compare HEAD to your SVN upstream if it can
 70 | # find one, or @{upstream} otherwise.  Once you have set
 71 | # GIT_PS1_SHOWUPSTREAM, you can override it on a per-repository basis by
 72 | # setting the bash.showUpstream config variable.
 73 | #
 74 | # You can change the separator between the branch name and the above
 75 | # state symbols by setting GIT_PS1_STATESEPARATOR. The default separator
 76 | # is SP.
 77 | #
 78 | # When there is an in-progress operation such as a merge, rebase,
 79 | # revert, cherry-pick, or bisect, the prompt will include information
 80 | # related to the operation, often in the form "|".
 81 | #
 82 | # When the repository has a sparse-checkout, a notification of the form
 83 | # "|SPARSE" will be included in the prompt.  This can be shortened to a
 84 | # single '?' character by setting GIT_PS1_COMPRESSSPARSESTATE, or omitted
 85 | # by setting GIT_PS1_OMITSPARSESTATE.
 86 | #
 87 | # If you would like to see a notification on the prompt when there are
 88 | # unresolved conflicts, set GIT_PS1_SHOWCONFLICTSTATE to "yes". The
 89 | # prompt will include "|CONFLICT".
 90 | #
 91 | # If you would like to see more information about the identity of
 92 | # commits checked out as a detached HEAD, set GIT_PS1_DESCRIBE_STYLE
 93 | # to one of these values:
 94 | #
 95 | #     contains      relative to newer annotated tag (v1.6.3.2~35)
 96 | #     branch        relative to newer tag or branch (master~4)
 97 | #     describe      relative to older annotated tag (v1.6.3.1-13-gdd42c2f)
 98 | #     tag           relative to any older tag (v1.6.3.1-13-gdd42c2f)
 99 | #     default       exactly matching tag
100 | #
101 | # If you would like a colored hint about the current dirty state, set
102 | # GIT_PS1_SHOWCOLORHINTS to a nonempty value. The colors are based on
103 | # the colored output of "git status -sb" and are available only when
104 | # using __git_ps1 for PROMPT_COMMAND or precmd in Bash,
105 | # but always available in Zsh.
106 | #
107 | # If you would like __git_ps1 to do nothing in the case when the current
108 | # directory is set up to be ignored by git, then set
109 | # GIT_PS1_HIDE_IF_PWD_IGNORED to a nonempty value. Override this on the
110 | # repository level by setting bash.hideIfPwdIgnored to "false".
111 | 
112 | # check whether printf supports -v
113 | __git_printf_supports_v=
114 | printf -v __git_printf_supports_v -- '%s' yes >/dev/null 2>&1
115 | 
116 | # stores the divergence from upstream in $p
117 | # used by GIT_PS1_SHOWUPSTREAM
118 | __git_ps1_show_upstream ()
119 | {
120 | 	local key value
121 | 	local svn_remote svn_url_pattern count n
122 | 	local upstream_type=git legacy="" verbose="" name=""
123 | 
124 | 	svn_remote=()
125 | 	# get some config options from git-config
126 | 	local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
127 | 	while read -r key value; do
128 | 		case "$key" in
129 | 		bash.showupstream)
130 | 			GIT_PS1_SHOWUPSTREAM="$value"
131 | 			if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
132 | 				p=""
133 | 				return
134 | 			fi
135 | 			;;
136 | 		svn-remote.*.url)
137 | 			svn_remote[$((${#svn_remote[@]} + 1))]="$value"
138 | 			svn_url_pattern="$svn_url_pattern\\|$value"
139 | 			upstream_type=svn+git # default upstream type is SVN if available, else git
140 | 			;;
141 | 		esac
142 | 	done <<< "$output"
143 | 
144 | 	# parse configuration values
145 | 	local option
146 | 	for option in ${GIT_PS1_SHOWUPSTREAM}; do
147 | 		case "$option" in
148 | 		git|svn) upstream_type="$option" ;;
149 | 		verbose) verbose=1 ;;
150 | 		legacy)  legacy=1  ;;
151 | 		name)    name=1 ;;
152 | 		esac
153 | 	done
154 | 
155 | 	# Find our upstream type
156 | 	case "$upstream_type" in
157 | 	git)    upstream_type="@{upstream}" ;;
158 | 	svn*)
159 | 		# get the upstream from the "git-svn-id: ..." in a commit message
160 | 		# (git-svn uses essentially the same procedure internally)
161 | 		local -a svn_upstream
162 | 		svn_upstream=($(git log --first-parent -1 \
163 | 					--grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
164 | 		if [[ 0 -ne ${#svn_upstream[@]} ]]; then
165 | 			svn_upstream=${svn_upstream[${#svn_upstream[@]} - 2]}
166 | 			svn_upstream=${svn_upstream%@*}
167 | 			local n_stop="${#svn_remote[@]}"
168 | 			for ((n=1; n <= n_stop; n++)); do
169 | 				svn_upstream=${svn_upstream#${svn_remote[$n]}}
170 | 			done
171 | 
172 | 			if [[ -z "$svn_upstream" ]]; then
173 | 				# default branch name for checkouts with no layout:
174 | 				upstream_type=${GIT_SVN_ID:-git-svn}
175 | 			else
176 | 				upstream_type=${svn_upstream#/}
177 | 			fi
178 | 		elif [[ "svn+git" = "$upstream_type" ]]; then
179 | 			upstream_type="@{upstream}"
180 | 		fi
181 | 		;;
182 | 	esac
183 | 
184 | 	# Find how many commits we are ahead/behind our upstream
185 | 	if [[ -z "$legacy" ]]; then
186 | 		count="$(git rev-list --count --left-right \
187 | 				"$upstream_type"...HEAD 2>/dev/null)"
188 | 	else
189 | 		# produce equivalent output to --count for older versions of git
190 | 		local commits
191 | 		if commits="$(git rev-list --left-right "$upstream_type"...HEAD 2>/dev/null)"
192 | 		then
193 | 			local commit behind=0 ahead=0
194 | 			for commit in $commits
195 | 			do
196 | 				case "$commit" in
197 | 				"<"*) ((behind++)) ;;
198 | 				*)    ((ahead++))  ;;
199 | 				esac
200 | 			done
201 | 			count="$behind	$ahead"
202 | 		else
203 | 			count=""
204 | 		fi
205 | 	fi
206 | 
207 | 	# calculate the result
208 | 	if [[ -z "$verbose" ]]; then
209 | 		case "$count" in
210 | 		"") # no upstream
211 | 			p="" ;;
212 | 		"0	0") # equal to upstream
213 | 			p="=" ;;
214 | 		"0	"*) # ahead of upstream
215 | 			p=">" ;;
216 | 		*"	0") # behind upstream
217 | 			p="<" ;;
218 | 		*)	    # diverged from upstream
219 | 			p="<>" ;;
220 | 		esac
221 | 	else # verbose, set upstream instead of p
222 | 		case "$count" in
223 | 		"") # no upstream
224 | 			upstream="" ;;
225 | 		"0	0") # equal to upstream
226 | 			upstream="|u=" ;;
227 | 		"0	"*) # ahead of upstream
228 | 			upstream="|u+${count#0	}" ;;
229 | 		*"	0") # behind upstream
230 | 			upstream="|u-${count%	0}" ;;
231 | 		*)	    # diverged from upstream
232 | 			upstream="|u+${count#*	}-${count%	*}" ;;
233 | 		esac
234 | 		if [[ -n "$count" && -n "$name" ]]; then
235 | 			__git_ps1_upstream_name=$(git rev-parse \
236 | 				--abbrev-ref "$upstream_type" 2>/dev/null)
237 | 			if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
238 | 				upstream="$upstream \${__git_ps1_upstream_name}"
239 | 			else
240 | 				upstream="$upstream ${__git_ps1_upstream_name}"
241 | 				# not needed anymore; keep user's
242 | 				# environment clean
243 | 				unset __git_ps1_upstream_name
244 | 			fi
245 | 		fi
246 | 	fi
247 | 
248 | }
249 | 
250 | # Helper function that is meant to be called from __git_ps1.  It
251 | # injects color codes into the appropriate gitstring variables used
252 | # to build a gitstring. Colored variables are responsible for clearing
253 | # their own color.
254 | __git_ps1_colorize_gitstring ()
255 | {
256 | 	if [[ -n ${ZSH_VERSION-} ]]; then
257 | 		local c_red='%F{red}'
258 | 		local c_green='%F{green}'
259 | 		local c_lblue='%F{blue}'
260 | 		local c_clear='%f'
261 | 	else
262 | 		# Using \[ and \] around colors is necessary to prevent
263 | 		# issues with command line editing/browsing/completion!
264 | 		local c_red='\[\e[31m\]'
265 | 		local c_green='\[\e[32m\]'
266 | 		local c_lblue='\[\e[1;34m\]'
267 | 		local c_clear='\[\e[0m\]'
268 | 	fi
269 | 	local bad_color=$c_red
270 | 	local ok_color=$c_green
271 | 	local flags_color="$c_lblue"
272 | 
273 | 	local branch_color=""
274 | 	if [ $detached = no ]; then
275 | 		branch_color="$ok_color"
276 | 	else
277 | 		branch_color="$bad_color"
278 | 	fi
279 | 	if [ -n "$c" ]; then
280 | 		c="$branch_color$c$c_clear"
281 | 	fi
282 | 	b="$branch_color$b$c_clear"
283 | 
284 | 	if [ -n "$w" ]; then
285 | 		w="$bad_color$w$c_clear"
286 | 	fi
287 | 	if [ -n "$i" ]; then
288 | 		i="$ok_color$i$c_clear"
289 | 	fi
290 | 	if [ -n "$s" ]; then
291 | 		s="$flags_color$s$c_clear"
292 | 	fi
293 | 	if [ -n "$u" ]; then
294 | 		u="$bad_color$u$c_clear"
295 | 	fi
296 | }
297 | 
298 | # Helper function to read the first line of a file into a variable.
299 | # __git_eread requires 2 arguments, the file path and the name of the
300 | # variable, in that order.
301 | __git_eread ()
302 | {
303 | 	test -r "$1" && IFS=$'\r\n' read "$2" <"$1"
304 | }
305 | 
306 | # see if a cherry-pick or revert is in progress, if the user has committed a
307 | # conflict resolution with 'git commit' in the middle of a sequence of picks or
308 | # reverts then CHERRY_PICK_HEAD/REVERT_HEAD will not exist so we have to read
309 | # the todo file.
310 | __git_sequencer_status ()
311 | {
312 | 	local todo
313 | 	if test -f "$g/CHERRY_PICK_HEAD"
314 | 	then
315 | 		r="|CHERRY-PICKING"
316 | 		return 0;
317 | 	elif test -f "$g/REVERT_HEAD"
318 | 	then
319 | 		r="|REVERTING"
320 | 		return 0;
321 | 	elif __git_eread "$g/sequencer/todo" todo
322 | 	then
323 | 		case "$todo" in
324 | 		p[\ \	]|pick[\ \	]*)
325 | 			r="|CHERRY-PICKING"
326 | 			return 0
327 | 		;;
328 | 		revert[\ \	]*)
329 | 			r="|REVERTING"
330 | 			return 0
331 | 		;;
332 | 		esac
333 | 	fi
334 | 	return 1
335 | }
336 | 
337 | # __git_ps1 accepts 0 or 1 arguments (i.e., format string)
338 | # when called from PS1 using command substitution
339 | # in this mode it prints text to add to bash PS1 prompt (includes branch name)
340 | #
341 | # __git_ps1 requires 2 or 3 arguments when called from PROMPT_COMMAND (pc)
342 | # in that case it _sets_ PS1. The arguments are parts of a PS1 string.
343 | # when two arguments are given, the first is prepended and the second appended
344 | # to the state string when assigned to PS1.
345 | # The optional third parameter will be used as printf format string to further
346 | # customize the output of the git-status string.
347 | # In this mode you can request colored hints using GIT_PS1_SHOWCOLORHINTS=true
348 | __git_ps1 ()
349 | {
350 | 	# preserve exit status
351 | 	local exit=$?
352 | 	local pcmode=no
353 | 	local detached=no
354 | 	local ps1pc_start='\u@\h:\w '
355 | 	local ps1pc_end='\$ '
356 | 	local printf_format=' (%s)'
357 | 
358 | 	case "$#" in
359 | 		2|3)	pcmode=yes
360 | 			ps1pc_start="$1"
361 | 			ps1pc_end="$2"
362 | 			printf_format="${3:-$printf_format}"
363 | 			# set PS1 to a plain prompt so that we can
364 | 			# simply return early if the prompt should not
365 | 			# be decorated
366 | 			PS1="$ps1pc_start$ps1pc_end"
367 | 		;;
368 | 		0|1)	printf_format="${1:-$printf_format}"
369 | 		;;
370 | 		*)	return $exit
371 | 		;;
372 | 	esac
373 | 
374 | 	# ps1_expanded:  This variable is set to 'yes' if the shell
375 | 	# subjects the value of PS1 to parameter expansion:
376 | 	#
377 | 	#   * bash does unless the promptvars option is disabled
378 | 	#   * zsh does not unless the PROMPT_SUBST option is set
379 | 	#   * POSIX shells always do
380 | 	#
381 | 	# If the shell would expand the contents of PS1 when drawing
382 | 	# the prompt, a raw ref name must not be included in PS1.
383 | 	# This protects the user from arbitrary code execution via
384 | 	# specially crafted ref names.  For example, a ref named
385 | 	# 'refs/heads/$(IFS=_;cmd=sudo_rm_-rf_/;$cmd)' might cause the
386 | 	# shell to execute 'sudo rm -rf /' when the prompt is drawn.
387 | 	#
388 | 	# Instead, the ref name should be placed in a separate global
389 | 	# variable (in the __git_ps1_* namespace to avoid colliding
390 | 	# with the user's environment) and that variable should be
391 | 	# referenced from PS1.  For example:
392 | 	#
393 | 	#     __git_ps1_foo=$(do_something_to_get_ref_name)
394 | 	#     PS1="...stuff...\${__git_ps1_foo}...stuff..."
395 | 	#
396 | 	# If the shell does not expand the contents of PS1, the raw
397 | 	# ref name must be included in PS1.
398 | 	#
399 | 	# The value of this variable is only relevant when in pcmode.
400 | 	#
401 | 	# Assume that the shell follows the POSIX specification and
402 | 	# expands PS1 unless determined otherwise.  (This is more
403 | 	# likely to be correct if the user has a non-bash, non-zsh
404 | 	# shell and safer than the alternative if the assumption is
405 | 	# incorrect.)
406 | 	#
407 | 	local ps1_expanded=yes
408 | 	[ -z "${ZSH_VERSION-}" ] || [[ -o PROMPT_SUBST ]] || ps1_expanded=no
409 | 	[ -z "${BASH_VERSION-}" ] || shopt -q promptvars || ps1_expanded=no
410 | 
411 | 	local repo_info rev_parse_exit_code
412 | 	repo_info="$(git rev-parse --git-dir --is-inside-git-dir \
413 | 		--is-bare-repository --is-inside-work-tree \
414 | 		--short HEAD 2>/dev/null)"
415 | 	rev_parse_exit_code="$?"
416 | 
417 | 	if [ -z "$repo_info" ]; then
418 | 		return $exit
419 | 	fi
420 | 
421 | 	local short_sha=""
422 | 	if [ "$rev_parse_exit_code" = "0" ]; then
423 | 		short_sha="${repo_info##*$'\n'}"
424 | 		repo_info="${repo_info%$'\n'*}"
425 | 	fi
426 | 	local inside_worktree="${repo_info##*$'\n'}"
427 | 	repo_info="${repo_info%$'\n'*}"
428 | 	local bare_repo="${repo_info##*$'\n'}"
429 | 	repo_info="${repo_info%$'\n'*}"
430 | 	local inside_gitdir="${repo_info##*$'\n'}"
431 | 	local g="${repo_info%$'\n'*}"
432 | 
433 | 	if [ "true" = "$inside_worktree" ] &&
434 | 	   [ -n "${GIT_PS1_HIDE_IF_PWD_IGNORED-}" ] &&
435 | 	   [ "$(git config --bool bash.hideIfPwdIgnored)" != "false" ] &&
436 | 	   git check-ignore -q .
437 | 	then
438 | 		return $exit
439 | 	fi
440 | 
441 | 	local sparse=""
442 | 	if [ -z "${GIT_PS1_COMPRESSSPARSESTATE-}" ] &&
443 | 	   [ -z "${GIT_PS1_OMITSPARSESTATE-}" ] &&
444 | 	   [ "$(git config --bool core.sparseCheckout)" = "true" ]; then
445 | 		sparse="|SPARSE"
446 | 	fi
447 | 
448 | 	local r=""
449 | 	local b=""
450 | 	local step=""
451 | 	local total=""
452 | 	if [ -d "$g/rebase-merge" ]; then
453 | 		__git_eread "$g/rebase-merge/head-name" b
454 | 		__git_eread "$g/rebase-merge/msgnum" step
455 | 		__git_eread "$g/rebase-merge/end" total
456 | 		r="|REBASE"
457 | 	else
458 | 		if [ -d "$g/rebase-apply" ]; then
459 | 			__git_eread "$g/rebase-apply/next" step
460 | 			__git_eread "$g/rebase-apply/last" total
461 | 			if [ -f "$g/rebase-apply/rebasing" ]; then
462 | 				__git_eread "$g/rebase-apply/head-name" b
463 | 				r="|REBASE"
464 | 			elif [ -f "$g/rebase-apply/applying" ]; then
465 | 				r="|AM"
466 | 			else
467 | 				r="|AM/REBASE"
468 | 			fi
469 | 		elif [ -f "$g/MERGE_HEAD" ]; then
470 | 			r="|MERGING"
471 | 		elif __git_sequencer_status; then
472 | 			:
473 | 		elif [ -f "$g/BISECT_LOG" ]; then
474 | 			r="|BISECTING"
475 | 		fi
476 | 
477 | 		if [ -n "$b" ]; then
478 | 			:
479 | 		elif [ -h "$g/HEAD" ]; then
480 | 			# symlink symbolic ref
481 | 			b="$(git symbolic-ref HEAD 2>/dev/null)"
482 | 		else
483 | 			local head=""
484 | 			if ! __git_eread "$g/HEAD" head; then
485 | 				return $exit
486 | 			fi
487 | 			# is it a symbolic ref?
488 | 			b="${head#ref: }"
489 | 			if [ "$head" = "$b" ]; then
490 | 				detached=yes
491 | 				b="$(
492 | 				case "${GIT_PS1_DESCRIBE_STYLE-}" in
493 | 				(contains)
494 | 					git describe --contains HEAD ;;
495 | 				(branch)
496 | 					git describe --contains --all HEAD ;;
497 | 				(tag)
498 | 					git describe --tags HEAD ;;
499 | 				(describe)
500 | 					git describe HEAD ;;
501 | 				(* | default)
502 | 					git describe --tags --exact-match HEAD ;;
503 | 				esac 2>/dev/null)" ||
504 | 
505 | 				b="$short_sha..."
506 | 				b="($b)"
507 | 			fi
508 | 		fi
509 | 	fi
510 | 
511 | 	if [ -n "$step" ] && [ -n "$total" ]; then
512 | 		r="$r $step/$total"
513 | 	fi
514 | 
515 | 	local conflict="" # state indicator for unresolved conflicts
516 | 	if [[ "${GIT_PS1_SHOWCONFLICTSTATE}" == "yes" ]] &&
517 | 	   [[ $(git ls-files --unmerged 2>/dev/null) ]]; then
518 | 		conflict="|CONFLICT"
519 | 	fi
520 | 
521 | 	local w=""
522 | 	local i=""
523 | 	local s=""
524 | 	local u=""
525 | 	local h=""
526 | 	local c=""
527 | 	local p="" # short version of upstream state indicator
528 | 	local upstream="" # verbose version of upstream state indicator
529 | 
530 | 	if [ "true" = "$inside_gitdir" ]; then
531 | 		if [ "true" = "$bare_repo" ]; then
532 | 			c="BARE:"
533 | 		else
534 | 			b="GIT_DIR!"
535 | 		fi
536 | 	elif [ "true" = "$inside_worktree" ]; then
537 | 		if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ] &&
538 | 		   [ "$(git config --bool bash.showDirtyState)" != "false" ]
539 | 		then
540 | 			git diff --no-ext-diff --quiet || w="*"
541 | 			git diff --no-ext-diff --cached --quiet || i="+"
542 | 			if [ -z "$short_sha" ] && [ -z "$i" ]; then
543 | 				i="#"
544 | 			fi
545 | 		fi
546 | 		if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ] &&
547 | 		   git rev-parse --verify --quiet refs/stash >/dev/null
548 | 		then
549 | 			s="$"
550 | 		fi
551 | 
552 | 		if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ] &&
553 | 		   [ "$(git config --bool bash.showUntrackedFiles)" != "false" ] &&
554 | 		   git ls-files --others --exclude-standard --directory --no-empty-directory --error-unmatch -- ':/*' >/dev/null 2>/dev/null
555 | 		then
556 | 			u="%${ZSH_VERSION+%}"
557 | 		fi
558 | 
559 | 		if [ -n "${GIT_PS1_COMPRESSSPARSESTATE-}" ] &&
560 | 		   [ "$(git config --bool core.sparseCheckout)" = "true" ]; then
561 | 			h="?"
562 | 		fi
563 | 
564 | 		if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
565 | 			__git_ps1_show_upstream
566 | 		fi
567 | 	fi
568 | 
569 | 	local z="${GIT_PS1_STATESEPARATOR-" "}"
570 | 
571 | 	b=${b##refs/heads/}
572 | 	if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
573 | 		__git_ps1_branch_name=$b
574 | 		b="\${__git_ps1_branch_name}"
575 | 	fi
576 | 
577 | 	# NO color option unless in PROMPT_COMMAND mode or it's Zsh
578 | 	if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then
579 | 		if [ $pcmode = yes ] || [ -n "${ZSH_VERSION-}" ]; then
580 | 			__git_ps1_colorize_gitstring
581 | 		fi
582 | 	fi
583 | 
584 | 	local f="$h$w$i$s$u$p"
585 | 	local gitstring="$c$b${f:+$z$f}${sparse}$r${upstream}${conflict}"
586 | 
587 | 	if [ $pcmode = yes ]; then
588 | 		if [ "${__git_printf_supports_v-}" != yes ]; then
589 | 			gitstring=$(printf -- "$printf_format" "$gitstring")
590 | 		else
591 | 			printf -v gitstring -- "$printf_format" "$gitstring"
592 | 		fi
593 | 		PS1="$ps1pc_start$gitstring$ps1pc_end"
594 | 	else
595 | 		printf -- "$printf_format" "$gitstring"
596 | 	fi
597 | 
598 | 	return $exit
599 | }
600 | 


--------------------------------------------------------------------------------
/vim/autoload/plug.vim:
--------------------------------------------------------------------------------
   1 | " vim-plug: Vim plugin manager
   2 | " ============================
   3 | "
   4 | " Download plug.vim and put it in ~/.vim/autoload
   5 | "
   6 | "   curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
   7 | "     https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
   8 | "
   9 | " Edit your .vimrc
  10 | "
  11 | "   call plug#begin('~/.vim/plugged')
  12 | "
  13 | "   " Make sure you use single quotes
  14 | "
  15 | "   " Shorthand notation; fetches https://github.com/junegunn/vim-easy-align
  16 | "   Plug 'junegunn/vim-easy-align'
  17 | "
  18 | "   " Any valid git URL is allowed
  19 | "   Plug 'https://github.com/junegunn/vim-github-dashboard.git'
  20 | "
  21 | "   " Multiple Plug commands can be written in a single line using | separators
  22 | "   Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets'
  23 | "
  24 | "   " On-demand loading
  25 | "   Plug 'preservim/nerdtree', { 'on': 'NERDTreeToggle' }
  26 | "   Plug 'tpope/vim-fireplace', { 'for': 'clojure' }
  27 | "
  28 | "   " Using a non-default branch
  29 | "   Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' }
  30 | "
  31 | "   " Using a tagged release; wildcard allowed (requires git 1.9.2 or above)
  32 | "   Plug 'fatih/vim-go', { 'tag': '*' }
  33 | "
  34 | "   " Plugin options
  35 | "   Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' }
  36 | "
  37 | "   " Plugin outside ~/.vim/plugged with post-update hook
  38 | "   Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
  39 | "
  40 | "   " Unmanaged plugin (manually installed and updated)
  41 | "   Plug '~/my-prototype-plugin'
  42 | "
  43 | "   " Initialize plugin system
  44 | "   call plug#end()
  45 | "
  46 | " Then reload .vimrc and :PlugInstall to install plugins.
  47 | "
  48 | " Plug options:
  49 | "
  50 | "| Option                  | Description                                      |
  51 | "| ----------------------- | ------------------------------------------------ |
  52 | "| `branch`/`tag`/`commit` | Branch/tag/commit of the repository to use       |
  53 | "| `rtp`                   | Subdirectory that contains Vim plugin            |
  54 | "| `dir`                   | Custom directory for the plugin                  |
  55 | "| `as`                    | Use different name for the plugin                |
  56 | "| `do`                    | Post-update hook (string or funcref)             |
  57 | "| `on`                    | On-demand loading: Commands or ``-mappings |
  58 | "| `for`                   | On-demand loading: File types                    |
  59 | "| `frozen`                | Do not update unless explicitly specified        |
  60 | "
  61 | " More information: https://github.com/junegunn/vim-plug
  62 | "
  63 | "
  64 | " Copyright (c) 2017 Junegunn Choi
  65 | "
  66 | " MIT License
  67 | "
  68 | " Permission is hereby granted, free of charge, to any person obtaining
  69 | " a copy of this software and associated documentation files (the
  70 | " "Software"), to deal in the Software without restriction, including
  71 | " without limitation the rights to use, copy, modify, merge, publish,
  72 | " distribute, sublicense, and/or sell copies of the Software, and to
  73 | " permit persons to whom the Software is furnished to do so, subject to
  74 | " the following conditions:
  75 | "
  76 | " The above copyright notice and this permission notice shall be
  77 | " included in all copies or substantial portions of the Software.
  78 | "
  79 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  80 | " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  81 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  82 | " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  83 | " LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  84 | " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  85 | " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  86 | 
  87 | if exists('g:loaded_plug')
  88 |   finish
  89 | endif
  90 | let g:loaded_plug = 1
  91 | 
  92 | let s:cpo_save = &cpo
  93 | set cpo&vim
  94 | 
  95 | let s:plug_src = 'https://github.com/junegunn/vim-plug.git'
  96 | let s:plug_tab = get(s:, 'plug_tab', -1)
  97 | let s:plug_buf = get(s:, 'plug_buf', -1)
  98 | let s:mac_gui = has('gui_macvim') && has('gui_running')
  99 | let s:is_win = has('win32')
 100 | let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win)
 101 | let s:vim8 = has('patch-8.0.0039') && exists('*job_start')
 102 | if s:is_win && &shellslash
 103 |   set noshellslash
 104 |   let s:me = resolve(expand(':p'))
 105 |   set shellslash
 106 | else
 107 |   let s:me = resolve(expand(':p'))
 108 | endif
 109 | let s:base_spec = { 'branch': '', 'frozen': 0 }
 110 | let s:TYPE = {
 111 | \   'string':  type(''),
 112 | \   'list':    type([]),
 113 | \   'dict':    type({}),
 114 | \   'funcref': type(function('call'))
 115 | \ }
 116 | let s:loaded = get(s:, 'loaded', {})
 117 | let s:triggers = get(s:, 'triggers', {})
 118 | 
 119 | function! s:is_powershell(shell)
 120 |   return a:shell =~# 'powershell\(\.exe\)\?$' || a:shell =~# 'pwsh\(\.exe\)\?$'
 121 | endfunction
 122 | 
 123 | function! s:isabsolute(dir) abort
 124 |   return a:dir =~# '^/' || (has('win32') && a:dir =~? '^\%(\\\|[A-Z]:\)')
 125 | endfunction
 126 | 
 127 | function! s:git_dir(dir) abort
 128 |   let gitdir = s:trim(a:dir) . '/.git'
 129 |   if isdirectory(gitdir)
 130 |     return gitdir
 131 |   endif
 132 |   if !filereadable(gitdir)
 133 |     return ''
 134 |   endif
 135 |   let gitdir = matchstr(get(readfile(gitdir), 0, ''), '^gitdir: \zs.*')
 136 |   if len(gitdir) && !s:isabsolute(gitdir)
 137 |     let gitdir = a:dir . '/' . gitdir
 138 |   endif
 139 |   return isdirectory(gitdir) ? gitdir : ''
 140 | endfunction
 141 | 
 142 | function! s:git_origin_url(dir) abort
 143 |   let gitdir = s:git_dir(a:dir)
 144 |   let config = gitdir . '/config'
 145 |   if empty(gitdir) || !filereadable(config)
 146 |     return ''
 147 |   endif
 148 |   return matchstr(join(readfile(config)), '\[remote "origin"\].\{-}url\s*=\s*\zs\S*\ze')
 149 | endfunction
 150 | 
 151 | function! s:git_revision(dir) abort
 152 |   let gitdir = s:git_dir(a:dir)
 153 |   let head = gitdir . '/HEAD'
 154 |   if empty(gitdir) || !filereadable(head)
 155 |     return ''
 156 |   endif
 157 | 
 158 |   let line = get(readfile(head), 0, '')
 159 |   let ref = matchstr(line, '^ref: \zs.*')
 160 |   if empty(ref)
 161 |     return line
 162 |   endif
 163 | 
 164 |   if filereadable(gitdir . '/' . ref)
 165 |     return get(readfile(gitdir . '/' . ref), 0, '')
 166 |   endif
 167 | 
 168 |   if filereadable(gitdir . '/packed-refs')
 169 |     for line in readfile(gitdir . '/packed-refs')
 170 |       if line =~# ' ' . ref
 171 |         return matchstr(line, '^[0-9a-f]*')
 172 |       endif
 173 |     endfor
 174 |   endif
 175 | 
 176 |   return ''
 177 | endfunction
 178 | 
 179 | function! s:git_local_branch(dir) abort
 180 |   let gitdir = s:git_dir(a:dir)
 181 |   let head = gitdir . '/HEAD'
 182 |   if empty(gitdir) || !filereadable(head)
 183 |     return ''
 184 |   endif
 185 |   let branch = matchstr(get(readfile(head), 0, ''), '^ref: refs/heads/\zs.*')
 186 |   return len(branch) ? branch : 'HEAD'
 187 | endfunction
 188 | 
 189 | function! s:git_origin_branch(spec)
 190 |   if len(a:spec.branch)
 191 |     return a:spec.branch
 192 |   endif
 193 | 
 194 |   " The file may not be present if this is a local repository
 195 |   let gitdir = s:git_dir(a:spec.dir)
 196 |   let origin_head = gitdir.'/refs/remotes/origin/HEAD'
 197 |   if len(gitdir) && filereadable(origin_head)
 198 |     return matchstr(get(readfile(origin_head), 0, ''),
 199 |                   \ '^ref: refs/remotes/origin/\zs.*')
 200 |   endif
 201 | 
 202 |   " The command may not return the name of a branch in detached HEAD state
 203 |   let result = s:lines(s:system('git symbolic-ref --short HEAD', a:spec.dir))
 204 |   return v:shell_error ? '' : result[-1]
 205 | endfunction
 206 | 
 207 | if s:is_win
 208 |   function! s:plug_call(fn, ...)
 209 |     let shellslash = &shellslash
 210 |     try
 211 |       set noshellslash
 212 |       return call(a:fn, a:000)
 213 |     finally
 214 |       let &shellslash = shellslash
 215 |     endtry
 216 |   endfunction
 217 | else
 218 |   function! s:plug_call(fn, ...)
 219 |     return call(a:fn, a:000)
 220 |   endfunction
 221 | endif
 222 | 
 223 | function! s:plug_getcwd()
 224 |   return s:plug_call('getcwd')
 225 | endfunction
 226 | 
 227 | function! s:plug_fnamemodify(fname, mods)
 228 |   return s:plug_call('fnamemodify', a:fname, a:mods)
 229 | endfunction
 230 | 
 231 | function! s:plug_expand(fmt)
 232 |   return s:plug_call('expand', a:fmt, 1)
 233 | endfunction
 234 | 
 235 | function! s:plug_tempname()
 236 |   return s:plug_call('tempname')
 237 | endfunction
 238 | 
 239 | function! plug#begin(...)
 240 |   if a:0 > 0
 241 |     let s:plug_home_org = a:1
 242 |     let home = s:path(s:plug_fnamemodify(s:plug_expand(a:1), ':p'))
 243 |   elseif exists('g:plug_home')
 244 |     let home = s:path(g:plug_home)
 245 |   elseif has('nvim')
 246 |     let home = stdpath('data') . '/plugged'
 247 |   elseif !empty(&rtp)
 248 |     let home = s:path(split(&rtp, ',')[0]) . '/plugged'
 249 |   else
 250 |     return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
 251 |   endif
 252 |   if s:plug_fnamemodify(home, ':t') ==# 'plugin' && s:plug_fnamemodify(home, ':h') ==# s:first_rtp
 253 |     return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.')
 254 |   endif
 255 | 
 256 |   let g:plug_home = home
 257 |   let g:plugs = {}
 258 |   let g:plugs_order = []
 259 |   let s:triggers = {}
 260 | 
 261 |   call s:define_commands()
 262 |   return 1
 263 | endfunction
 264 | 
 265 | function! s:define_commands()
 266 |   command! -nargs=+ -bar Plug call plug#()
 267 |   if !executable('git')
 268 |     return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.')
 269 |   endif
 270 |   if has('win32')
 271 |   \ && &shellslash
 272 |   \ && (&shell =~# 'cmd\(\.exe\)\?$' || s:is_powershell(&shell))
 273 |     return s:err('vim-plug does not support shell, ' . &shell . ', when shellslash is set.')
 274 |   endif
 275 |   if !has('nvim')
 276 |     \ && (has('win32') || has('win32unix'))
 277 |     \ && !has('multi_byte')
 278 |     return s:err('Vim needs +multi_byte feature on Windows to run shell commands. Enable +iconv for best results.')
 279 |   endif
 280 |   command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(0, [])
 281 |   command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate  call s:update(0, [])
 282 |   command! -nargs=0 -bar -bang PlugClean call s:clean(0)
 283 |   command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
 284 |   command! -nargs=0 -bar PlugStatus  call s:status()
 285 |   command! -nargs=0 -bar PlugDiff    call s:diff()
 286 |   command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(0, )
 287 | endfunction
 288 | 
 289 | function! s:to_a(v)
 290 |   return type(a:v) == s:TYPE.list ? a:v : [a:v]
 291 | endfunction
 292 | 
 293 | function! s:to_s(v)
 294 |   return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
 295 | endfunction
 296 | 
 297 | function! s:glob(from, pattern)
 298 |   return s:lines(globpath(a:from, a:pattern))
 299 | endfunction
 300 | 
 301 | function! s:source(from, ...)
 302 |   let found = 0
 303 |   for pattern in a:000
 304 |     for vim in s:glob(a:from, pattern)
 305 |       execute 'source' s:esc(vim)
 306 |       let found = 1
 307 |     endfor
 308 |   endfor
 309 |   return found
 310 | endfunction
 311 | 
 312 | function! s:assoc(dict, key, val)
 313 |   let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
 314 | endfunction
 315 | 
 316 | function! s:ask(message, ...)
 317 |   call inputsave()
 318 |   echohl WarningMsg
 319 |   let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) '))
 320 |   echohl None
 321 |   call inputrestore()
 322 |   echo "\r"
 323 |   return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0
 324 | endfunction
 325 | 
 326 | function! s:ask_no_interrupt(...)
 327 |   try
 328 |     return call('s:ask', a:000)
 329 |   catch
 330 |     return 0
 331 |   endtry
 332 | endfunction
 333 | 
 334 | function! s:lazy(plug, opt)
 335 |   return has_key(a:plug, a:opt) &&
 336 |         \ (empty(s:to_a(a:plug[a:opt]))         ||
 337 |         \  !isdirectory(a:plug.dir)             ||
 338 |         \  len(s:glob(s:rtp(a:plug), 'plugin')) ||
 339 |         \  len(s:glob(s:rtp(a:plug), 'after/plugin')))
 340 | endfunction
 341 | 
 342 | function! plug#end()
 343 |   if !exists('g:plugs')
 344 |     return s:err('plug#end() called without calling plug#begin() first')
 345 |   endif
 346 | 
 347 |   if exists('#PlugLOD')
 348 |     augroup PlugLOD
 349 |       autocmd!
 350 |     augroup END
 351 |     augroup! PlugLOD
 352 |   endif
 353 |   let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
 354 | 
 355 |   if get(g:, 'did_load_filetypes', 0)
 356 |     filetype off
 357 |   endif
 358 |   for name in g:plugs_order
 359 |     if !has_key(g:plugs, name)
 360 |       continue
 361 |     endif
 362 |     let plug = g:plugs[name]
 363 |     if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for')
 364 |       let s:loaded[name] = 1
 365 |       continue
 366 |     endif
 367 | 
 368 |     if has_key(plug, 'on')
 369 |       let s:triggers[name] = { 'map': [], 'cmd': [] }
 370 |       for cmd in s:to_a(plug.on)
 371 |         if cmd =~? '^.\+'
 372 |           if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
 373 |             call s:assoc(lod.map, cmd, name)
 374 |           endif
 375 |           call add(s:triggers[name].map, cmd)
 376 |         elseif cmd =~# '^[A-Z]'
 377 |           let cmd = substitute(cmd, '!*$', '', '')
 378 |           if exists(':'.cmd) != 2
 379 |             call s:assoc(lod.cmd, cmd, name)
 380 |           endif
 381 |           call add(s:triggers[name].cmd, cmd)
 382 |         else
 383 |           call s:err('Invalid `on` option: '.cmd.
 384 |           \ '. Should start with an uppercase letter or ``.')
 385 |         endif
 386 |       endfor
 387 |     endif
 388 | 
 389 |     if has_key(plug, 'for')
 390 |       let types = s:to_a(plug.for)
 391 |       if !empty(types)
 392 |         augroup filetypedetect
 393 |         call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
 394 |         augroup END
 395 |       endif
 396 |       for type in types
 397 |         call s:assoc(lod.ft, type, name)
 398 |       endfor
 399 |     endif
 400 |   endfor
 401 | 
 402 |   for [cmd, names] in items(lod.cmd)
 403 |     execute printf(
 404 |     \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , %s)',
 405 |     \ cmd, string(cmd), string(names))
 406 |   endfor
 407 | 
 408 |   for [map, names] in items(lod.map)
 409 |     for [mode, map_prefix, key_prefix] in
 410 |           \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
 411 |       execute printf(
 412 |       \ '%snoremap  %s %s:call lod_map(%s, %s, %s, "%s")',
 413 |       \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix)
 414 |     endfor
 415 |   endfor
 416 | 
 417 |   for [ft, names] in items(lod.ft)
 418 |     augroup PlugLOD
 419 |       execute printf('autocmd FileType %s call lod_ft(%s, %s)',
 420 |             \ ft, string(ft), string(names))
 421 |     augroup END
 422 |   endfor
 423 | 
 424 |   call s:reorg_rtp()
 425 |   filetype plugin indent on
 426 |   if has('vim_starting')
 427 |     if has('syntax') && !exists('g:syntax_on')
 428 |       syntax enable
 429 |     end
 430 |   else
 431 |     call s:reload_plugins()
 432 |   endif
 433 | endfunction
 434 | 
 435 | function! s:loaded_names()
 436 |   return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
 437 | endfunction
 438 | 
 439 | function! s:load_plugin(spec)
 440 |   call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
 441 | endfunction
 442 | 
 443 | function! s:reload_plugins()
 444 |   for name in s:loaded_names()
 445 |     call s:load_plugin(g:plugs[name])
 446 |   endfor
 447 | endfunction
 448 | 
 449 | function! s:trim(str)
 450 |   return substitute(a:str, '[\/]\+$', '', '')
 451 | endfunction
 452 | 
 453 | function! s:version_requirement(val, min)
 454 |   for idx in range(0, len(a:min) - 1)
 455 |     let v = get(a:val, idx, 0)
 456 |     if     v < a:min[idx] | return 0
 457 |     elseif v > a:min[idx] | return 1
 458 |     endif
 459 |   endfor
 460 |   return 1
 461 | endfunction
 462 | 
 463 | function! s:git_version_requirement(...)
 464 |   if !exists('s:git_version')
 465 |     let s:git_version = map(split(split(s:system(['git', '--version']))[2], '\.'), 'str2nr(v:val)')
 466 |   endif
 467 |   return s:version_requirement(s:git_version, a:000)
 468 | endfunction
 469 | 
 470 | function! s:progress_opt(base)
 471 |   return a:base && !s:is_win &&
 472 |         \ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
 473 | endfunction
 474 | 
 475 | function! s:rtp(spec)
 476 |   return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
 477 | endfunction
 478 | 
 479 | if s:is_win
 480 |   function! s:path(path)
 481 |     return s:trim(substitute(a:path, '/', '\', 'g'))
 482 |   endfunction
 483 | 
 484 |   function! s:dirpath(path)
 485 |     return s:path(a:path) . '\'
 486 |   endfunction
 487 | 
 488 |   function! s:is_local_plug(repo)
 489 |     return a:repo =~? '^[a-z]:\|^[%~]'
 490 |   endfunction
 491 | 
 492 |   " Copied from fzf
 493 |   function! s:wrap_cmds(cmds)
 494 |     let cmds = [
 495 |       \ '@echo off',
 496 |       \ 'setlocal enabledelayedexpansion']
 497 |     \ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
 498 |     \ + ['endlocal']
 499 |     if has('iconv')
 500 |       if !exists('s:codepage')
 501 |         let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
 502 |       endif
 503 |       return map(cmds, printf('iconv(v:val."\r", "%s", "cp%d")', &encoding, s:codepage))
 504 |     endif
 505 |     return map(cmds, 'v:val."\r"')
 506 |   endfunction
 507 | 
 508 |   function! s:batchfile(cmd)
 509 |     let batchfile = s:plug_tempname().'.bat'
 510 |     call writefile(s:wrap_cmds(a:cmd), batchfile)
 511 |     let cmd = plug#shellescape(batchfile, {'shell': &shell, 'script': 0})
 512 |     if s:is_powershell(&shell)
 513 |       let cmd = '& ' . cmd
 514 |     endif
 515 |     return [batchfile, cmd]
 516 |   endfunction
 517 | else
 518 |   function! s:path(path)
 519 |     return s:trim(a:path)
 520 |   endfunction
 521 | 
 522 |   function! s:dirpath(path)
 523 |     return substitute(a:path, '[/\\]*$', '/', '')
 524 |   endfunction
 525 | 
 526 |   function! s:is_local_plug(repo)
 527 |     return a:repo[0] =~ '[/$~]'
 528 |   endfunction
 529 | endif
 530 | 
 531 | function! s:err(msg)
 532 |   echohl ErrorMsg
 533 |   echom '[vim-plug] '.a:msg
 534 |   echohl None
 535 | endfunction
 536 | 
 537 | function! s:warn(cmd, msg)
 538 |   echohl WarningMsg
 539 |   execute a:cmd 'a:msg'
 540 |   echohl None
 541 | endfunction
 542 | 
 543 | function! s:esc(path)
 544 |   return escape(a:path, ' ')
 545 | endfunction
 546 | 
 547 | function! s:escrtp(path)
 548 |   return escape(a:path, ' ,')
 549 | endfunction
 550 | 
 551 | function! s:remove_rtp()
 552 |   for name in s:loaded_names()
 553 |     let rtp = s:rtp(g:plugs[name])
 554 |     execute 'set rtp-='.s:escrtp(rtp)
 555 |     let after = globpath(rtp, 'after')
 556 |     if isdirectory(after)
 557 |       execute 'set rtp-='.s:escrtp(after)
 558 |     endif
 559 |   endfor
 560 | endfunction
 561 | 
 562 | function! s:reorg_rtp()
 563 |   if !empty(s:first_rtp)
 564 |     execute 'set rtp-='.s:first_rtp
 565 |     execute 'set rtp-='.s:last_rtp
 566 |   endif
 567 | 
 568 |   " &rtp is modified from outside
 569 |   if exists('s:prtp') && s:prtp !=# &rtp
 570 |     call s:remove_rtp()
 571 |     unlet! s:middle
 572 |   endif
 573 | 
 574 |   let s:middle = get(s:, 'middle', &rtp)
 575 |   let rtps     = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
 576 |   let afters   = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)')
 577 |   let rtp      = join(map(rtps, 'escape(v:val, ",")'), ',')
 578 |                  \ . ','.s:middle.','
 579 |                  \ . join(map(afters, 'escape(v:val, ",")'), ',')
 580 |   let &rtp     = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
 581 |   let s:prtp   = &rtp
 582 | 
 583 |   if !empty(s:first_rtp)
 584 |     execute 'set rtp^='.s:first_rtp
 585 |     execute 'set rtp+='.s:last_rtp
 586 |   endif
 587 | endfunction
 588 | 
 589 | function! s:doautocmd(...)
 590 |   if exists('#'.join(a:000, '#'))
 591 |     execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '' : '') join(a:000)
 592 |   endif
 593 | endfunction
 594 | 
 595 | function! s:dobufread(names)
 596 |   for name in a:names
 597 |     let path = s:rtp(g:plugs[name])
 598 |     for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin']
 599 |       if len(finddir(dir, path))
 600 |         if exists('#BufRead')
 601 |           doautocmd BufRead
 602 |         endif
 603 |         return
 604 |       endif
 605 |     endfor
 606 |   endfor
 607 | endfunction
 608 | 
 609 | function! plug#load(...)
 610 |   if a:0 == 0
 611 |     return s:err('Argument missing: plugin name(s) required')
 612 |   endif
 613 |   if !exists('g:plugs')
 614 |     return s:err('plug#begin was not called')
 615 |   endif
 616 |   let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000
 617 |   let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)')
 618 |   if !empty(unknowns)
 619 |     let s = len(unknowns) > 1 ? 's' : ''
 620 |     return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
 621 |   end
 622 |   let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)')
 623 |   if !empty(unloaded)
 624 |     for name in unloaded
 625 |       call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
 626 |     endfor
 627 |     call s:dobufread(unloaded)
 628 |     return 1
 629 |   end
 630 |   return 0
 631 | endfunction
 632 | 
 633 | function! s:remove_triggers(name)
 634 |   if !has_key(s:triggers, a:name)
 635 |     return
 636 |   endif
 637 |   for cmd in s:triggers[a:name].cmd
 638 |     execute 'silent! delc' cmd
 639 |   endfor
 640 |   for map in s:triggers[a:name].map
 641 |     execute 'silent! unmap' map
 642 |     execute 'silent! iunmap' map
 643 |   endfor
 644 |   call remove(s:triggers, a:name)
 645 | endfunction
 646 | 
 647 | function! s:lod(names, types, ...)
 648 |   for name in a:names
 649 |     call s:remove_triggers(name)
 650 |     let s:loaded[name] = 1
 651 |   endfor
 652 |   call s:reorg_rtp()
 653 | 
 654 |   for name in a:names
 655 |     let rtp = s:rtp(g:plugs[name])
 656 |     for dir in a:types
 657 |       call s:source(rtp, dir.'/**/*.vim')
 658 |     endfor
 659 |     if a:0
 660 |       if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2))
 661 |         execute 'runtime' a:1
 662 |       endif
 663 |       call s:source(rtp, a:2)
 664 |     endif
 665 |     call s:doautocmd('User', name)
 666 |   endfor
 667 | endfunction
 668 | 
 669 | function! s:lod_ft(pat, names)
 670 |   let syn = 'syntax/'.a:pat.'.vim'
 671 |   call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn)
 672 |   execute 'autocmd! PlugLOD FileType' a:pat
 673 |   call s:doautocmd('filetypeplugin', 'FileType')
 674 |   call s:doautocmd('filetypeindent', 'FileType')
 675 | endfunction
 676 | 
 677 | function! s:lod_cmd(cmd, bang, l1, l2, args, names)
 678 |   call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
 679 |   call s:dobufread(a:names)
 680 |   execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
 681 | endfunction
 682 | 
 683 | function! s:lod_map(map, names, with_prefix, prefix)
 684 |   call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
 685 |   call s:dobufread(a:names)
 686 |   let extra = ''
 687 |   while 1
 688 |     let c = getchar(0)
 689 |     if c == 0
 690 |       break
 691 |     endif
 692 |     let extra .= nr2char(c)
 693 |   endwhile
 694 | 
 695 |   if a:with_prefix
 696 |     let prefix = v:count ? v:count : ''
 697 |     let prefix .= '"'.v:register.a:prefix
 698 |     if mode(1) == 'no'
 699 |       if v:operator == 'c'
 700 |         let prefix = "\" . prefix
 701 |       endif
 702 |       let prefix .= v:operator
 703 |     endif
 704 |     call feedkeys(prefix, 'n')
 705 |   endif
 706 |   call feedkeys(substitute(a:map, '^', "\", '') . extra)
 707 | endfunction
 708 | 
 709 | function! plug#(repo, ...)
 710 |   if a:0 > 1
 711 |     return s:err('Invalid number of arguments (1..2)')
 712 |   endif
 713 | 
 714 |   try
 715 |     let repo = s:trim(a:repo)
 716 |     let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec
 717 |     let name = get(opts, 'as', s:plug_fnamemodify(repo, ':t:s?\.git$??'))
 718 |     let spec = extend(s:infer_properties(name, repo), opts)
 719 |     if !has_key(g:plugs, name)
 720 |       call add(g:plugs_order, name)
 721 |     endif
 722 |     let g:plugs[name] = spec
 723 |     let s:loaded[name] = get(s:loaded, name, 0)
 724 |   catch
 725 |     return s:err(repo . ' ' . v:exception)
 726 |   endtry
 727 | endfunction
 728 | 
 729 | function! s:parse_options(arg)
 730 |   let opts = copy(s:base_spec)
 731 |   let type = type(a:arg)
 732 |   let opt_errfmt = 'Invalid argument for "%s" option of :Plug (expected: %s)'
 733 |   if type == s:TYPE.string
 734 |     if empty(a:arg)
 735 |       throw printf(opt_errfmt, 'tag', 'string')
 736 |     endif
 737 |     let opts.tag = a:arg
 738 |   elseif type == s:TYPE.dict
 739 |     for opt in ['branch', 'tag', 'commit', 'rtp', 'dir', 'as']
 740 |       if has_key(a:arg, opt)
 741 |       \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
 742 |         throw printf(opt_errfmt, opt, 'string')
 743 |       endif
 744 |     endfor
 745 |     for opt in ['on', 'for']
 746 |       if has_key(a:arg, opt)
 747 |       \ && type(a:arg[opt]) != s:TYPE.list
 748 |       \ && (type(a:arg[opt]) != s:TYPE.string || empty(a:arg[opt]))
 749 |         throw printf(opt_errfmt, opt, 'string or list')
 750 |       endif
 751 |     endfor
 752 |     if has_key(a:arg, 'do')
 753 |       \ && type(a:arg.do) != s:TYPE.funcref
 754 |       \ && (type(a:arg.do) != s:TYPE.string || empty(a:arg.do))
 755 |         throw printf(opt_errfmt, 'do', 'string or funcref')
 756 |     endif
 757 |     call extend(opts, a:arg)
 758 |     if has_key(opts, 'dir')
 759 |       let opts.dir = s:dirpath(s:plug_expand(opts.dir))
 760 |     endif
 761 |   else
 762 |     throw 'Invalid argument type (expected: string or dictionary)'
 763 |   endif
 764 |   return opts
 765 | endfunction
 766 | 
 767 | function! s:infer_properties(name, repo)
 768 |   let repo = a:repo
 769 |   if s:is_local_plug(repo)
 770 |     return { 'dir': s:dirpath(s:plug_expand(repo)) }
 771 |   else
 772 |     if repo =~ ':'
 773 |       let uri = repo
 774 |     else
 775 |       if repo !~ '/'
 776 |         throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo)
 777 |       endif
 778 |       let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
 779 |       let uri = printf(fmt, repo)
 780 |     endif
 781 |     return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri }
 782 |   endif
 783 | endfunction
 784 | 
 785 | function! s:install(force, names)
 786 |   call s:update_impl(0, a:force, a:names)
 787 | endfunction
 788 | 
 789 | function! s:update(force, names)
 790 |   call s:update_impl(1, a:force, a:names)
 791 | endfunction
 792 | 
 793 | function! plug#helptags()
 794 |   if !exists('g:plugs')
 795 |     return s:err('plug#begin was not called')
 796 |   endif
 797 |   for spec in values(g:plugs)
 798 |     let docd = join([s:rtp(spec), 'doc'], '/')
 799 |     if isdirectory(docd)
 800 |       silent! execute 'helptags' s:esc(docd)
 801 |     endif
 802 |   endfor
 803 |   return 1
 804 | endfunction
 805 | 
 806 | function! s:syntax()
 807 |   syntax clear
 808 |   syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
 809 |   syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
 810 |   syn match plugNumber /[0-9]\+[0-9.]*/ contained
 811 |   syn match plugBracket /[[\]]/ contained
 812 |   syn match plugX /x/ contained
 813 |   syn match plugDash /^-\{1}\ /
 814 |   syn match plugPlus /^+/
 815 |   syn match plugStar /^*/
 816 |   syn match plugMessage /\(^- \)\@<=.*/
 817 |   syn match plugName /\(^- \)\@<=[^ ]*:/
 818 |   syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/
 819 |   syn match plugTag /(tag: [^)]\+)/
 820 |   syn match plugInstall /\(^+ \)\@<=[^:]*/
 821 |   syn match plugUpdate /\(^* \)\@<=[^:]*/
 822 |   syn match plugCommit /^  \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag
 823 |   syn match plugEdge /^  \X\+$/
 824 |   syn match plugEdge /^  \X*/ contained nextgroup=plugSha
 825 |   syn match plugSha /[0-9a-f]\{7,9}/ contained
 826 |   syn match plugRelDate /([^)]*)$/ contained
 827 |   syn match plugNotLoaded /(not loaded)$/
 828 |   syn match plugError /^x.*/
 829 |   syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/
 830 |   syn match plugH2 /^.*:\n-\+$/
 831 |   syn match plugH2 /^-\{2,}/
 832 |   syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
 833 |   hi def link plug1       Title
 834 |   hi def link plug2       Repeat
 835 |   hi def link plugH2      Type
 836 |   hi def link plugX       Exception
 837 |   hi def link plugBracket Structure
 838 |   hi def link plugNumber  Number
 839 | 
 840 |   hi def link plugDash    Special
 841 |   hi def link plugPlus    Constant
 842 |   hi def link plugStar    Boolean
 843 | 
 844 |   hi def link plugMessage Function
 845 |   hi def link plugName    Label
 846 |   hi def link plugInstall Function
 847 |   hi def link plugUpdate  Type
 848 | 
 849 |   hi def link plugError   Error
 850 |   hi def link plugDeleted Ignore
 851 |   hi def link plugRelDate Comment
 852 |   hi def link plugEdge    PreProc
 853 |   hi def link plugSha     Identifier
 854 |   hi def link plugTag     Constant
 855 | 
 856 |   hi def link plugNotLoaded Comment
 857 | endfunction
 858 | 
 859 | function! s:lpad(str, len)
 860 |   return a:str . repeat(' ', a:len - len(a:str))
 861 | endfunction
 862 | 
 863 | function! s:lines(msg)
 864 |   return split(a:msg, "[\r\n]")
 865 | endfunction
 866 | 
 867 | function! s:lastline(msg)
 868 |   return get(s:lines(a:msg), -1, '')
 869 | endfunction
 870 | 
 871 | function! s:new_window()
 872 |   execute get(g:, 'plug_window', 'vertical topleft new')
 873 | endfunction
 874 | 
 875 | function! s:plug_window_exists()
 876 |   let buflist = tabpagebuflist(s:plug_tab)
 877 |   return !empty(buflist) && index(buflist, s:plug_buf) >= 0
 878 | endfunction
 879 | 
 880 | function! s:switch_in()
 881 |   if !s:plug_window_exists()
 882 |     return 0
 883 |   endif
 884 | 
 885 |   if winbufnr(0) != s:plug_buf
 886 |     let s:pos = [tabpagenr(), winnr(), winsaveview()]
 887 |     execute 'normal!' s:plug_tab.'gt'
 888 |     let winnr = bufwinnr(s:plug_buf)
 889 |     execute winnr.'wincmd w'
 890 |     call add(s:pos, winsaveview())
 891 |   else
 892 |     let s:pos = [winsaveview()]
 893 |   endif
 894 | 
 895 |   setlocal modifiable
 896 |   return 1
 897 | endfunction
 898 | 
 899 | function! s:switch_out(...)
 900 |   call winrestview(s:pos[-1])
 901 |   setlocal nomodifiable
 902 |   if a:0 > 0
 903 |     execute a:1
 904 |   endif
 905 | 
 906 |   if len(s:pos) > 1
 907 |     execute 'normal!' s:pos[0].'gt'
 908 |     execute s:pos[1] 'wincmd w'
 909 |     call winrestview(s:pos[2])
 910 |   endif
 911 | endfunction
 912 | 
 913 | function! s:finish_bindings()
 914 |   nnoremap   R  :call retry()
 915 |   nnoremap   D  :PlugDiff
 916 |   nnoremap   S  :PlugStatus
 917 |   nnoremap   U  :call status_update()
 918 |   xnoremap   U  :call status_update()
 919 |   nnoremap   ]] :silent! call section('')
 920 |   nnoremap   [[ :silent! call section('b')
 921 | endfunction
 922 | 
 923 | function! s:prepare(...)
 924 |   if empty(s:plug_getcwd())
 925 |     throw 'Invalid current working directory. Cannot proceed.'
 926 |   endif
 927 | 
 928 |   for evar in ['$GIT_DIR', '$GIT_WORK_TREE']
 929 |     if exists(evar)
 930 |       throw evar.' detected. Cannot proceed.'
 931 |     endif
 932 |   endfor
 933 | 
 934 |   call s:job_abort()
 935 |   if s:switch_in()
 936 |     if b:plug_preview == 1
 937 |       pc
 938 |     endif
 939 |     enew
 940 |   else
 941 |     call s:new_window()
 942 |   endif
 943 | 
 944 |   nnoremap   q :call close_pane()
 945 |   if a:0 == 0
 946 |     call s:finish_bindings()
 947 |   endif
 948 |   let b:plug_preview = -1
 949 |   let s:plug_tab = tabpagenr()
 950 |   let s:plug_buf = winbufnr(0)
 951 |   call s:assign_name()
 952 | 
 953 |   for k in ['', 'L', 'o', 'X', 'd', 'dd']
 954 |     execute 'silent! unmap ' k
 955 |   endfor
 956 |   setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell
 957 |   if exists('+colorcolumn')
 958 |     setlocal colorcolumn=
 959 |   endif
 960 |   setf vim-plug
 961 |   if exists('g:syntax_on')
 962 |     call s:syntax()
 963 |   endif
 964 | endfunction
 965 | 
 966 | function! s:close_pane()
 967 |   if b:plug_preview == 1
 968 |     pc
 969 |     let b:plug_preview = -1
 970 |   else
 971 |     bd
 972 |   endif
 973 | endfunction
 974 | 
 975 | function! s:assign_name()
 976 |   " Assign buffer name
 977 |   let prefix = '[Plugins]'
 978 |   let name   = prefix
 979 |   let idx    = 2
 980 |   while bufexists(name)
 981 |     let name = printf('%s (%s)', prefix, idx)
 982 |     let idx = idx + 1
 983 |   endwhile
 984 |   silent! execute 'f' fnameescape(name)
 985 | endfunction
 986 | 
 987 | function! s:chsh(swap)
 988 |   let prev = [&shell, &shellcmdflag, &shellredir]
 989 |   if !s:is_win
 990 |     set shell=sh
 991 |   endif
 992 |   if a:swap
 993 |     if s:is_powershell(&shell)
 994 |       let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s'
 995 |     elseif &shell =~# 'sh' || &shell =~# 'cmd\(\.exe\)\?$'
 996 |       set shellredir=>%s\ 2>&1
 997 |     endif
 998 |   endif
 999 |   return prev
1000 | endfunction
1001 | 
1002 | function! s:bang(cmd, ...)
1003 |   let batchfile = ''
1004 |   try
1005 |     let [sh, shellcmdflag, shrd] = s:chsh(a:0)
1006 |     " FIXME: Escaping is incomplete. We could use shellescape with eval,
1007 |     "        but it won't work on Windows.
1008 |     let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
1009 |     if s:is_win
1010 |       let [batchfile, cmd] = s:batchfile(cmd)
1011 |     endif
1012 |     let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%')
1013 |     execute "normal! :execute g:_plug_bang\\"
1014 |   finally
1015 |     unlet g:_plug_bang
1016 |     let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
1017 |     if s:is_win && filereadable(batchfile)
1018 |       call delete(batchfile)
1019 |     endif
1020 |   endtry
1021 |   return v:shell_error ? 'Exit status: ' . v:shell_error : ''
1022 | endfunction
1023 | 
1024 | function! s:regress_bar()
1025 |   let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '')
1026 |   call s:progress_bar(2, bar, len(bar))
1027 | endfunction
1028 | 
1029 | function! s:is_updated(dir)
1030 |   return !empty(s:system_chomp(['git', 'log', '--pretty=format:%h', 'HEAD...HEAD@{1}'], a:dir))
1031 | endfunction
1032 | 
1033 | function! s:do(pull, force, todo)
1034 |   for [name, spec] in items(a:todo)
1035 |     if !isdirectory(spec.dir)
1036 |       continue
1037 |     endif
1038 |     let installed = has_key(s:update.new, name)
1039 |     let updated = installed ? 0 :
1040 |       \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir))
1041 |     if a:force || installed || updated
1042 |       execute 'cd' s:esc(spec.dir)
1043 |       call append(3, '- Post-update hook for '. name .' ... ')
1044 |       let error = ''
1045 |       let type = type(spec.do)
1046 |       if type == s:TYPE.string
1047 |         if spec.do[0] == ':'
1048 |           if !get(s:loaded, name, 0)
1049 |             let s:loaded[name] = 1
1050 |             call s:reorg_rtp()
1051 |           endif
1052 |           call s:load_plugin(spec)
1053 |           try
1054 |             execute spec.do[1:]
1055 |           catch
1056 |             let error = v:exception
1057 |           endtry
1058 |           if !s:plug_window_exists()
1059 |             cd -
1060 |             throw 'Warning: vim-plug was terminated by the post-update hook of '.name
1061 |           endif
1062 |         else
1063 |           let error = s:bang(spec.do)
1064 |         endif
1065 |       elseif type == s:TYPE.funcref
1066 |         try
1067 |           call s:load_plugin(spec)
1068 |           let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
1069 |           call spec.do({ 'name': name, 'status': status, 'force': a:force })
1070 |         catch
1071 |           let error = v:exception
1072 |         endtry
1073 |       else
1074 |         let error = 'Invalid hook type'
1075 |       endif
1076 |       call s:switch_in()
1077 |       call setline(4, empty(error) ? (getline(4) . 'OK')
1078 |                                  \ : ('x' . getline(4)[1:] . error))
1079 |       if !empty(error)
1080 |         call add(s:update.errors, name)
1081 |         call s:regress_bar()
1082 |       endif
1083 |       cd -
1084 |     endif
1085 |   endfor
1086 | endfunction
1087 | 
1088 | function! s:hash_match(a, b)
1089 |   return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
1090 | endfunction
1091 | 
1092 | function! s:checkout(spec)
1093 |   let sha = a:spec.commit
1094 |   let output = s:git_revision(a:spec.dir)
1095 |   if !empty(output) && !s:hash_match(sha, s:lines(output)[0])
1096 |     let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : ''
1097 |     let output = s:system(
1098 |           \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir)
1099 |   endif
1100 |   return output
1101 | endfunction
1102 | 
1103 | function! s:finish(pull)
1104 |   let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
1105 |   if new_frozen
1106 |     let s = new_frozen > 1 ? 's' : ''
1107 |     call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
1108 |   endif
1109 |   call append(3, '- Finishing ... ') | 4
1110 |   redraw
1111 |   call plug#helptags()
1112 |   call plug#end()
1113 |   call setline(4, getline(4) . 'Done!')
1114 |   redraw
1115 |   let msgs = []
1116 |   if !empty(s:update.errors)
1117 |     call add(msgs, "Press 'R' to retry.")
1118 |   endif
1119 |   if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
1120 |                 \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'"))
1121 |     call add(msgs, "Press 'D' to see the updated changes.")
1122 |   endif
1123 |   echo join(msgs, ' ')
1124 |   call s:finish_bindings()
1125 | endfunction
1126 | 
1127 | function! s:retry()
1128 |   if empty(s:update.errors)
1129 |     return
1130 |   endif
1131 |   echo
1132 |   call s:update_impl(s:update.pull, s:update.force,
1133 |         \ extend(copy(s:update.errors), [s:update.threads]))
1134 | endfunction
1135 | 
1136 | function! s:is_managed(name)
1137 |   return has_key(g:plugs[a:name], 'uri')
1138 | endfunction
1139 | 
1140 | function! s:names(...)
1141 |   return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
1142 | endfunction
1143 | 
1144 | function! s:check_ruby()
1145 |   silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'")
1146 |   if !exists('g:plug_ruby')
1147 |     redraw!
1148 |     return s:warn('echom', 'Warning: Ruby interface is broken')
1149 |   endif
1150 |   let ruby_version = split(g:plug_ruby, '\.')
1151 |   unlet g:plug_ruby
1152 |   return s:version_requirement(ruby_version, [1, 8, 7])
1153 | endfunction
1154 | 
1155 | function! s:update_impl(pull, force, args) abort
1156 |   let sync = index(a:args, '--sync') >= 0 || has('vim_starting')
1157 |   let args = filter(copy(a:args), 'v:val != "--sync"')
1158 |   let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
1159 |                   \ remove(args, -1) : get(g:, 'plug_threads', 16)
1160 | 
1161 |   let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
1162 |   let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
1163 |                          \ filter(managed, 'index(args, v:key) >= 0')
1164 | 
1165 |   if empty(todo)
1166 |     return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install'))
1167 |   endif
1168 | 
1169 |   if !s:is_win && s:git_version_requirement(2, 3)
1170 |     let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
1171 |     let $GIT_TERMINAL_PROMPT = 0
1172 |     for plug in values(todo)
1173 |       let plug.uri = substitute(plug.uri,
1174 |             \ '^https://git::@github\.com', 'https://github.com', '')
1175 |     endfor
1176 |   endif
1177 | 
1178 |   if !isdirectory(g:plug_home)
1179 |     try
1180 |       call mkdir(g:plug_home, 'p')
1181 |     catch
1182 |       return s:err(printf('Invalid plug directory: %s. '.
1183 |               \ 'Try to call plug#begin with a valid directory', g:plug_home))
1184 |     endtry
1185 |   endif
1186 | 
1187 |   if has('nvim') && !exists('*jobwait') && threads > 1
1188 |     call s:warn('echom', '[vim-plug] Update Neovim for parallel installer')
1189 |   endif
1190 | 
1191 |   let use_job = s:nvim || s:vim8
1192 |   let python = (has('python') || has('python3')) && !use_job
1193 |   let ruby = has('ruby') && !use_job && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) && threads > 1 && s:check_ruby()
1194 | 
1195 |   let s:update = {
1196 |     \ 'start':   reltime(),
1197 |     \ 'all':     todo,
1198 |     \ 'todo':    copy(todo),
1199 |     \ 'errors':  [],
1200 |     \ 'pull':    a:pull,
1201 |     \ 'force':   a:force,
1202 |     \ 'new':     {},
1203 |     \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1,
1204 |     \ 'bar':     '',
1205 |     \ 'fin':     0
1206 |   \ }
1207 | 
1208 |   call s:prepare(1)
1209 |   call append(0, ['', ''])
1210 |   normal! 2G
1211 |   silent! redraw
1212 | 
1213 |   " Set remote name, overriding a possible user git config's clone.defaultRemoteName
1214 |   let s:clone_opt = ['--origin', 'origin']
1215 |   if get(g:, 'plug_shallow', 1)
1216 |     call extend(s:clone_opt, ['--depth', '1'])
1217 |     if s:git_version_requirement(1, 7, 10)
1218 |       call add(s:clone_opt, '--no-single-branch')
1219 |     endif
1220 |   endif
1221 | 
1222 |   if has('win32unix') || has('wsl')
1223 |     call extend(s:clone_opt, ['-c', 'core.eol=lf', '-c', 'core.autocrlf=input'])
1224 |   endif
1225 | 
1226 |   let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : ''
1227 | 
1228 |   " Python version requirement (>= 2.7)
1229 |   if python && !has('python3') && !ruby && !use_job && s:update.threads > 1
1230 |     redir => pyv
1231 |     silent python import platform; print platform.python_version()
1232 |     redir END
1233 |     let python = s:version_requirement(
1234 |           \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
1235 |   endif
1236 | 
1237 |   if (python || ruby) && s:update.threads > 1
1238 |     try
1239 |       let imd = &imd
1240 |       if s:mac_gui
1241 |         set noimd
1242 |       endif
1243 |       if ruby
1244 |         call s:update_ruby()
1245 |       else
1246 |         call s:update_python()
1247 |       endif
1248 |     catch
1249 |       let lines = getline(4, '$')
1250 |       let printed = {}
1251 |       silent! 4,$d _
1252 |       for line in lines
1253 |         let name = s:extract_name(line, '.', '')
1254 |         if empty(name) || !has_key(printed, name)
1255 |           call append('$', line)
1256 |           if !empty(name)
1257 |             let printed[name] = 1
1258 |             if line[0] == 'x' && index(s:update.errors, name) < 0
1259 |               call add(s:update.errors, name)
1260 |             end
1261 |           endif
1262 |         endif
1263 |       endfor
1264 |     finally
1265 |       let &imd = imd
1266 |       call s:update_finish()
1267 |     endtry
1268 |   else
1269 |     call s:update_vim()
1270 |     while use_job && sync
1271 |       sleep 100m
1272 |       if s:update.fin
1273 |         break
1274 |       endif
1275 |     endwhile
1276 |   endif
1277 | endfunction
1278 | 
1279 | function! s:log4(name, msg)
1280 |   call setline(4, printf('- %s (%s)', a:msg, a:name))
1281 |   redraw
1282 | endfunction
1283 | 
1284 | function! s:update_finish()
1285 |   if exists('s:git_terminal_prompt')
1286 |     let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
1287 |   endif
1288 |   if s:switch_in()
1289 |     call append(3, '- Updating ...') | 4
1290 |     for [name, spec] in items(filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && (s:update.force || s:update.pull || has_key(s:update.new, v:key))'))
1291 |       let [pos, _] = s:logpos(name)
1292 |       if !pos
1293 |         continue
1294 |       endif
1295 |       if has_key(spec, 'commit')
1296 |         call s:log4(name, 'Checking out '.spec.commit)
1297 |         let out = s:checkout(spec)
1298 |       elseif has_key(spec, 'tag')
1299 |         let tag = spec.tag
1300 |         if tag =~ '\*'
1301 |           let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir))
1302 |           if !v:shell_error && !empty(tags)
1303 |             let tag = tags[0]
1304 |             call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag))
1305 |             call append(3, '')
1306 |           endif
1307 |         endif
1308 |         call s:log4(name, 'Checking out '.tag)
1309 |         let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir)
1310 |       else
1311 |         let branch = s:git_origin_branch(spec)
1312 |         call s:log4(name, 'Merging origin/'.s:esc(branch))
1313 |         let out = s:system('git checkout -q '.plug#shellescape(branch).' -- 2>&1'
1314 |               \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only '.plug#shellescape('origin/'.branch).' 2>&1')), spec.dir)
1315 |       endif
1316 |       if !v:shell_error && filereadable(spec.dir.'/.gitmodules') &&
1317 |             \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir))
1318 |         call s:log4(name, 'Updating submodules. This may take a while.')
1319 |         let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir)
1320 |       endif
1321 |       let msg = s:format_message(v:shell_error ? 'x': '-', name, out)
1322 |       if v:shell_error
1323 |         call add(s:update.errors, name)
1324 |         call s:regress_bar()
1325 |         silent execute pos 'd _'
1326 |         call append(4, msg) | 4
1327 |       elseif !empty(out)
1328 |         call setline(pos, msg[0])
1329 |       endif
1330 |       redraw
1331 |     endfor
1332 |     silent 4 d _
1333 |     try
1334 |       call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'index(s:update.errors, v:key) < 0 && has_key(v:val, "do")'))
1335 |     catch
1336 |       call s:warn('echom', v:exception)
1337 |       call s:warn('echo', '')
1338 |       return
1339 |     endtry
1340 |     call s:finish(s:update.pull)
1341 |     call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
1342 |     call s:switch_out('normal! gg')
1343 |   endif
1344 | endfunction
1345 | 
1346 | function! s:job_abort()
1347 |   if (!s:nvim && !s:vim8) || !exists('s:jobs')
1348 |     return
1349 |   endif
1350 | 
1351 |   for [name, j] in items(s:jobs)
1352 |     if s:nvim
1353 |       silent! call jobstop(j.jobid)
1354 |     elseif s:vim8
1355 |       silent! call job_stop(j.jobid)
1356 |     endif
1357 |     if j.new
1358 |       call s:rm_rf(g:plugs[name].dir)
1359 |     endif
1360 |   endfor
1361 |   let s:jobs = {}
1362 | endfunction
1363 | 
1364 | function! s:last_non_empty_line(lines)
1365 |   let len = len(a:lines)
1366 |   for idx in range(len)
1367 |     let line = a:lines[len-idx-1]
1368 |     if !empty(line)
1369 |       return line
1370 |     endif
1371 |   endfor
1372 |   return ''
1373 | endfunction
1374 | 
1375 | function! s:job_out_cb(self, data) abort
1376 |   let self = a:self
1377 |   let data = remove(self.lines, -1) . a:data
1378 |   let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]')
1379 |   call extend(self.lines, lines)
1380 |   " To reduce the number of buffer updates
1381 |   let self.tick = get(self, 'tick', -1) + 1
1382 |   if !self.running || self.tick % len(s:jobs) == 0
1383 |     let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-')
1384 |     let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines)
1385 |     call s:log(bullet, self.name, result)
1386 |   endif
1387 | endfunction
1388 | 
1389 | function! s:job_exit_cb(self, data) abort
1390 |   let a:self.running = 0
1391 |   let a:self.error = a:data != 0
1392 |   call s:reap(a:self.name)
1393 |   call s:tick()
1394 | endfunction
1395 | 
1396 | function! s:job_cb(fn, job, ch, data)
1397 |   if !s:plug_window_exists() " plug window closed
1398 |     return s:job_abort()
1399 |   endif
1400 |   call call(a:fn, [a:job, a:data])
1401 | endfunction
1402 | 
1403 | function! s:nvim_cb(job_id, data, event) dict abort
1404 |   return (a:event == 'stdout' || a:event == 'stderr') ?
1405 |     \ s:job_cb('s:job_out_cb',  self, 0, join(a:data, "\n")) :
1406 |     \ s:job_cb('s:job_exit_cb', self, 0, a:data)
1407 | endfunction
1408 | 
1409 | function! s:spawn(name, cmd, opts)
1410 |   let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''],
1411 |             \ 'new': get(a:opts, 'new', 0) }
1412 |   let s:jobs[a:name] = job
1413 | 
1414 |   if s:nvim
1415 |     if has_key(a:opts, 'dir')
1416 |       let job.cwd = a:opts.dir
1417 |     endif
1418 |     let argv = a:cmd
1419 |     call extend(job, {
1420 |     \ 'on_stdout': function('s:nvim_cb'),
1421 |     \ 'on_stderr': function('s:nvim_cb'),
1422 |     \ 'on_exit':   function('s:nvim_cb'),
1423 |     \ })
1424 |     let jid = s:plug_call('jobstart', argv, job)
1425 |     if jid > 0
1426 |       let job.jobid = jid
1427 |     else
1428 |       let job.running = 0
1429 |       let job.error   = 1
1430 |       let job.lines   = [jid < 0 ? argv[0].' is not executable' :
1431 |             \ 'Invalid arguments (or job table is full)']
1432 |     endif
1433 |   elseif s:vim8
1434 |     let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"script": 0})'))
1435 |     if has_key(a:opts, 'dir')
1436 |       let cmd = s:with_cd(cmd, a:opts.dir, 0)
1437 |     endif
1438 |     let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
1439 |     let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
1440 |     \ 'out_cb':   function('s:job_cb', ['s:job_out_cb',  job]),
1441 |     \ 'err_cb':   function('s:job_cb', ['s:job_out_cb',  job]),
1442 |     \ 'exit_cb':  function('s:job_cb', ['s:job_exit_cb', job]),
1443 |     \ 'err_mode': 'raw',
1444 |     \ 'out_mode': 'raw'
1445 |     \})
1446 |     if job_status(jid) == 'run'
1447 |       let job.jobid = jid
1448 |     else
1449 |       let job.running = 0
1450 |       let job.error   = 1
1451 |       let job.lines   = ['Failed to start job']
1452 |     endif
1453 |   else
1454 |     let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd]))
1455 |     let job.error = v:shell_error != 0
1456 |     let job.running = 0
1457 |   endif
1458 | endfunction
1459 | 
1460 | function! s:reap(name)
1461 |   let job = s:jobs[a:name]
1462 |   if job.error
1463 |     call add(s:update.errors, a:name)
1464 |   elseif get(job, 'new', 0)
1465 |     let s:update.new[a:name] = 1
1466 |   endif
1467 |   let s:update.bar .= job.error ? 'x' : '='
1468 | 
1469 |   let bullet = job.error ? 'x' : '-'
1470 |   let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines)
1471 |   call s:log(bullet, a:name, empty(result) ? 'OK' : result)
1472 |   call s:bar()
1473 | 
1474 |   call remove(s:jobs, a:name)
1475 | endfunction
1476 | 
1477 | function! s:bar()
1478 |   if s:switch_in()
1479 |     let total = len(s:update.all)
1480 |     call setline(1, (s:update.pull ? 'Updating' : 'Installing').
1481 |           \ ' plugins ('.len(s:update.bar).'/'.total.')')
1482 |     call s:progress_bar(2, s:update.bar, total)
1483 |     call s:switch_out()
1484 |   endif
1485 | endfunction
1486 | 
1487 | function! s:logpos(name)
1488 |   let max = line('$')
1489 |   for i in range(4, max > 4 ? max : 4)
1490 |     if getline(i) =~# '^[-+x*] '.a:name.':'
1491 |       for j in range(i + 1, max > 5 ? max : 5)
1492 |         if getline(j) !~ '^ '
1493 |           return [i, j - 1]
1494 |         endif
1495 |       endfor
1496 |       return [i, i]
1497 |     endif
1498 |   endfor
1499 |   return [0, 0]
1500 | endfunction
1501 | 
1502 | function! s:log(bullet, name, lines)
1503 |   if s:switch_in()
1504 |     let [b, e] = s:logpos(a:name)
1505 |     if b > 0
1506 |       silent execute printf('%d,%d d _', b, e)
1507 |       if b > winheight('.')
1508 |         let b = 4
1509 |       endif
1510 |     else
1511 |       let b = 4
1512 |     endif
1513 |     " FIXME For some reason, nomodifiable is set after :d in vim8
1514 |     setlocal modifiable
1515 |     call append(b - 1, s:format_message(a:bullet, a:name, a:lines))
1516 |     call s:switch_out()
1517 |   endif
1518 | endfunction
1519 | 
1520 | function! s:update_vim()
1521 |   let s:jobs = {}
1522 | 
1523 |   call s:bar()
1524 |   call s:tick()
1525 | endfunction
1526 | 
1527 | function! s:tick()
1528 |   let pull = s:update.pull
1529 |   let prog = s:progress_opt(s:nvim || s:vim8)
1530 | while 1 " Without TCO, Vim stack is bound to explode
1531 |   if empty(s:update.todo)
1532 |     if empty(s:jobs) && !s:update.fin
1533 |       call s:update_finish()
1534 |       let s:update.fin = 1
1535 |     endif
1536 |     return
1537 |   endif
1538 | 
1539 |   let name = keys(s:update.todo)[0]
1540 |   let spec = remove(s:update.todo, name)
1541 |   let new  = empty(globpath(spec.dir, '.git', 1))
1542 | 
1543 |   call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
1544 |   redraw
1545 | 
1546 |   let has_tag = has_key(spec, 'tag')
1547 |   if !new
1548 |     let [error, _] = s:git_validate(spec, 0)
1549 |     if empty(error)
1550 |       if pull
1551 |         let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
1552 |         if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
1553 |           call extend(cmd, ['--depth', '99999999'])
1554 |         endif
1555 |         if !empty(prog)
1556 |           call add(cmd, prog)
1557 |         endif
1558 |         call s:spawn(name, cmd, { 'dir': spec.dir })
1559 |       else
1560 |         let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 }
1561 |       endif
1562 |     else
1563 |       let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 }
1564 |     endif
1565 |   else
1566 |     let cmd = ['git', 'clone']
1567 |     if !has_tag
1568 |       call extend(cmd, s:clone_opt)
1569 |     endif
1570 |     if !empty(prog)
1571 |       call add(cmd, prog)
1572 |     endif
1573 |     call s:spawn(name, extend(cmd, [spec.uri, s:trim(spec.dir)]), { 'new': 1 })
1574 |   endif
1575 | 
1576 |   if !s:jobs[name].running
1577 |     call s:reap(name)
1578 |   endif
1579 |   if len(s:jobs) >= s:update.threads
1580 |     break
1581 |   endif
1582 | endwhile
1583 | endfunction
1584 | 
1585 | function! s:update_python()
1586 | let py_exe = has('python') ? 'python' : 'python3'
1587 | execute py_exe "<< EOF"
1588 | import datetime
1589 | import functools
1590 | import os
1591 | try:
1592 |   import queue
1593 | except ImportError:
1594 |   import Queue as queue
1595 | import random
1596 | import re
1597 | import shutil
1598 | import signal
1599 | import subprocess
1600 | import tempfile
1601 | import threading as thr
1602 | import time
1603 | import traceback
1604 | import vim
1605 | 
1606 | G_NVIM = vim.eval("has('nvim')") == '1'
1607 | G_PULL = vim.eval('s:update.pull') == '1'
1608 | G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
1609 | G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
1610 | G_CLONE_OPT = ' '.join(vim.eval('s:clone_opt'))
1611 | G_PROGRESS = vim.eval('s:progress_opt(1)')
1612 | G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
1613 | G_STOP = thr.Event()
1614 | G_IS_WIN = vim.eval('s:is_win') == '1'
1615 | 
1616 | class PlugError(Exception):
1617 |   def __init__(self, msg):
1618 |     self.msg = msg
1619 | class CmdTimedOut(PlugError):
1620 |   pass
1621 | class CmdFailed(PlugError):
1622 |   pass
1623 | class InvalidURI(PlugError):
1624 |   pass
1625 | class Action(object):
1626 |   INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
1627 | 
1628 | class Buffer(object):
1629 |   def __init__(self, lock, num_plugs, is_pull):
1630 |     self.bar = ''
1631 |     self.event = 'Updating' if is_pull else 'Installing'
1632 |     self.lock = lock
1633 |     self.maxy = int(vim.eval('winheight(".")'))
1634 |     self.num_plugs = num_plugs
1635 | 
1636 |   def __where(self, name):
1637 |     """ Find first line with name in current buffer. Return line num. """
1638 |     found, lnum = False, 0
1639 |     matcher = re.compile('^[-+x*] {0}:'.format(name))
1640 |     for line in vim.current.buffer:
1641 |       if matcher.search(line) is not None:
1642 |         found = True
1643 |         break
1644 |       lnum += 1
1645 | 
1646 |     if not found:
1647 |       lnum = -1
1648 |     return lnum
1649 | 
1650 |   def header(self):
1651 |     curbuf = vim.current.buffer
1652 |     curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs)
1653 | 
1654 |     num_spaces = self.num_plugs - len(self.bar)
1655 |     curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
1656 | 
1657 |     with self.lock:
1658 |       vim.command('normal! 2G')
1659 |       vim.command('redraw')
1660 | 
1661 |   def write(self, action, name, lines):
1662 |     first, rest = lines[0], lines[1:]
1663 |     msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)]
1664 |     msg.extend(['    ' + line for line in rest])
1665 | 
1666 |     try:
1667 |       if action == Action.ERROR:
1668 |         self.bar += 'x'
1669 |         vim.command("call add(s:update.errors, '{0}')".format(name))
1670 |       elif action == Action.DONE:
1671 |         self.bar += '='
1672 | 
1673 |       curbuf = vim.current.buffer
1674 |       lnum = self.__where(name)
1675 |       if lnum != -1: # Found matching line num
1676 |         del curbuf[lnum]
1677 |         if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]):
1678 |           lnum = 3
1679 |       else:
1680 |         lnum = 3
1681 |       curbuf.append(msg, lnum)
1682 | 
1683 |       self.header()
1684 |     except vim.error:
1685 |       pass
1686 | 
1687 | class Command(object):
1688 |   CD = 'cd /d' if G_IS_WIN else 'cd'
1689 | 
1690 |   def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None):
1691 |     self.cmd = cmd
1692 |     if cmd_dir:
1693 |       self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd)
1694 |     self.timeout = timeout
1695 |     self.callback = cb if cb else (lambda msg: None)
1696 |     self.clean = clean if clean else (lambda: None)
1697 |     self.proc = None
1698 | 
1699 |   @property
1700 |   def alive(self):
1701 |     """ Returns true only if command still running. """
1702 |     return self.proc and self.proc.poll() is None
1703 | 
1704 |   def execute(self, ntries=3):
1705 |     """ Execute the command with ntries if CmdTimedOut.
1706 |         Returns the output of the command if no Exception.
1707 |     """
1708 |     attempt, finished, limit = 0, False, self.timeout
1709 | 
1710 |     while not finished:
1711 |       try:
1712 |         attempt += 1
1713 |         result = self.try_command()
1714 |         finished = True
1715 |         return result
1716 |       except CmdTimedOut:
1717 |         if attempt != ntries:
1718 |           self.notify_retry()
1719 |           self.timeout += limit
1720 |         else:
1721 |           raise
1722 | 
1723 |   def notify_retry(self):
1724 |     """ Retry required for command, notify user. """
1725 |     for count in range(3, 0, -1):
1726 |       if G_STOP.is_set():
1727 |         raise KeyboardInterrupt
1728 |       msg = 'Timeout. Will retry in {0} second{1} ...'.format(
1729 |             count, 's' if count != 1 else '')
1730 |       self.callback([msg])
1731 |       time.sleep(1)
1732 |     self.callback(['Retrying ...'])
1733 | 
1734 |   def try_command(self):
1735 |     """ Execute a cmd & poll for callback. Returns list of output.
1736 |         Raises CmdFailed   -> return code for Popen isn't 0
1737 |         Raises CmdTimedOut -> command exceeded timeout without new output
1738 |     """
1739 |     first_line = True
1740 | 
1741 |     try:
1742 |       tfile = tempfile.NamedTemporaryFile(mode='w+b')
1743 |       preexec_fn = not G_IS_WIN and os.setsid or None
1744 |       self.proc = subprocess.Popen(self.cmd, stdout=tfile,
1745 |                                    stderr=subprocess.STDOUT,
1746 |                                    stdin=subprocess.PIPE, shell=True,
1747 |                                    preexec_fn=preexec_fn)
1748 |       thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,))
1749 |       thrd.start()
1750 | 
1751 |       thread_not_started = True
1752 |       while thread_not_started:
1753 |         try:
1754 |           thrd.join(0.1)
1755 |           thread_not_started = False
1756 |         except RuntimeError:
1757 |           pass
1758 | 
1759 |       while self.alive:
1760 |         if G_STOP.is_set():
1761 |           raise KeyboardInterrupt
1762 | 
1763 |         if first_line or random.random() < G_LOG_PROB:
1764 |           first_line = False
1765 |           line = '' if G_IS_WIN else nonblock_read(tfile.name)
1766 |           if line:
1767 |             self.callback([line])
1768 | 
1769 |         time_diff = time.time() - os.path.getmtime(tfile.name)
1770 |         if time_diff > self.timeout:
1771 |           raise CmdTimedOut(['Timeout!'])
1772 | 
1773 |         thrd.join(0.5)
1774 | 
1775 |       tfile.seek(0)
1776 |       result = [line.decode('utf-8', 'replace').rstrip() for line in tfile]
1777 | 
1778 |       if self.proc.returncode != 0:
1779 |         raise CmdFailed([''] + result)
1780 | 
1781 |       return result
1782 |     except:
1783 |       self.terminate()
1784 |       raise
1785 | 
1786 |   def terminate(self):
1787 |     """ Terminate process and cleanup. """
1788 |     if self.alive:
1789 |       if G_IS_WIN:
1790 |         os.kill(self.proc.pid, signal.SIGINT)
1791 |       else:
1792 |         os.killpg(self.proc.pid, signal.SIGTERM)
1793 |     self.clean()
1794 | 
1795 | class Plugin(object):
1796 |   def __init__(self, name, args, buf_q, lock):
1797 |     self.name = name
1798 |     self.args = args
1799 |     self.buf_q = buf_q
1800 |     self.lock = lock
1801 |     self.tag = args.get('tag', 0)
1802 | 
1803 |   def manage(self):
1804 |     try:
1805 |       if os.path.exists(self.args['dir']):
1806 |         self.update()
1807 |       else:
1808 |         self.install()
1809 |         with self.lock:
1810 |           thread_vim_command("let s:update.new['{0}'] = 1".format(self.name))
1811 |     except PlugError as exc:
1812 |       self.write(Action.ERROR, self.name, exc.msg)
1813 |     except KeyboardInterrupt:
1814 |       G_STOP.set()
1815 |       self.write(Action.ERROR, self.name, ['Interrupted!'])
1816 |     except:
1817 |       # Any exception except those above print stack trace
1818 |       msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip())
1819 |       self.write(Action.ERROR, self.name, msg.split('\n'))
1820 |       raise
1821 | 
1822 |   def install(self):
1823 |     target = self.args['dir']
1824 |     if target[-1] == '\\':
1825 |       target = target[0:-1]
1826 | 
1827 |     def clean(target):
1828 |       def _clean():
1829 |         try:
1830 |           shutil.rmtree(target)
1831 |         except OSError:
1832 |           pass
1833 |       return _clean
1834 | 
1835 |     self.write(Action.INSTALL, self.name, ['Installing ...'])
1836 |     callback = functools.partial(self.write, Action.INSTALL, self.name)
1837 |     cmd = 'git clone {0} {1} {2} {3} 2>&1'.format(
1838 |           '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'],
1839 |           esc(target))
1840 |     com = Command(cmd, None, G_TIMEOUT, callback, clean(target))
1841 |     result = com.execute(G_RETRIES)
1842 |     self.write(Action.DONE, self.name, result[-1:])
1843 | 
1844 |   def repo_uri(self):
1845 |     cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url'
1846 |     command = Command(cmd, self.args['dir'], G_TIMEOUT,)
1847 |     result = command.execute(G_RETRIES)
1848 |     return result[-1]
1849 | 
1850 |   def update(self):
1851 |     actual_uri = self.repo_uri()
1852 |     expect_uri = self.args['uri']
1853 |     regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$')
1854 |     ma = regex.match(actual_uri)
1855 |     mb = regex.match(expect_uri)
1856 |     if ma is None or mb is None or ma.groups() != mb.groups():
1857 |       msg = ['',
1858 |              'Invalid URI: {0}'.format(actual_uri),
1859 |              'Expected     {0}'.format(expect_uri),
1860 |              'PlugClean required.']
1861 |       raise InvalidURI(msg)
1862 | 
1863 |     if G_PULL:
1864 |       self.write(Action.UPDATE, self.name, ['Updating ...'])
1865 |       callback = functools.partial(self.write, Action.UPDATE, self.name)
1866 |       fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else ''
1867 |       cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS)
1868 |       com = Command(cmd, self.args['dir'], G_TIMEOUT, callback)
1869 |       result = com.execute(G_RETRIES)
1870 |       self.write(Action.DONE, self.name, result[-1:])
1871 |     else:
1872 |       self.write(Action.DONE, self.name, ['Already installed'])
1873 | 
1874 |   def write(self, action, name, msg):
1875 |     self.buf_q.put((action, name, msg))
1876 | 
1877 | class PlugThread(thr.Thread):
1878 |   def __init__(self, tname, args):
1879 |     super(PlugThread, self).__init__()
1880 |     self.tname = tname
1881 |     self.args = args
1882 | 
1883 |   def run(self):
1884 |     thr.current_thread().name = self.tname
1885 |     buf_q, work_q, lock = self.args
1886 | 
1887 |     try:
1888 |       while not G_STOP.is_set():
1889 |         name, args = work_q.get_nowait()
1890 |         plug = Plugin(name, args, buf_q, lock)
1891 |         plug.manage()
1892 |         work_q.task_done()
1893 |     except queue.Empty:
1894 |       pass
1895 | 
1896 | class RefreshThread(thr.Thread):
1897 |   def __init__(self, lock):
1898 |     super(RefreshThread, self).__init__()
1899 |     self.lock = lock
1900 |     self.running = True
1901 | 
1902 |   def run(self):
1903 |     while self.running:
1904 |       with self.lock:
1905 |         thread_vim_command('noautocmd normal! a')
1906 |       time.sleep(0.33)
1907 | 
1908 |   def stop(self):
1909 |     self.running = False
1910 | 
1911 | if G_NVIM:
1912 |   def thread_vim_command(cmd):
1913 |     vim.session.threadsafe_call(lambda: vim.command(cmd))
1914 | else:
1915 |   def thread_vim_command(cmd):
1916 |     vim.command(cmd)
1917 | 
1918 | def esc(name):
1919 |   return '"' + name.replace('"', '\"') + '"'
1920 | 
1921 | def nonblock_read(fname):
1922 |   """ Read a file with nonblock flag. Return the last line. """
1923 |   fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
1924 |   buf = os.read(fread, 100000).decode('utf-8', 'replace')
1925 |   os.close(fread)
1926 | 
1927 |   line = buf.rstrip('\r\n')
1928 |   left = max(line.rfind('\r'), line.rfind('\n'))
1929 |   if left != -1:
1930 |     left += 1
1931 |     line = line[left:]
1932 | 
1933 |   return line
1934 | 
1935 | def main():
1936 |   thr.current_thread().name = 'main'
1937 |   nthreads = int(vim.eval('s:update.threads'))
1938 |   plugs = vim.eval('s:update.todo')
1939 |   mac_gui = vim.eval('s:mac_gui') == '1'
1940 | 
1941 |   lock = thr.Lock()
1942 |   buf = Buffer(lock, len(plugs), G_PULL)
1943 |   buf_q, work_q = queue.Queue(), queue.Queue()
1944 |   for work in plugs.items():
1945 |     work_q.put(work)
1946 | 
1947 |   start_cnt = thr.active_count()
1948 |   for num in range(nthreads):
1949 |     tname = 'PlugT-{0:02}'.format(num)
1950 |     thread = PlugThread(tname, (buf_q, work_q, lock))
1951 |     thread.start()
1952 |   if mac_gui:
1953 |     rthread = RefreshThread(lock)
1954 |     rthread.start()
1955 | 
1956 |   while not buf_q.empty() or thr.active_count() != start_cnt:
1957 |     try:
1958 |       action, name, msg = buf_q.get(True, 0.25)
1959 |       buf.write(action, name, ['OK'] if not msg else msg)
1960 |       buf_q.task_done()
1961 |     except queue.Empty:
1962 |       pass
1963 |     except KeyboardInterrupt:
1964 |       G_STOP.set()
1965 | 
1966 |   if mac_gui:
1967 |     rthread.stop()
1968 |     rthread.join()
1969 | 
1970 | main()
1971 | EOF
1972 | endfunction
1973 | 
1974 | function! s:update_ruby()
1975 |   ruby << EOF
1976 |   module PlugStream
1977 |     SEP = ["\r", "\n", nil]
1978 |     def get_line
1979 |       buffer = ''
1980 |       loop do
1981 |         char = readchar rescue return
1982 |         if SEP.include? char.chr
1983 |           buffer << $/
1984 |           break
1985 |         else
1986 |           buffer << char
1987 |         end
1988 |       end
1989 |       buffer
1990 |     end
1991 |   end unless defined?(PlugStream)
1992 | 
1993 |   def esc arg
1994 |     %["#{arg.gsub('"', '\"')}"]
1995 |   end
1996 | 
1997 |   def killall pid
1998 |     pids = [pid]
1999 |     if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
2000 |       pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil }
2001 |     else
2002 |       unless `which pgrep 2> /dev/null`.empty?
2003 |         children = pids
2004 |         until children.empty?
2005 |           children = children.map { |pid|
2006 |             `pgrep -P #{pid}`.lines.map { |l| l.chomp }
2007 |           }.flatten
2008 |           pids += children
2009 |         end
2010 |       end
2011 |       pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
2012 |     end
2013 |   end
2014 | 
2015 |   def compare_git_uri a, b
2016 |     regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$}
2017 |     regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1)
2018 |   end
2019 | 
2020 |   require 'thread'
2021 |   require 'fileutils'
2022 |   require 'timeout'
2023 |   running = true
2024 |   iswin = VIM::evaluate('s:is_win').to_i == 1
2025 |   pull  = VIM::evaluate('s:update.pull').to_i == 1
2026 |   base  = VIM::evaluate('g:plug_home')
2027 |   all   = VIM::evaluate('s:update.todo')
2028 |   limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
2029 |   tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
2030 |   nthr  = VIM::evaluate('s:update.threads').to_i
2031 |   maxy  = VIM::evaluate('winheight(".")').to_i
2032 |   vim7  = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/
2033 |   cd    = iswin ? 'cd /d' : 'cd'
2034 |   tot   = VIM::evaluate('len(s:update.todo)') || 0
2035 |   bar   = ''
2036 |   skip  = 'Already installed'
2037 |   mtx   = Mutex.new
2038 |   take1 = proc { mtx.synchronize { running && all.shift } }
2039 |   logh  = proc {
2040 |     cnt = bar.length
2041 |     $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
2042 |     $curbuf[2] = '[' + bar.ljust(tot) + ']'
2043 |     VIM::command('normal! 2G')
2044 |     VIM::command('redraw')
2045 |   }
2046 |   where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
2047 |   log   = proc { |name, result, type|
2048 |     mtx.synchronize do
2049 |       ing  = ![true, false].include?(type)
2050 |       bar += type ? '=' : 'x' unless ing
2051 |       b = case type
2052 |           when :install  then '+' when :update then '*'
2053 |           when true, nil then '-' else
2054 |             VIM::command("call add(s:update.errors, '#{name}')")
2055 |             'x'
2056 |           end
2057 |       result =
2058 |         if type || type.nil?
2059 |           ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"]
2060 |         elsif result =~ /^Interrupted|^Timeout/
2061 |           ["#{b} #{name}: #{result}"]
2062 |         else
2063 |           ["#{b} #{name}"] + result.lines.map { |l| "    " << l }
2064 |         end
2065 |       if lnum = where.call(name)
2066 |         $curbuf.delete lnum
2067 |         lnum = 4 if ing && lnum > maxy
2068 |       end
2069 |       result.each_with_index do |line, offset|
2070 |         $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
2071 |       end
2072 |       logh.call
2073 |     end
2074 |   }
2075 |   bt = proc { |cmd, name, type, cleanup|
2076 |     tried = timeout = 0
2077 |     begin
2078 |       tried += 1
2079 |       timeout += limit
2080 |       fd = nil
2081 |       data = ''
2082 |       if iswin
2083 |         Timeout::timeout(timeout) do
2084 |           tmp = VIM::evaluate('tempname()')
2085 |           system("(#{cmd}) > #{tmp}")
2086 |           data = File.read(tmp).chomp
2087 |           File.unlink tmp rescue nil
2088 |         end
2089 |       else
2090 |         fd = IO.popen(cmd).extend(PlugStream)
2091 |         first_line = true
2092 |         log_prob = 1.0 / nthr
2093 |         while line = Timeout::timeout(timeout) { fd.get_line }
2094 |           data << line
2095 |           log.call name, line.chomp, type if name && (first_line || rand < log_prob)
2096 |           first_line = false
2097 |         end
2098 |         fd.close
2099 |       end
2100 |       [$? == 0, data.chomp]
2101 |     rescue Timeout::Error, Interrupt => e
2102 |       if fd && !fd.closed?
2103 |         killall fd.pid
2104 |         fd.close
2105 |       end
2106 |       cleanup.call if cleanup
2107 |       if e.is_a?(Timeout::Error) && tried < tries
2108 |         3.downto(1) do |countdown|
2109 |           s = countdown > 1 ? 's' : ''
2110 |           log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
2111 |           sleep 1
2112 |         end
2113 |         log.call name, 'Retrying ...', type
2114 |         retry
2115 |       end
2116 |       [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
2117 |     end
2118 |   }
2119 |   main = Thread.current
2120 |   threads = []
2121 |   watcher = Thread.new {
2122 |     if vim7
2123 |       while VIM::evaluate('getchar(1)')
2124 |         sleep 0.1
2125 |       end
2126 |     else
2127 |       require 'io/console' # >= Ruby 1.9
2128 |       nil until IO.console.getch == 3.chr
2129 |     end
2130 |     mtx.synchronize do
2131 |       running = false
2132 |       threads.each { |t| t.raise Interrupt } unless vim7
2133 |     end
2134 |     threads.each { |t| t.join rescue nil }
2135 |     main.kill
2136 |   }
2137 |   refresh = Thread.new {
2138 |     while true
2139 |       mtx.synchronize do
2140 |         break unless running
2141 |         VIM::command('noautocmd normal! a')
2142 |       end
2143 |       sleep 0.2
2144 |     end
2145 |   } if VIM::evaluate('s:mac_gui') == 1
2146 | 
2147 |   clone_opt = VIM::evaluate('s:clone_opt').join(' ')
2148 |   progress = VIM::evaluate('s:progress_opt(1)')
2149 |   nthr.times do
2150 |     mtx.synchronize do
2151 |       threads << Thread.new {
2152 |         while pair = take1.call
2153 |           name = pair.first
2154 |           dir, uri, tag = pair.last.values_at *%w[dir uri tag]
2155 |           exists = File.directory? dir
2156 |           ok, result =
2157 |             if exists
2158 |               chdir = "#{cd} #{iswin ? dir : esc(dir)}"
2159 |               ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil
2160 |               current_uri = data.lines.to_a.last
2161 |               if !ret
2162 |                 if data =~ /^Interrupted|^Timeout/
2163 |                   [false, data]
2164 |                 else
2165 |                   [false, [data.chomp, "PlugClean required."].join($/)]
2166 |                 end
2167 |               elsif !compare_git_uri(current_uri, uri)
2168 |                 [false, ["Invalid URI: #{current_uri}",
2169 |                          "Expected:    #{uri}",
2170 |                          "PlugClean required."].join($/)]
2171 |               else
2172 |                 if pull
2173 |                   log.call name, 'Updating ...', :update
2174 |                   fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : ''
2175 |                   bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil
2176 |                 else
2177 |                   [true, skip]
2178 |                 end
2179 |               end
2180 |             else
2181 |               d = esc dir.sub(%r{[\\/]+$}, '')
2182 |               log.call name, 'Installing ...', :install
2183 |               bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc {
2184 |                 FileUtils.rm_rf dir
2185 |               }
2186 |             end
2187 |           mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
2188 |           log.call name, result, ok
2189 |         end
2190 |       } if running
2191 |     end
2192 |   end
2193 |   threads.each { |t| t.join rescue nil }
2194 |   logh.call
2195 |   refresh.kill if refresh
2196 |   watcher.kill
2197 | EOF
2198 | endfunction
2199 | 
2200 | function! s:shellesc_cmd(arg, script)
2201 |   let escaped = substitute('"'.a:arg.'"', '[&|<>()@^!"]', '^&', 'g')
2202 |   return substitute(escaped, '%', (a:script ? '%' : '^') . '&', 'g')
2203 | endfunction
2204 | 
2205 | function! s:shellesc_ps1(arg)
2206 |   return "'".substitute(escape(a:arg, '\"'), "'", "''", 'g')."'"
2207 | endfunction
2208 | 
2209 | function! s:shellesc_sh(arg)
2210 |   return "'".substitute(a:arg, "'", "'\\\\''", 'g')."'"
2211 | endfunction
2212 | 
2213 | " Escape the shell argument based on the shell.
2214 | " Vim and Neovim's shellescape() are insufficient.
2215 | " 1. shellslash determines whether to use single/double quotes.
2216 | "    Double-quote escaping is fragile for cmd.exe.
2217 | " 2. It does not work for powershell.
2218 | " 3. It does not work for *sh shells if the command is executed
2219 | "    via cmd.exe (ie. cmd.exe /c sh -c command command_args)
2220 | " 4. It does not support batchfile syntax.
2221 | "
2222 | " Accepts an optional dictionary with the following keys:
2223 | " - shell: same as Vim/Neovim 'shell' option.
2224 | "          If unset, fallback to 'cmd.exe' on Windows or 'sh'.
2225 | " - script: If truthy and shell is cmd.exe, escape for batchfile syntax.
2226 | function! plug#shellescape(arg, ...)
2227 |   if a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
2228 |     return a:arg
2229 |   endif
2230 |   let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
2231 |   let shell = get(opts, 'shell', s:is_win ? 'cmd.exe' : 'sh')
2232 |   let script = get(opts, 'script', 1)
2233 |   if shell =~# 'cmd\(\.exe\)\?$'
2234 |     return s:shellesc_cmd(a:arg, script)
2235 |   elseif s:is_powershell(shell)
2236 |     return s:shellesc_ps1(a:arg)
2237 |   endif
2238 |   return s:shellesc_sh(a:arg)
2239 | endfunction
2240 | 
2241 | function! s:glob_dir(path)
2242 |   return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2243 | endfunction
2244 | 
2245 | function! s:progress_bar(line, bar, total)
2246 |   call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2247 | endfunction
2248 | 
2249 | function! s:compare_git_uri(a, b)
2250 |   " See `git help clone'
2251 |   " https:// [user@] github.com[:port] / junegunn/vim-plug [.git]
2252 |   "          [git@]  github.com[:port] : junegunn/vim-plug [.git]
2253 |   " file://                            / junegunn/vim-plug        [/]
2254 |   "                                    / junegunn/vim-plug        [/]
2255 |   let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$'
2256 |   let ma = matchlist(a:a, pat)
2257 |   let mb = matchlist(a:b, pat)
2258 |   return ma[1:2] ==# mb[1:2]
2259 | endfunction
2260 | 
2261 | function! s:format_message(bullet, name, message)
2262 |   if a:bullet != 'x'
2263 |     return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2264 |   else
2265 |     let lines = map(s:lines(a:message), '"    ".v:val')
2266 |     return extend([printf('x %s:', a:name)], lines)
2267 |   endif
2268 | endfunction
2269 | 
2270 | function! s:with_cd(cmd, dir, ...)
2271 |   let script = a:0 > 0 ? a:1 : 1
2272 |   return printf('cd%s %s && %s', s:is_win ? ' /d' : '', plug#shellescape(a:dir, {'script': script}), a:cmd)
2273 | endfunction
2274 | 
2275 | function! s:system(cmd, ...)
2276 |   let batchfile = ''
2277 |   try
2278 |     let [sh, shellcmdflag, shrd] = s:chsh(1)
2279 |     if type(a:cmd) == s:TYPE.list
2280 |       " Neovim's system() supports list argument to bypass the shell
2281 |       " but it cannot set the working directory for the command.
2282 |       " Assume that the command does not rely on the shell.
2283 |       if has('nvim') && a:0 == 0
2284 |         return system(a:cmd)
2285 |       endif
2286 |       let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})'))
2287 |       if s:is_powershell(&shell)
2288 |         let cmd = '& ' . cmd
2289 |       endif
2290 |     else
2291 |       let cmd = a:cmd
2292 |     endif
2293 |     if a:0 > 0
2294 |       let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
2295 |     endif
2296 |     if s:is_win && type(a:cmd) != s:TYPE.list
2297 |       let [batchfile, cmd] = s:batchfile(cmd)
2298 |     endif
2299 |     return system(cmd)
2300 |   finally
2301 |     let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2302 |     if s:is_win && filereadable(batchfile)
2303 |       call delete(batchfile)
2304 |     endif
2305 |   endtry
2306 | endfunction
2307 | 
2308 | function! s:system_chomp(...)
2309 |   let ret = call('s:system', a:000)
2310 |   return v:shell_error ? '' : substitute(ret, '\n$', '', '')
2311 | endfunction
2312 | 
2313 | function! s:git_validate(spec, check_branch)
2314 |   let err = ''
2315 |   if isdirectory(a:spec.dir)
2316 |     let result = [s:git_local_branch(a:spec.dir), s:git_origin_url(a:spec.dir)]
2317 |     let remote = result[-1]
2318 |     if empty(remote)
2319 |       let err = join([remote, 'PlugClean required.'], "\n")
2320 |     elseif !s:compare_git_uri(remote, a:spec.uri)
2321 |       let err = join(['Invalid URI: '.remote,
2322 |                     \ 'Expected:    '.a:spec.uri,
2323 |                     \ 'PlugClean required.'], "\n")
2324 |     elseif a:check_branch && has_key(a:spec, 'commit')
2325 |       let sha = s:git_revision(a:spec.dir)
2326 |       if empty(sha)
2327 |         let err = join(add(result, 'PlugClean required.'), "\n")
2328 |       elseif !s:hash_match(sha, a:spec.commit)
2329 |         let err = join([printf('Invalid HEAD (expected: %s, actual: %s)',
2330 |                               \ a:spec.commit[:6], sha[:6]),
2331 |                       \ 'PlugUpdate required.'], "\n")
2332 |       endif
2333 |     elseif a:check_branch
2334 |       let current_branch = result[0]
2335 |       " Check tag
2336 |       let origin_branch = s:git_origin_branch(a:spec)
2337 |       if has_key(a:spec, 'tag')
2338 |         let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2339 |         if a:spec.tag !=# tag && a:spec.tag !~ '\*'
2340 |           let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
2341 |                 \ (empty(tag) ? 'N/A' : tag), a:spec.tag)
2342 |         endif
2343 |       " Check branch
2344 |       elseif origin_branch !=# current_branch
2345 |         let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
2346 |               \ current_branch, origin_branch)
2347 |       endif
2348 |       if empty(err)
2349 |         let [ahead, behind] = split(s:lastline(s:system([
2350 |         \ 'git', 'rev-list', '--count', '--left-right',
2351 |         \ printf('HEAD...origin/%s', origin_branch)
2352 |         \ ], a:spec.dir)), '\t')
2353 |         if !v:shell_error && ahead
2354 |           if behind
2355 |             " Only mention PlugClean if diverged, otherwise it's likely to be
2356 |             " pushable (and probably not that messed up).
2357 |             let err = printf(
2358 |                   \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n"
2359 |                   \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', origin_branch, ahead, behind)
2360 |           else
2361 |             let err = printf("Ahead of origin/%s by %d commit(s).\n"
2362 |                   \ .'Cannot update until local changes are pushed.',
2363 |                   \ origin_branch, ahead)
2364 |           endif
2365 |         endif
2366 |       endif
2367 |     endif
2368 |   else
2369 |     let err = 'Not found'
2370 |   endif
2371 |   return [err, err =~# 'PlugClean']
2372 | endfunction
2373 | 
2374 | function! s:rm_rf(dir)
2375 |   if isdirectory(a:dir)
2376 |     return s:system(s:is_win
2377 |     \ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
2378 |     \ : ['rm', '-rf', a:dir])
2379 |   endif
2380 | endfunction
2381 | 
2382 | function! s:clean(force)
2383 |   call s:prepare()
2384 |   call append(0, 'Searching for invalid plugins in '.g:plug_home)
2385 |   call append(1, '')
2386 | 
2387 |   " List of valid directories
2388 |   let dirs = []
2389 |   let errs = {}
2390 |   let [cnt, total] = [0, len(g:plugs)]
2391 |   for [name, spec] in items(g:plugs)
2392 |     if !s:is_managed(name)
2393 |       call add(dirs, spec.dir)
2394 |     else
2395 |       let [err, clean] = s:git_validate(spec, 1)
2396 |       if clean
2397 |         let errs[spec.dir] = s:lines(err)[0]
2398 |       else
2399 |         call add(dirs, spec.dir)
2400 |       endif
2401 |     endif
2402 |     let cnt += 1
2403 |     call s:progress_bar(2, repeat('=', cnt), total)
2404 |     normal! 2G
2405 |     redraw
2406 |   endfor
2407 | 
2408 |   let allowed = {}
2409 |   for dir in dirs
2410 |     let allowed[s:dirpath(s:plug_fnamemodify(dir, ':h:h'))] = 1
2411 |     let allowed[dir] = 1
2412 |     for child in s:glob_dir(dir)
2413 |       let allowed[child] = 1
2414 |     endfor
2415 |   endfor
2416 | 
2417 |   let todo = []
2418 |   let found = sort(s:glob_dir(g:plug_home))
2419 |   while !empty(found)
2420 |     let f = remove(found, 0)
2421 |     if !has_key(allowed, f) && isdirectory(f)
2422 |       call add(todo, f)
2423 |       call append(line('$'), '- ' . f)
2424 |       if has_key(errs, f)
2425 |         call append(line('$'), '    ' . errs[f])
2426 |       endif
2427 |       let found = filter(found, 'stridx(v:val, f) != 0')
2428 |     end
2429 |   endwhile
2430 | 
2431 |   4
2432 |   redraw
2433 |   if empty(todo)
2434 |     call append(line('$'), 'Already clean.')
2435 |   else
2436 |     let s:clean_count = 0
2437 |     call append(3, ['Directories to delete:', ''])
2438 |     redraw!
2439 |     if a:force || s:ask_no_interrupt('Delete all directories?')
2440 |       call s:delete([6, line('$')], 1)
2441 |     else
2442 |       call setline(4, 'Cancelled.')
2443 |       nnoremap   d :set opfunc=delete_opg@
2444 |       nmap       dd d_
2445 |       xnoremap   d :call delete_op(visualmode(), 1)
2446 |       echo 'Delete the lines (d{motion}) to delete the corresponding directories'
2447 |     endif
2448 |   endif
2449 |   4
2450 |   setlocal nomodifiable
2451 | endfunction
2452 | 
2453 | function! s:delete_op(type, ...)
2454 |   call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0)
2455 | endfunction
2456 | 
2457 | function! s:delete(range, force)
2458 |   let [l1, l2] = a:range
2459 |   let force = a:force
2460 |   let err_count = 0
2461 |   while l1 <= l2
2462 |     let line = getline(l1)
2463 |     if line =~ '^- ' && isdirectory(line[2:])
2464 |       execute l1
2465 |       redraw!
2466 |       let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1)
2467 |       let force = force || answer > 1
2468 |       if answer
2469 |         let err = s:rm_rf(line[2:])
2470 |         setlocal modifiable
2471 |         if empty(err)
2472 |           call setline(l1, '~'.line[1:])
2473 |           let s:clean_count += 1
2474 |         else
2475 |           delete _
2476 |           call append(l1 - 1, s:format_message('x', line[1:], err))
2477 |           let l2 += len(s:lines(err))
2478 |           let err_count += 1
2479 |         endif
2480 |         let msg = printf('Removed %d directories.', s:clean_count)
2481 |         if err_count > 0
2482 |           let msg .= printf(' Failed to remove %d directories.', err_count)
2483 |         endif
2484 |         call setline(4, msg)
2485 |         setlocal nomodifiable
2486 |       endif
2487 |     endif
2488 |     let l1 += 1
2489 |   endwhile
2490 | endfunction
2491 | 
2492 | function! s:upgrade()
2493 |   echo 'Downloading the latest version of vim-plug'
2494 |   redraw
2495 |   let tmp = s:plug_tempname()
2496 |   let new = tmp . '/plug.vim'
2497 | 
2498 |   try
2499 |     let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp])
2500 |     if v:shell_error
2501 |       return s:err('Error upgrading vim-plug: '. out)
2502 |     endif
2503 | 
2504 |     if readfile(s:me) ==# readfile(new)
2505 |       echo 'vim-plug is already up-to-date'
2506 |       return 0
2507 |     else
2508 |       call rename(s:me, s:me . '.old')
2509 |       call rename(new, s:me)
2510 |       unlet g:loaded_plug
2511 |       echo 'vim-plug has been upgraded'
2512 |       return 1
2513 |     endif
2514 |   finally
2515 |     silent! call s:rm_rf(tmp)
2516 |   endtry
2517 | endfunction
2518 | 
2519 | function! s:upgrade_specs()
2520 |   for spec in values(g:plugs)
2521 |     let spec.frozen = get(spec, 'frozen', 0)
2522 |   endfor
2523 | endfunction
2524 | 
2525 | function! s:status()
2526 |   call s:prepare()
2527 |   call append(0, 'Checking plugins')
2528 |   call append(1, '')
2529 | 
2530 |   let ecnt = 0
2531 |   let unloaded = 0
2532 |   let [cnt, total] = [0, len(g:plugs)]
2533 |   for [name, spec] in items(g:plugs)
2534 |     let is_dir = isdirectory(spec.dir)
2535 |     if has_key(spec, 'uri')
2536 |       if is_dir
2537 |         let [err, _] = s:git_validate(spec, 1)
2538 |         let [valid, msg] = [empty(err), empty(err) ? 'OK' : err]
2539 |       else
2540 |         let [valid, msg] = [0, 'Not found. Try PlugInstall.']
2541 |       endif
2542 |     else
2543 |       if is_dir
2544 |         let [valid, msg] = [1, 'OK']
2545 |       else
2546 |         let [valid, msg] = [0, 'Not found.']
2547 |       endif
2548 |     endif
2549 |     let cnt += 1
2550 |     let ecnt += !valid
2551 |     " `s:loaded` entry can be missing if PlugUpgraded
2552 |     if is_dir && get(s:loaded, name, -1) == 0
2553 |       let unloaded = 1
2554 |       let msg .= ' (not loaded)'
2555 |     endif
2556 |     call s:progress_bar(2, repeat('=', cnt), total)
2557 |     call append(3, s:format_message(valid ? '-' : 'x', name, msg))
2558 |     normal! 2G
2559 |     redraw
2560 |   endfor
2561 |   call setline(1, 'Finished. '.ecnt.' error(s).')
2562 |   normal! gg
2563 |   setlocal nomodifiable
2564 |   if unloaded
2565 |     echo "Press 'L' on each line to load plugin, or 'U' to update"
2566 |     nnoremap   L :call status_load(line('.'))
2567 |     xnoremap   L :call status_load(line('.'))
2568 |   end
2569 | endfunction
2570 | 
2571 | function! s:extract_name(str, prefix, suffix)
2572 |   return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
2573 | endfunction
2574 | 
2575 | function! s:status_load(lnum)
2576 |   let line = getline(a:lnum)
2577 |   let name = s:extract_name(line, '-', '(not loaded)')
2578 |   if !empty(name)
2579 |     call plug#load(name)
2580 |     setlocal modifiable
2581 |     call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
2582 |     setlocal nomodifiable
2583 |   endif
2584 | endfunction
2585 | 
2586 | function! s:status_update() range
2587 |   let lines = getline(a:firstline, a:lastline)
2588 |   let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
2589 |   if !empty(names)
2590 |     echo
2591 |     execute 'PlugUpdate' join(names)
2592 |   endif
2593 | endfunction
2594 | 
2595 | function! s:is_preview_window_open()
2596 |   silent! wincmd P
2597 |   if &previewwindow
2598 |     wincmd p
2599 |     return 1
2600 |   endif
2601 | endfunction
2602 | 
2603 | function! s:find_name(lnum)
2604 |   for lnum in reverse(range(1, a:lnum))
2605 |     let line = getline(lnum)
2606 |     if empty(line)
2607 |       return ''
2608 |     endif
2609 |     let name = s:extract_name(line, '-', '')
2610 |     if !empty(name)
2611 |       return name
2612 |     endif
2613 |   endfor
2614 |   return ''
2615 | endfunction
2616 | 
2617 | function! s:preview_commit()
2618 |   if b:plug_preview < 0
2619 |     let b:plug_preview = !s:is_preview_window_open()
2620 |   endif
2621 | 
2622 |   let sha = matchstr(getline('.'), '^  \X*\zs[0-9a-f]\{7,9}')
2623 |   if empty(sha)
2624 |     let name = matchstr(getline('.'), '^- \zs[^:]*\ze:$')
2625 |     if empty(name)
2626 |       return
2627 |     endif
2628 |     let title = 'HEAD@{1}..'
2629 |     let command = 'git diff --no-color HEAD@{1}'
2630 |   else
2631 |     let title = sha
2632 |     let command = 'git show --no-color --pretty=medium '.sha
2633 |     let name = s:find_name(line('.'))
2634 |   endif
2635 | 
2636 |   if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
2637 |     return
2638 |   endif
2639 | 
2640 |   if exists('g:plug_pwindow') && !s:is_preview_window_open()
2641 |     execute g:plug_pwindow
2642 |     execute 'e' title
2643 |   else
2644 |     execute 'pedit' title
2645 |     wincmd P
2646 |   endif
2647 |   setlocal previewwindow filetype=git buftype=nofile bufhidden=wipe nobuflisted modifiable
2648 |   let batchfile = ''
2649 |   try
2650 |     let [sh, shellcmdflag, shrd] = s:chsh(1)
2651 |     let cmd = 'cd '.plug#shellescape(g:plugs[name].dir).' && '.command
2652 |     if s:is_win
2653 |       let [batchfile, cmd] = s:batchfile(cmd)
2654 |     endif
2655 |     execute 'silent %!' cmd
2656 |   finally
2657 |     let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd]
2658 |     if s:is_win && filereadable(batchfile)
2659 |       call delete(batchfile)
2660 |     endif
2661 |   endtry
2662 |   setlocal nomodifiable
2663 |   nnoremap   q :q
2664 |   wincmd p
2665 | endfunction
2666 | 
2667 | function! s:section(flags)
2668 |   call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
2669 | endfunction
2670 | 
2671 | function! s:format_git_log(line)
2672 |   let indent = '  '
2673 |   let tokens = split(a:line, nr2char(1))
2674 |   if len(tokens) != 5
2675 |     return indent.substitute(a:line, '\s*$', '', '')
2676 |   endif
2677 |   let [graph, sha, refs, subject, date] = tokens
2678 |   let tag = matchstr(refs, 'tag: [^,)]\+')
2679 |   let tag = empty(tag) ? ' ' : ' ('.tag.') '
2680 |   return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date)
2681 | endfunction
2682 | 
2683 | function! s:append_ul(lnum, text)
2684 |   call append(a:lnum, ['', a:text, repeat('-', len(a:text))])
2685 | endfunction
2686 | 
2687 | function! s:diff()
2688 |   call s:prepare()
2689 |   call append(0, ['Collecting changes ...', ''])
2690 |   let cnts = [0, 0]
2691 |   let bar = ''
2692 |   let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)')
2693 |   call s:progress_bar(2, bar, len(total))
2694 |   for origin in [1, 0]
2695 |     let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))'))))
2696 |     if empty(plugs)
2697 |       continue
2698 |     endif
2699 |     call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:')
2700 |     for [k, v] in plugs
2701 |       let branch = s:git_origin_branch(v)
2702 |       if len(branch)
2703 |         let range = origin ? '..origin/'.branch : 'HEAD@{1}..'
2704 |         let cmd = ['git', 'log', '--graph', '--color=never']
2705 |         if s:git_version_requirement(2, 10, 0)
2706 |           call add(cmd, '--no-show-signature')
2707 |         endif
2708 |         call extend(cmd, ['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range])
2709 |         if has_key(v, 'rtp')
2710 |           call extend(cmd, ['--', v.rtp])
2711 |         endif
2712 |         let diff = s:system_chomp(cmd, v.dir)
2713 |         if !empty(diff)
2714 |           let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : ''
2715 |           call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)')))
2716 |           let cnts[origin] += 1
2717 |         endif
2718 |       endif
2719 |       let bar .= '='
2720 |       call s:progress_bar(2, bar, len(total))
2721 |       normal! 2G
2722 |       redraw
2723 |     endfor
2724 |     if !cnts[origin]
2725 |       call append(5, ['', 'N/A'])
2726 |     endif
2727 |   endfor
2728 |   call setline(1, printf('%d plugin(s) updated.', cnts[0])
2729 |         \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : ''))
2730 | 
2731 |   if cnts[0] || cnts[1]
2732 |     nnoremap   (plug-preview) :silent! call preview_commit()
2733 |     if empty(maparg("\", 'n'))
2734 |       nmap   (plug-preview)
2735 |     endif
2736 |     if empty(maparg('o', 'n'))
2737 |       nmap  o (plug-preview)
2738 |     endif
2739 |   endif
2740 |   if cnts[0]
2741 |     nnoremap   X :call revert()
2742 |     echo "Press 'X' on each block to revert the update"
2743 |   endif
2744 |   normal! gg
2745 |   setlocal nomodifiable
2746 | endfunction
2747 | 
2748 | function! s:revert()
2749 |   if search('^Pending updates', 'bnW')
2750 |     return
2751 |   endif
2752 | 
2753 |   let name = s:find_name(line('.'))
2754 |   if empty(name) || !has_key(g:plugs, name) ||
2755 |     \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
2756 |     return
2757 |   endif
2758 | 
2759 |   call s:system('git reset --hard HEAD@{1} && git checkout '.plug#shellescape(g:plugs[name].branch).' --', g:plugs[name].dir)
2760 |   setlocal modifiable
2761 |   normal! "_dap
2762 |   setlocal nomodifiable
2763 |   echo 'Reverted'
2764 | endfunction
2765 | 
2766 | function! s:snapshot(force, ...) abort
2767 |   call s:prepare()
2768 |   setf vim
2769 |   call append(0, ['" Generated by vim-plug',
2770 |                 \ '" '.strftime("%c"),
2771 |                 \ '" :source this file in vim to restore the snapshot',
2772 |                 \ '" or execute: vim -S snapshot.vim',
2773 |                 \ '', '', 'PlugUpdate!'])
2774 |   1
2775 |   let anchor = line('$') - 3
2776 |   let names = sort(keys(filter(copy(g:plugs),
2777 |         \'has_key(v:val, "uri") && isdirectory(v:val.dir)')))
2778 |   for name in reverse(names)
2779 |     let sha = has_key(g:plugs[name], 'commit') ? g:plugs[name].commit : s:git_revision(g:plugs[name].dir)
2780 |     if !empty(sha)
2781 |       call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha))
2782 |       redraw
2783 |     endif
2784 |   endfor
2785 | 
2786 |   if a:0 > 0
2787 |     let fn = s:plug_expand(a:1)
2788 |     if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?'))
2789 |       return
2790 |     endif
2791 |     call writefile(getline(1, '$'), fn)
2792 |     echo 'Saved as '.a:1
2793 |     silent execute 'e' s:esc(fn)
2794 |     setf vim
2795 |   endif
2796 | endfunction
2797 | 
2798 | function! s:split_rtp()
2799 |   return split(&rtp, '\\\@