├── vim ├── .gitignore ├── ftdetect │ ├── babel.vim │ ├── eslint.vim │ ├── jest.vim │ ├── prettier.vim │ └── firebase.vim └── autoload │ └── plug.vim ├── bash_profile ├── .gitignore ├── .gitmodules ├── gitignore ├── setup-bash ├── gvimrc ├── README.md ├── setup-vim ├── setup ├── setup-helpers ├── setup-node ├── setup-homebrew ├── setup-git ├── bashrc ├── config └── karabiner │ ├── assets │ └── complex_modifications │ │ ├── 1556809246.json │ │ ├── 1556809716.json │ │ └── 1556809399.json │ └── karabiner.json ├── setup-mac └── vimrc /vim/.gitignore: -------------------------------------------------------------------------------- 1 | /.netrwhist 2 | /plugs 3 | -------------------------------------------------------------------------------- /bash_profile: -------------------------------------------------------------------------------- 1 | [ -s "$HOME/.bashrc" ] && . "$HOME/.bashrc" 2 | -------------------------------------------------------------------------------- /vim/ftdetect/babel.vim: -------------------------------------------------------------------------------- 1 | au BufNewFile,BufRead .babelrc set filetype=json 2 | -------------------------------------------------------------------------------- /vim/ftdetect/eslint.vim: -------------------------------------------------------------------------------- 1 | au BufNewFile,BufRead .eslintrc set filetype=json 2 | -------------------------------------------------------------------------------- /vim/ftdetect/jest.vim: -------------------------------------------------------------------------------- 1 | au BufNewFile,BufRead *.js.snap set filetype=javascript 2 | -------------------------------------------------------------------------------- /vim/ftdetect/prettier.vim: -------------------------------------------------------------------------------- 1 | au BufNewFile,BufRead .prettierrc set filetype=json 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /config/configstore/ 2 | /config/gcloud/ 3 | /config/karabiner/automatic_backups/ 4 | -------------------------------------------------------------------------------- /vim/ftdetect/firebase.vim: -------------------------------------------------------------------------------- 1 | au BufNewFile,BufRead .firebaserc,database.rules.json,firebase-rules.json set filetype=json 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "config/base16-shell"] 2 | path = config/base16-shell 3 | url = https://github.com/chriskempson/base16-shell.git 4 | -------------------------------------------------------------------------------- /gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | *.swo 4 | *.sublime-project 5 | *.sublime-workspace 6 | *.pyc 7 | .bundle 8 | .env 9 | .rvmrc 10 | .ruby-version 11 | 12 | npm-debug.log 13 | yarn-error.log 14 | -------------------------------------------------------------------------------- /setup-bash: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | source setup-helpers 6 | 7 | install_dotfile "bash_profile" 8 | install_dotfile "bashrc" 9 | 10 | mkdir -p $HOME/.config 11 | 12 | install_dotfile "config/base16-shell" 13 | -------------------------------------------------------------------------------- /gvimrc: -------------------------------------------------------------------------------- 1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | " Settings for MacVim 3 | 4 | " Font 5 | set guifont=Monaco:h14 6 | 7 | " Remove scrollbars 8 | set guioptions-=r 9 | set guioptions-=L 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## mjackson's dotfiles 2 | 3 | My dotfiles and setup scripts. Setup a new machine by running `./setup`. Or setup an individual component by running e.g. `./setup-vim`. 4 | 5 | The goal is for all scripts to be idempotent and able to run on any platform that I use, including: 6 | 7 | * macOS 8 | * ChromeOS 9 | -------------------------------------------------------------------------------- /setup-vim: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | source setup-helpers 6 | 7 | # Install vim 8 | # TODO: Make this cross-platform 9 | brew install vim 10 | 11 | mkdir -p "$HOME/.vim" 12 | mkdir -p "$HOME/.vim/swapfiles" 13 | 14 | install_dotfile "vim/autoload" 15 | install_dotfile "vimrc" 16 | 17 | vim +PlugInstall +qall 18 | -------------------------------------------------------------------------------- /setup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | uname=`uname` 6 | platform="unknown" 7 | if [[ $uname == "Darwin" ]]; then 8 | platform="macos" 9 | elif [[ $uname == "Linux" ]]; then 10 | platform="linux" 11 | fi 12 | 13 | if [ "$platform" == "macos" ]; then 14 | ./setup-homebrew 15 | ./setup-mac 16 | fi 17 | 18 | ./setup-bash 19 | ./setup-git 20 | ./setup-vim 21 | ./setup-node 22 | -------------------------------------------------------------------------------- /setup-helpers: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | fancy_echo() { 4 | local fmt="$1"; shift 5 | 6 | # shellcheck disable=SC2059 7 | printf "\n$fmt\n" "$@" 8 | } 9 | 10 | dotfiles_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 11 | 12 | install_dotfile() { 13 | local name="$1" 14 | local from="$dotfiles_dir/$name" 15 | local to="$HOME/.$name" 16 | 17 | if [ ! -e $to ]; then 18 | fancy_echo "Creating $to ..." 19 | ln -s $from $to 20 | fi 21 | } 22 | -------------------------------------------------------------------------------- /setup-node: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | # Install nvm 6 | curl -o- https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash 7 | 8 | # Install and use node version 10 by default 9 | nvm install 10 10 | nvm alias default 10 11 | 12 | # Install yarn 13 | curl -o- -L https://yarnpkg.com/install.sh | bash 14 | 15 | # Install a few packages globally 16 | yarn global add create-react-app 17 | yarn global add prettier 18 | yarn global add serve 19 | -------------------------------------------------------------------------------- /setup-homebrew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | homebrew_prefix="/usr/local" 6 | 7 | if [ -d "$homebrew_prefix" ]; then 8 | if ! [ -r "$homebrew_prefix" ]; then 9 | sudo chown -R "$LOGNAME:admin" /usr/local 10 | fi 11 | else 12 | sudo mkdir "$homebrew_prefix" 13 | sudo chflags norestricted "$homebrew_prefix" 14 | sudo chown -R "$LOGNAME:admin" "$homebrew_prefix" 15 | fi 16 | 17 | if ! command -v brew >/dev/null; then 18 | curl -fsS \ 19 | 'https://raw.githubusercontent.com/Homebrew/install/master/install' | ruby 20 | fi 21 | 22 | brew update 23 | -------------------------------------------------------------------------------- /setup-git: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | source setup-helpers 6 | 7 | # Install git 8 | # TODO: Make this cross-platform 9 | brew install git 10 | 11 | install_dotfile "gitignore" 12 | 13 | git config --global alias.br branch 14 | git config --global alias.cm commit 15 | git config --global alias.cmm "commit -m" 16 | git config --global alias.co checkout 17 | git config --global alias.lol "log --graph --decorate --pretty=oneline --abbrev-commit" 18 | git config --global alias.lola "log --graph --decorate --pretty=oneline --abbrev-commit --all" 19 | git config --global alias.ls ls-files 20 | git config --global alias.st status 21 | 22 | git config --global color.ui true 23 | 24 | git config --global core.autocrlf input 25 | git config --global core.editor vim 26 | git config --global core.eol lf 27 | git config --global core.excludesfile "$HOME/.gitignore" 28 | 29 | git config --global credential.helper osxkeychain 30 | 31 | git config --global push.default current 32 | 33 | git config --global url.https://.insteadOf git:// 34 | 35 | git config --global user.email "mj@mjackson.me" 36 | git config --global user.name "Michael Jackson" 37 | -------------------------------------------------------------------------------- /bashrc: -------------------------------------------------------------------------------- 1 | # Test for an interactive shell. There is no need to set anything 2 | # past this point for scp and rcp, and it's important to refrain from 3 | # outputting anything in those cases. 4 | if [[ $- != *i* ]] ; then 5 | # Shell is non-interactive. Be done now! 6 | return 7 | fi 8 | 9 | # Disable flow control (Ctrl+s will not suspend input to the terminal, so now 10 | # we can use it to open split panes in vim) 11 | stty -ixon -ixoff 12 | 13 | alias ls="ls -G" 14 | alias ll="ls -alG" 15 | 16 | export PATH="$PATH":"$HOME/.yarn/bin" 17 | export PATH="$PATH":"./node_modules/.bin" 18 | export PATH="$PATH":"/usr/local/opt/mysql-client/bin" 19 | export PATH="$PATH":"$HOME/Projects/flutter/bin" 20 | export PATH="$PATH":"$HOME/.pub-cache/bin" 21 | 22 | # Load base16 shell helpers 23 | BASE16_SHELL="$HOME/.config/base16-shell/" 24 | [ -n "$PS1" ] && [ -s "$BASE16_SHELL/profile_helper.sh" ] && eval "$($BASE16_SHELL/profile_helper.sh)" 25 | 26 | HOMEBREW_DIR=$(brew --prefix) 27 | 28 | # Set pager 29 | export PAGER="$HOMEBREW_DIR/bin/less" 30 | 31 | # Set Postgres data dir 32 | export PGDATA="$HOMEBREW_DIR/var/postgres" 33 | 34 | # Load homebrew bash completion 35 | [ -s "$HOMEBREW_DIR/etc/bash_completion" ] && . "$HOMEBREW_DIR/etc/bash_completion" 36 | 37 | # Load nvm 38 | export NVM_DIR="$HOME/.nvm" 39 | [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" 40 | [ -s "$NVM_DIR/bash_completion" ] && . "$NVM_DIR/bash_completion" 41 | 42 | # Load gcloud 43 | export GCLOUD_DIR="$HOMEBREW_DIR/Caskroom/google-cloud-sdk/latest/google-cloud-sdk" 44 | [ -s "$GCLOUD_DIR/completion.bash.inc" ] && . "$GCLOUD_DIR/completion.bash.inc" 45 | [ -s "$GCLOUD_DIR/path.bash.inc" ] && . "$GCLOUD_DIR/path.bash.inc" 46 | 47 | # Load Travis 48 | TRAVIS_DIR="$HOME/.travis" 49 | [ -s "$TRAVIS_DIR/travis.sh" ] && . "$TRAVIS_DIR/travis.sh" 50 | -------------------------------------------------------------------------------- /config/karabiner/assets/complex_modifications/1556809246.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Change control key", 3 | "rules": [ 4 | { 5 | "description": "Post escape if left_control is pressed alone.", 6 | "manipulators": [ 7 | { 8 | "type": "basic", 9 | "from": { 10 | "key_code": "left_control", 11 | "modifiers": { 12 | "optional": [ 13 | "any" 14 | ] 15 | } 16 | }, 17 | "to": [ 18 | { 19 | "key_code": "left_control" 20 | } 21 | ], 22 | "to_if_alone": [ 23 | { 24 | "key_code": "escape" 25 | } 26 | ] 27 | } 28 | ] 29 | }, 30 | { 31 | "description": "Post caps_lock if left_control is pressed alone.", 32 | "manipulators": [ 33 | { 34 | "type": "basic", 35 | "from": { 36 | "key_code": "left_control", 37 | "modifiers": { 38 | "optional": [ 39 | "any" 40 | ] 41 | } 42 | }, 43 | "to": [ 44 | { 45 | "key_code": "left_control" 46 | } 47 | ], 48 | "to_if_alone": [ 49 | { 50 | "key_code": "caps_lock" 51 | } 52 | ] 53 | } 54 | ] 55 | }, 56 | { 57 | "description": "Post return_or_enter if right_control is pressed alone.", 58 | "manipulators": [ 59 | { 60 | "type": "basic", 61 | "from": { 62 | "key_code": "right_control", 63 | "modifiers": { 64 | "optional": [ 65 | "any" 66 | ] 67 | } 68 | }, 69 | "to": [ 70 | { 71 | "key_code": "right_control" 72 | } 73 | ], 74 | "to_if_alone": [ 75 | { 76 | "key_code": "return_or_enter" 77 | } 78 | ] 79 | } 80 | ] 81 | } 82 | ] 83 | } 84 | -------------------------------------------------------------------------------- /setup-mac: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Based on https://github.com/mathiasbynens/dotfiles/blob/master/.macos 4 | 5 | set -e 6 | 7 | # Close any open System Preferences panes, to prevent them from overriding 8 | # settings we’re about to change 9 | osascript -e 'tell application "System Preferences" to quit' 10 | 11 | # Ask for the administrator password upfront 12 | sudo -v 13 | 14 | # Disable the sound effects on boot 15 | sudo nvram SystemAudioVolume=" " 16 | 17 | # Increase the limit for max opened files. 18 | sudo launchctl limit maxfiles 2048 unlimited 19 | 20 | # Show the ~/Library folder 21 | chflags nohidden ~/Library 22 | 23 | # Show the /Volumes folder 24 | sudo chflags nohidden /Volumes 25 | 26 | # Save to disk (not to iCloud) by default 27 | defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false 28 | 29 | # Automatically quit printer app once the print jobs complete 30 | defaults write com.apple.print.PrintingPrefs "Quit When Finished" -bool true 31 | 32 | # Disable the “Are you sure you want to open this application?” dialog 33 | defaults write com.apple.LaunchServices LSQuarantine -bool false 34 | 35 | # Disable Resume system-wide 36 | defaults write com.apple.systempreferences NSQuitAlwaysKeepsWindows -bool false 37 | 38 | # Restart automatically if the computer freezes 39 | sudo systemsetup -setrestartfreeze on 40 | 41 | # Disable auto-correct 42 | defaults write NSGlobalDomain NSAutomaticSpellingCorrectionEnabled -bool false 43 | 44 | # Use scroll gesture with the Ctrl (^) modifier key to zoom 45 | defaults write com.apple.universalaccess closeViewScrollWheelToggle -bool true 46 | defaults write com.apple.universalaccess HIDScrollZoomModifierMask -int 262144 47 | 48 | # Follow the keyboard focus while zoomed in 49 | defaults write com.apple.universalaccess closeViewZoomFollowsFocus -bool true 50 | 51 | # Disable press-and-hold for keys in favor of key repeat 52 | defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool false 53 | 54 | # Set a fast keyboard repeat rate 55 | defaults write NSGlobalDomain KeyRepeat -int 1 56 | defaults write NSGlobalDomain InitialKeyRepeat -int 15 57 | 58 | # Show language menu in the top right corner of the boot screen 59 | sudo defaults write /Library/Preferences/com.apple.loginwindow showInputMenu -bool true 60 | 61 | # Stop iTunes from responding to the keyboard media keys 62 | launchctl unload -w /System/Library/LaunchAgents/com.apple.rcd.plist 2> /dev/null 63 | 64 | # Require password 30s after sleep or screen saver begins 65 | defaults write com.apple.screensaver askForPassword -int 1 66 | defaults write com.apple.screensaver askForPasswordDelay -int 30 67 | 68 | # Display full POSIX path as Finder window title 69 | defaults write com.apple.finder _FXShowPosixPathInTitle -bool true 70 | 71 | # Keep folders on top when sorting by name 72 | defaults write com.apple.finder _FXSortFoldersFirst -bool true 73 | 74 | # Disable the warning when changing a file extension 75 | defaults write com.apple.finder FXEnableExtensionChangeWarning -bool false 76 | 77 | # Enable spring loading for directories 78 | defaults write NSGlobalDomain com.apple.springing.enabled -bool true 79 | 80 | # Remove the spring loading delay for directories 81 | defaults write NSGlobalDomain com.apple.springing.delay -float 0 82 | 83 | # Disable the warning before emptying the Trash 84 | defaults write com.apple.finder WarnOnEmptyTrash -bool false 85 | 86 | # Hot corners 87 | # Possible values: 88 | # 0: no-op 89 | # 2: Mission Control 90 | # 3: Show application windows 91 | # 4: Desktop 92 | # 5: Start screen saver 93 | # 6: Disable screen saver 94 | # 7: Dashboard 95 | # 10: Put display to sleep 96 | # 11: Launchpad 97 | # 12: Notification Center 98 | # Top left screen corner → Mission Control 99 | defaults write com.apple.dock wvous-tl-corner -int 2 100 | defaults write com.apple.dock wvous-tl-modifier -int 0 101 | # Top right screen corner → Desktop 102 | defaults write com.apple.dock wvous-tr-corner -int 4 103 | defaults write com.apple.dock wvous-tr-modifier -int 0 104 | # Bottom left screen corner → Start screen saver 105 | defaults write com.apple.dock wvous-bl-corner -int 5 106 | defaults write com.apple.dock wvous-bl-modifier -int 0 107 | 108 | # Only use UTF-8 in Terminal.app 109 | defaults write com.apple.terminal StringEncodings -array 4 110 | -------------------------------------------------------------------------------- /config/karabiner/assets/complex_modifications/1556809716.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Change caps_lock key (rev 4)", 3 | "rules": [ 4 | { 5 | "description": "Change caps_lock key to command+control+option+shift if pressed with other keys", 6 | "manipulators": [ 7 | { 8 | "type": "basic", 9 | "from": { 10 | "key_code": "caps_lock", 11 | "modifiers": { 12 | "optional": [ 13 | "any" 14 | ] 15 | } 16 | }, 17 | "to": [ 18 | { 19 | "key_code": "left_shift", 20 | "modifiers": [ 21 | "left_command", 22 | "left_control", 23 | "left_option" 24 | ] 25 | } 26 | ], 27 | "to_if_alone": [ 28 | { 29 | "hold_down_milliseconds": 100, 30 | "key_code": "caps_lock" 31 | } 32 | ] 33 | } 34 | ] 35 | }, 36 | { 37 | "description": "Change caps_lock key to command+control+option+shift. (Post escape key when pressed alone)", 38 | "manipulators": [ 39 | { 40 | "type": "basic", 41 | "from": { 42 | "key_code": "caps_lock", 43 | "modifiers": { 44 | "optional": [ 45 | "any" 46 | ] 47 | } 48 | }, 49 | "to": [ 50 | { 51 | "key_code": "left_shift", 52 | "modifiers": [ 53 | "left_command", 54 | "left_control", 55 | "left_option" 56 | ] 57 | } 58 | ], 59 | "to_if_alone": [ 60 | { 61 | "key_code": "escape" 62 | } 63 | ] 64 | } 65 | ] 66 | }, 67 | { 68 | "description": "Change caps_lock key to command+control+option+shift. (Post f19 key when pressed alone)", 69 | "manipulators": [ 70 | { 71 | "type": "basic", 72 | "from": { 73 | "key_code": "caps_lock", 74 | "modifiers": { 75 | "optional": [ 76 | "any" 77 | ] 78 | } 79 | }, 80 | "to": [ 81 | { 82 | "key_code": "left_shift", 83 | "modifiers": [ 84 | "left_command", 85 | "left_control", 86 | "left_option" 87 | ] 88 | } 89 | ], 90 | "to_if_alone": [ 91 | { 92 | "key_code": "f19" 93 | } 94 | ] 95 | } 96 | ] 97 | }, 98 | { 99 | "description": "Change caps_lock key to command+control+option+shift. (Use shift+caps_lock as caps_lock)", 100 | "manipulators": [ 101 | { 102 | "type": "basic", 103 | "from": { 104 | "key_code": "caps_lock", 105 | "modifiers": { 106 | "mandatory": [ 107 | "shift" 108 | ], 109 | "optional": [ 110 | "caps_lock" 111 | ] 112 | } 113 | }, 114 | "to": [ 115 | { 116 | "key_code": "caps_lock" 117 | } 118 | ] 119 | }, 120 | { 121 | "type": "basic", 122 | "from": { 123 | "key_code": "caps_lock", 124 | "modifiers": { 125 | "optional": [ 126 | "any" 127 | ] 128 | } 129 | }, 130 | "to": [ 131 | { 132 | "key_code": "left_shift", 133 | "modifiers": [ 134 | "left_command", 135 | "left_control", 136 | "left_option" 137 | ] 138 | } 139 | ] 140 | } 141 | ] 142 | }, 143 | { 144 | "description": "Change caps_lock to control if pressed with other keys, to escape if pressed alone.", 145 | "manipulators": [ 146 | { 147 | "type": "basic", 148 | "from": { 149 | "key_code": "caps_lock", 150 | "modifiers": { 151 | "optional": [ 152 | "any" 153 | ] 154 | } 155 | }, 156 | "to": [ 157 | { 158 | "key_code": "left_control" 159 | } 160 | ], 161 | "to_if_alone": [ 162 | { 163 | "key_code": "escape" 164 | } 165 | ] 166 | } 167 | ] 168 | }, 169 | { 170 | "description": "Change caps_lock to control if pressed with other keys. (rev 2)", 171 | "manipulators": [ 172 | { 173 | "type": "basic", 174 | "from": { 175 | "key_code": "caps_lock", 176 | "modifiers": { 177 | "optional": [ 178 | "any" 179 | ] 180 | } 181 | }, 182 | "to": [ 183 | { 184 | "key_code": "left_control" 185 | } 186 | ], 187 | "to_if_alone": [ 188 | { 189 | "key_code": "caps_lock", 190 | "hold_down_milliseconds": 500 191 | } 192 | ] 193 | } 194 | ] 195 | }, 196 | { 197 | "description": "Disable caps_lock delay (rev 1)", 198 | "manipulators": [ 199 | { 200 | "type": "basic", 201 | "from": { 202 | "key_code": "caps_lock", 203 | "modifiers": { 204 | "optional": [ 205 | "any" 206 | ] 207 | } 208 | }, 209 | "to": [ 210 | { 211 | "key_code": "caps_lock", 212 | "hold_down_milliseconds": 200 213 | }, 214 | { 215 | "key_code": "vk_none" 216 | } 217 | ] 218 | } 219 | ] 220 | } 221 | ] 222 | } 223 | -------------------------------------------------------------------------------- /vimrc: -------------------------------------------------------------------------------- 1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | " Plugins 3 | 4 | call plug#begin('~/.vim/plugs') 5 | 6 | Plug 'tpope/vim-sensible' 7 | Plug 'tpope/vim-surround' 8 | Plug 'tpope/vim-vinegar' 9 | " Plug 'preservim/nerdtree' 10 | Plug 'tpope/vim-sleuth' 11 | Plug 'tpope/vim-commentary' 12 | Plug 'tpope/vim-repeat' 13 | Plug 'wellle/targets.vim' 14 | Plug 'junegunn/goyo.vim' 15 | Plug 'junegunn/limelight.vim' 16 | "Plug 'rstacruz/vim-closer' 17 | "Plug 'Raimondi/delimitMate' 18 | "Plug 'tpope/vim-endwise' 19 | Plug 'jiangmiao/auto-pairs' 20 | Plug 'ervandew/supertab' 21 | "Plug 'lifepillar/vim-mucomplete' 22 | Plug 'wincent/command-t', { 23 | \ 'do': 'cd ruby/command-t/ext/command-t && ruby extconf.rb && make' } 24 | Plug 'wincent/terminus' 25 | Plug 'sheerun/vim-polyglot' 26 | Plug 'jxnblk/vim-mdx-js' 27 | Plug 'mileszs/ack.vim' 28 | Plug 'chriskempson/base16-vim' 29 | Plug 'vim-airline/vim-airline' 30 | Plug 'vim-airline/vim-airline-themes' 31 | Plug 'dense-analysis/ale' 32 | 33 | call plug#end() 34 | 35 | " Keep .swp files in uniquely-named files in $HOME/.vim/swapfiles 36 | set directory=$HOME/.vim/swapfiles// 37 | 38 | " Use , as 39 | let mapleader = "," 40 | 41 | " Use %% on the command line to expand to the dir of the current file 42 | cnoremap %% expand("%:h") . "/" 43 | 44 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 45 | " Editing 46 | 47 | " Use . in visual mode to execute the dot command on each selected line 48 | xnoremap . :normal . 49 | 50 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 51 | " Formatting 52 | 53 | set expandtab " use spaces instead of tabs 54 | set tabstop=2 " use 2 char width for tabs 55 | set shiftwidth=2 " use 2 spaces for indent 56 | set textwidth=80 " auto wrap text at 80 chars 57 | 58 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 59 | " Linting 60 | 61 | set signcolumn=yes " always show the signcolumn on LH side 62 | let g:ale_set_highlights = 0 " don't highlight first char of errors 63 | let g:ale_completion_enabled = 1 " enable completion when available 64 | 65 | let g:ale_linters = { 66 | \ 'javascript': ['eslint'], 67 | \ 'typescript': ['eslint', 'tsserver'] 68 | \} 69 | let g:ale_linters_ignore = { 70 | \ 'typescript': ['tslint'] 71 | \} 72 | 73 | let g:ale_fix_on_save = 1 74 | let g:ale_fixers = { 75 | \ 'css': ['prettier'], 76 | \ 'html': ['prettier'], 77 | \ 'javascript': ['prettier'], 78 | \ 'typescript': ['prettier'], 79 | \ 'json': ['prettier'] 80 | \} 81 | 82 | " Use aj or ak for quickly jumping between lint errors 83 | nmap aj :ALENext 84 | nmap ak :ALEPrevious 85 | 86 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 87 | " Movement 88 | 89 | " Always move linewise in normal mode 90 | nnoremap k gk 91 | nnoremap j gj 92 | 93 | " Preserve indentation when moving lines 94 | " See http://vim.wikia.com/wiki/Moving_lines_up_or_down 95 | nnoremap :m .+1== 96 | nnoremap :m .-2== 97 | vnoremap :m '>+1gv=gv 98 | vnoremap :m '<-2gv=gv 99 | 100 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 101 | " Appearance 102 | 103 | set splitbelow " open split panes on bottom (instead of top) 104 | set splitright " open split panes on right (instead of left) 105 | 106 | set laststatus=2 " always show status bar 107 | 108 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 109 | " Copy/paste 110 | 111 | set clipboard=unnamed " copy to the clipboard when yanking 112 | 113 | " Re-select the last pasted text with `gp` 114 | nnoremap gp `[v`] 115 | 116 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 117 | " Line numbers 118 | 119 | set number " show line numbers 120 | 121 | " Toggle line numbers with n 122 | noremap n :set number! 123 | 124 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 125 | " Wrapping 126 | 127 | set wrap " wrap long lines by default 128 | set linebreak " when wrapping, break on word boundaries 129 | 130 | " Toggle wrapping with w 131 | noremap w :set wrap! 132 | 133 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 134 | " Searching 135 | 136 | set ignorecase " ignore case when searching 137 | set smartcase " don't ignore case if using any uppercase 138 | set hlsearch " highlight matches 139 | 140 | " Clear highlighting for the current search with / 141 | " Adapted from https://stackoverflow.com/questions/657447/vim-clear-last-search-highlighting/657484#657484 142 | nnoremap / :let @/ = "" 143 | 144 | " Open :Ack with a 145 | nnoremap a :Ack 146 | vnoremap a :Ack 147 | 148 | " Ignore node_modules with command-t 149 | let g:CommandTWildIgnore = &wildignore . ",*/node_modules" 150 | 151 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 152 | " Syntax 153 | 154 | let g:jsx_ext_required = 0 " allow JSX in .js files 155 | let g:javascript_plugin_flow = 1 " allow Flow in .js files 156 | 157 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 158 | " Colors 159 | 160 | set termguicolors " enable gui colors in the terminal 161 | 162 | " Use current terminal color scheme for vim 163 | if filereadable(expand("~/.vimrc_background")) 164 | "let base16colorspace = 256 165 | source ~/.vimrc_background 166 | endif 167 | 168 | " Sync vim-airline colors with current base16 color scheme 169 | let g:airline_theme='base16' 170 | 171 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 172 | " Netrw 173 | 174 | " Allow netrw to remove non-empty local directories 175 | let g:netrw_localrmdir = "trash" 176 | 177 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 178 | " Goyo 179 | 180 | " Automatically enable/disable Limelight with Goyo 181 | autocmd! User GoyoEnter Limelight 182 | autocmd! User GoyoLeave Limelight! 183 | 184 | let g:goyo_width = 82 " give Goyo 1 column on either side 185 | -------------------------------------------------------------------------------- /config/karabiner/karabiner.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "check_for_updates_on_startup": true, 4 | "show_in_menu_bar": false, 5 | "show_profile_name_in_menu_bar": false 6 | }, 7 | "profiles": [ 8 | { 9 | "complex_modifications": { 10 | "parameters": { 11 | "basic.simultaneous_threshold_milliseconds": 50, 12 | "basic.to_delayed_action_delay_milliseconds": 500, 13 | "basic.to_if_alone_timeout_milliseconds": 1000, 14 | "basic.to_if_held_down_threshold_milliseconds": 500, 15 | "mouse_motion_to_scroll.speed": 100 16 | }, 17 | "rules": [] 18 | }, 19 | "devices": [ 20 | { 21 | "disable_built_in_keyboard_if_exists": false, 22 | "fn_function_keys": [], 23 | "identifiers": { 24 | "is_keyboard": true, 25 | "is_pointing_device": false, 26 | "product_id": 1957, 27 | "vendor_id": 1118 28 | }, 29 | "ignore": false, 30 | "manipulate_caps_lock_led": false, 31 | "simple_modifications": [ 32 | { 33 | "from": { 34 | "key_code": "application" 35 | }, 36 | "to": { 37 | "key_code": "right_option" 38 | } 39 | }, 40 | { 41 | "from": { 42 | "key_code": "left_command" 43 | }, 44 | "to": { 45 | "key_code": "left_option" 46 | } 47 | }, 48 | { 49 | "from": { 50 | "key_code": "left_option" 51 | }, 52 | "to": { 53 | "key_code": "left_command" 54 | } 55 | }, 56 | { 57 | "from": { 58 | "key_code": "pause" 59 | }, 60 | "to": { 61 | "consumer_key_code": "play_or_pause" 62 | } 63 | }, 64 | { 65 | "from": { 66 | "key_code": "print_screen" 67 | }, 68 | "to": { 69 | "consumer_key_code": "rewind" 70 | } 71 | }, 72 | { 73 | "from": { 74 | "key_code": "right_option" 75 | }, 76 | "to": { 77 | "key_code": "right_command" 78 | } 79 | }, 80 | { 81 | "from": { 82 | "key_code": "scroll_lock" 83 | }, 84 | "to": { 85 | "consumer_key_code": "fastforward" 86 | } 87 | } 88 | ] 89 | }, 90 | { 91 | "disable_built_in_keyboard_if_exists": false, 92 | "fn_function_keys": [], 93 | "identifiers": { 94 | "is_keyboard": true, 95 | "is_pointing_device": false, 96 | "product_id": 256, 97 | "vendor_id": 2131 98 | }, 99 | "ignore": false, 100 | "manipulate_caps_lock_led": false, 101 | "simple_modifications": [] 102 | }, 103 | { 104 | "disable_built_in_keyboard_if_exists": false, 105 | "fn_function_keys": [], 106 | "identifiers": { 107 | "is_keyboard": true, 108 | "is_pointing_device": false, 109 | "product_id": 630, 110 | "vendor_id": 1452 111 | }, 112 | "ignore": false, 113 | "manipulate_caps_lock_led": true, 114 | "simple_modifications": [ 115 | { 116 | "from": { 117 | "key_code": "caps_lock" 118 | }, 119 | "to": { 120 | "key_code": "escape" 121 | } 122 | } 123 | ] 124 | } 125 | ], 126 | "fn_function_keys": [ 127 | { 128 | "from": { 129 | "key_code": "f1" 130 | }, 131 | "to": { 132 | "consumer_key_code": "display_brightness_decrement" 133 | } 134 | }, 135 | { 136 | "from": { 137 | "key_code": "f2" 138 | }, 139 | "to": { 140 | "consumer_key_code": "display_brightness_increment" 141 | } 142 | }, 143 | { 144 | "from": { 145 | "key_code": "f3" 146 | }, 147 | "to": { 148 | "key_code": "mission_control" 149 | } 150 | }, 151 | { 152 | "from": { 153 | "key_code": "f4" 154 | }, 155 | "to": { 156 | "key_code": "launchpad" 157 | } 158 | }, 159 | { 160 | "from": { 161 | "key_code": "f5" 162 | }, 163 | "to": { 164 | "key_code": "illumination_decrement" 165 | } 166 | }, 167 | { 168 | "from": { 169 | "key_code": "f6" 170 | }, 171 | "to": { 172 | "key_code": "illumination_increment" 173 | } 174 | }, 175 | { 176 | "from": { 177 | "key_code": "f7" 178 | }, 179 | "to": { 180 | "consumer_key_code": "rewind" 181 | } 182 | }, 183 | { 184 | "from": { 185 | "key_code": "f8" 186 | }, 187 | "to": { 188 | "consumer_key_code": "play_or_pause" 189 | } 190 | }, 191 | { 192 | "from": { 193 | "key_code": "f9" 194 | }, 195 | "to": { 196 | "consumer_key_code": "fastforward" 197 | } 198 | }, 199 | { 200 | "from": { 201 | "key_code": "f10" 202 | }, 203 | "to": { 204 | "consumer_key_code": "mute" 205 | } 206 | }, 207 | { 208 | "from": { 209 | "key_code": "f11" 210 | }, 211 | "to": { 212 | "consumer_key_code": "volume_decrement" 213 | } 214 | }, 215 | { 216 | "from": { 217 | "key_code": "f12" 218 | }, 219 | "to": { 220 | "consumer_key_code": "volume_increment" 221 | } 222 | } 223 | ], 224 | "name": "Default profile", 225 | "parameters": { 226 | "delay_milliseconds_before_open_device": 1000 227 | }, 228 | "selected": true, 229 | "simple_modifications": [ 230 | { 231 | "from": { 232 | "key_code": "caps_lock" 233 | }, 234 | "to": { 235 | "key_code": "escape" 236 | } 237 | } 238 | ], 239 | "virtual_hid_keyboard": { 240 | "country_code": 0, 241 | "mouse_key_xy_scale": 100 242 | } 243 | } 244 | ] 245 | } -------------------------------------------------------------------------------- /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 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' } 26 | " Plug 'tpope/vim-fireplace', { 'for': 'clojure' } 27 | " 28 | " " Using a non-master 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 | let s:me = resolve(expand(':p')) 103 | let s:base_spec = { 'branch': 'master', 'frozen': 0 } 104 | let s:TYPE = { 105 | \ 'string': type(''), 106 | \ 'list': type([]), 107 | \ 'dict': type({}), 108 | \ 'funcref': type(function('call')) 109 | \ } 110 | let s:loaded = get(s:, 'loaded', {}) 111 | let s:triggers = get(s:, 'triggers', {}) 112 | 113 | function! plug#begin(...) 114 | if a:0 > 0 115 | let s:plug_home_org = a:1 116 | let home = s:path(fnamemodify(expand(a:1), ':p')) 117 | elseif exists('g:plug_home') 118 | let home = s:path(g:plug_home) 119 | elseif !empty(&rtp) 120 | let home = s:path(split(&rtp, ',')[0]) . '/plugged' 121 | else 122 | return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.') 123 | endif 124 | if fnamemodify(home, ':t') ==# 'plugin' && fnamemodify(home, ':h') ==# s:first_rtp 125 | return s:err('Invalid plug home. '.home.' is a standard Vim runtime path and is not allowed.') 126 | endif 127 | 128 | let g:plug_home = home 129 | let g:plugs = {} 130 | let g:plugs_order = [] 131 | let s:triggers = {} 132 | 133 | call s:define_commands() 134 | return 1 135 | endfunction 136 | 137 | function! s:define_commands() 138 | command! -nargs=+ -bar Plug call plug#() 139 | if !executable('git') 140 | return s:err('`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.') 141 | endif 142 | command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install(0, []) 143 | command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update(0, []) 144 | command! -nargs=0 -bar -bang PlugClean call s:clean(0) 145 | command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif 146 | command! -nargs=0 -bar PlugStatus call s:status() 147 | command! -nargs=0 -bar PlugDiff call s:diff() 148 | command! -nargs=? -bar -bang -complete=file PlugSnapshot call s:snapshot(0, ) 149 | endfunction 150 | 151 | function! s:to_a(v) 152 | return type(a:v) == s:TYPE.list ? a:v : [a:v] 153 | endfunction 154 | 155 | function! s:to_s(v) 156 | return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" 157 | endfunction 158 | 159 | function! s:glob(from, pattern) 160 | return s:lines(globpath(a:from, a:pattern)) 161 | endfunction 162 | 163 | function! s:source(from, ...) 164 | let found = 0 165 | for pattern in a:000 166 | for vim in s:glob(a:from, pattern) 167 | execute 'source' s:esc(vim) 168 | let found = 1 169 | endfor 170 | endfor 171 | return found 172 | endfunction 173 | 174 | function! s:assoc(dict, key, val) 175 | let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) 176 | endfunction 177 | 178 | function! s:ask(message, ...) 179 | call inputsave() 180 | echohl WarningMsg 181 | let answer = input(a:message.(a:0 ? ' (y/N/a) ' : ' (y/N) ')) 182 | echohl None 183 | call inputrestore() 184 | echo "\r" 185 | return (a:0 && answer =~? '^a') ? 2 : (answer =~? '^y') ? 1 : 0 186 | endfunction 187 | 188 | function! s:ask_no_interrupt(...) 189 | try 190 | return call('s:ask', a:000) 191 | catch 192 | return 0 193 | endtry 194 | endfunction 195 | 196 | function! s:lazy(plug, opt) 197 | return has_key(a:plug, a:opt) && 198 | \ (empty(s:to_a(a:plug[a:opt])) || 199 | \ !isdirectory(a:plug.dir) || 200 | \ len(s:glob(s:rtp(a:plug), 'plugin')) || 201 | \ len(s:glob(s:rtp(a:plug), 'after/plugin'))) 202 | endfunction 203 | 204 | function! plug#end() 205 | if !exists('g:plugs') 206 | return s:err('Call plug#begin() first') 207 | endif 208 | 209 | if exists('#PlugLOD') 210 | augroup PlugLOD 211 | autocmd! 212 | augroup END 213 | augroup! PlugLOD 214 | endif 215 | let lod = { 'ft': {}, 'map': {}, 'cmd': {} } 216 | 217 | if exists('g:did_load_filetypes') 218 | filetype off 219 | endif 220 | for name in g:plugs_order 221 | if !has_key(g:plugs, name) 222 | continue 223 | endif 224 | let plug = g:plugs[name] 225 | if get(s:loaded, name, 0) || !s:lazy(plug, 'on') && !s:lazy(plug, 'for') 226 | let s:loaded[name] = 1 227 | continue 228 | endif 229 | 230 | if has_key(plug, 'on') 231 | let s:triggers[name] = { 'map': [], 'cmd': [] } 232 | for cmd in s:to_a(plug.on) 233 | if cmd =~? '^.\+' 234 | if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) 235 | call s:assoc(lod.map, cmd, name) 236 | endif 237 | call add(s:triggers[name].map, cmd) 238 | elseif cmd =~# '^[A-Z]' 239 | let cmd = substitute(cmd, '!*$', '', '') 240 | if exists(':'.cmd) != 2 241 | call s:assoc(lod.cmd, cmd, name) 242 | endif 243 | call add(s:triggers[name].cmd, cmd) 244 | else 245 | call s:err('Invalid `on` option: '.cmd. 246 | \ '. Should start with an uppercase letter or ``.') 247 | endif 248 | endfor 249 | endif 250 | 251 | if has_key(plug, 'for') 252 | let types = s:to_a(plug.for) 253 | if !empty(types) 254 | augroup filetypedetect 255 | call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') 256 | augroup END 257 | endif 258 | for type in types 259 | call s:assoc(lod.ft, type, name) 260 | endfor 261 | endif 262 | endfor 263 | 264 | for [cmd, names] in items(lod.cmd) 265 | execute printf( 266 | \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , %s)', 267 | \ cmd, string(cmd), string(names)) 268 | endfor 269 | 270 | for [map, names] in items(lod.map) 271 | for [mode, map_prefix, key_prefix] in 272 | \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] 273 | execute printf( 274 | \ '%snoremap %s %s:call lod_map(%s, %s, %s, "%s")', 275 | \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) 276 | endfor 277 | endfor 278 | 279 | for [ft, names] in items(lod.ft) 280 | augroup PlugLOD 281 | execute printf('autocmd FileType %s call lod_ft(%s, %s)', 282 | \ ft, string(ft), string(names)) 283 | augroup END 284 | endfor 285 | 286 | call s:reorg_rtp() 287 | filetype plugin indent on 288 | if has('vim_starting') 289 | if has('syntax') && !exists('g:syntax_on') 290 | syntax enable 291 | end 292 | else 293 | call s:reload_plugins() 294 | endif 295 | endfunction 296 | 297 | function! s:loaded_names() 298 | return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') 299 | endfunction 300 | 301 | function! s:load_plugin(spec) 302 | call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') 303 | endfunction 304 | 305 | function! s:reload_plugins() 306 | for name in s:loaded_names() 307 | call s:load_plugin(g:plugs[name]) 308 | endfor 309 | endfunction 310 | 311 | function! s:trim(str) 312 | return substitute(a:str, '[\/]\+$', '', '') 313 | endfunction 314 | 315 | function! s:version_requirement(val, min) 316 | for idx in range(0, len(a:min) - 1) 317 | let v = get(a:val, idx, 0) 318 | if v < a:min[idx] | return 0 319 | elseif v > a:min[idx] | return 1 320 | endif 321 | endfor 322 | return 1 323 | endfunction 324 | 325 | function! s:git_version_requirement(...) 326 | if !exists('s:git_version') 327 | let s:git_version = map(split(split(s:system('git --version'))[2], '\.'), 'str2nr(v:val)') 328 | endif 329 | return s:version_requirement(s:git_version, a:000) 330 | endfunction 331 | 332 | function! s:progress_opt(base) 333 | return a:base && !s:is_win && 334 | \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' 335 | endfunction 336 | 337 | if s:is_win 338 | function! s:rtp(spec) 339 | return s:path(a:spec.dir . get(a:spec, 'rtp', '')) 340 | endfunction 341 | 342 | function! s:path(path) 343 | return s:trim(substitute(a:path, '/', '\', 'g')) 344 | endfunction 345 | 346 | function! s:dirpath(path) 347 | return s:path(a:path) . '\' 348 | endfunction 349 | 350 | function! s:is_local_plug(repo) 351 | return a:repo =~? '^[a-z]:\|^[%~]' 352 | endfunction 353 | else 354 | function! s:rtp(spec) 355 | return s:dirpath(a:spec.dir . get(a:spec, 'rtp', '')) 356 | endfunction 357 | 358 | function! s:path(path) 359 | return s:trim(a:path) 360 | endfunction 361 | 362 | function! s:dirpath(path) 363 | return substitute(a:path, '[/\\]*$', '/', '') 364 | endfunction 365 | 366 | function! s:is_local_plug(repo) 367 | return a:repo[0] =~ '[/$~]' 368 | endfunction 369 | endif 370 | 371 | function! s:err(msg) 372 | echohl ErrorMsg 373 | echom '[vim-plug] '.a:msg 374 | echohl None 375 | endfunction 376 | 377 | function! s:warn(cmd, msg) 378 | echohl WarningMsg 379 | execute a:cmd 'a:msg' 380 | echohl None 381 | endfunction 382 | 383 | function! s:esc(path) 384 | return escape(a:path, ' ') 385 | endfunction 386 | 387 | function! s:escrtp(path) 388 | return escape(a:path, ' ,') 389 | endfunction 390 | 391 | function! s:remove_rtp() 392 | for name in s:loaded_names() 393 | let rtp = s:rtp(g:plugs[name]) 394 | execute 'set rtp-='.s:escrtp(rtp) 395 | let after = globpath(rtp, 'after') 396 | if isdirectory(after) 397 | execute 'set rtp-='.s:escrtp(after) 398 | endif 399 | endfor 400 | endfunction 401 | 402 | function! s:reorg_rtp() 403 | if !empty(s:first_rtp) 404 | execute 'set rtp-='.s:first_rtp 405 | execute 'set rtp-='.s:last_rtp 406 | endif 407 | 408 | " &rtp is modified from outside 409 | if exists('s:prtp') && s:prtp !=# &rtp 410 | call s:remove_rtp() 411 | unlet! s:middle 412 | endif 413 | 414 | let s:middle = get(s:, 'middle', &rtp) 415 | let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') 416 | let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') 417 | let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') 418 | \ . ','.s:middle.',' 419 | \ . join(map(afters, 'escape(v:val, ",")'), ',') 420 | let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') 421 | let s:prtp = &rtp 422 | 423 | if !empty(s:first_rtp) 424 | execute 'set rtp^='.s:first_rtp 425 | execute 'set rtp+='.s:last_rtp 426 | endif 427 | endfunction 428 | 429 | function! s:doautocmd(...) 430 | if exists('#'.join(a:000, '#')) 431 | execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '' : '') join(a:000) 432 | endif 433 | endfunction 434 | 435 | function! s:dobufread(names) 436 | for name in a:names 437 | let path = s:rtp(g:plugs[name]) 438 | for dir in ['ftdetect', 'ftplugin', 'after/ftdetect', 'after/ftplugin'] 439 | if len(finddir(dir, path)) 440 | if exists('#BufRead') 441 | doautocmd BufRead 442 | endif 443 | return 444 | endif 445 | endfor 446 | endfor 447 | endfunction 448 | 449 | function! plug#load(...) 450 | if a:0 == 0 451 | return s:err('Argument missing: plugin name(s) required') 452 | endif 453 | if !exists('g:plugs') 454 | return s:err('plug#begin was not called') 455 | endif 456 | let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 457 | let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') 458 | if !empty(unknowns) 459 | let s = len(unknowns) > 1 ? 's' : '' 460 | return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) 461 | end 462 | let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') 463 | if !empty(unloaded) 464 | for name in unloaded 465 | call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 466 | endfor 467 | call s:dobufread(unloaded) 468 | return 1 469 | end 470 | return 0 471 | endfunction 472 | 473 | function! s:remove_triggers(name) 474 | if !has_key(s:triggers, a:name) 475 | return 476 | endif 477 | for cmd in s:triggers[a:name].cmd 478 | execute 'silent! delc' cmd 479 | endfor 480 | for map in s:triggers[a:name].map 481 | execute 'silent! unmap' map 482 | execute 'silent! iunmap' map 483 | endfor 484 | call remove(s:triggers, a:name) 485 | endfunction 486 | 487 | function! s:lod(names, types, ...) 488 | for name in a:names 489 | call s:remove_triggers(name) 490 | let s:loaded[name] = 1 491 | endfor 492 | call s:reorg_rtp() 493 | 494 | for name in a:names 495 | let rtp = s:rtp(g:plugs[name]) 496 | for dir in a:types 497 | call s:source(rtp, dir.'/**/*.vim') 498 | endfor 499 | if a:0 500 | if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) 501 | execute 'runtime' a:1 502 | endif 503 | call s:source(rtp, a:2) 504 | endif 505 | call s:doautocmd('User', name) 506 | endfor 507 | endfunction 508 | 509 | function! s:lod_ft(pat, names) 510 | let syn = 'syntax/'.a:pat.'.vim' 511 | call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) 512 | execute 'autocmd! PlugLOD FileType' a:pat 513 | call s:doautocmd('filetypeplugin', 'FileType') 514 | call s:doautocmd('filetypeindent', 'FileType') 515 | endfunction 516 | 517 | function! s:lod_cmd(cmd, bang, l1, l2, args, names) 518 | call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 519 | call s:dobufread(a:names) 520 | execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) 521 | endfunction 522 | 523 | function! s:lod_map(map, names, with_prefix, prefix) 524 | call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 525 | call s:dobufread(a:names) 526 | let extra = '' 527 | while 1 528 | let c = getchar(0) 529 | if c == 0 530 | break 531 | endif 532 | let extra .= nr2char(c) 533 | endwhile 534 | 535 | if a:with_prefix 536 | let prefix = v:count ? v:count : '' 537 | let prefix .= '"'.v:register.a:prefix 538 | if mode(1) == 'no' 539 | if v:operator == 'c' 540 | let prefix = "\" . prefix 541 | endif 542 | let prefix .= v:operator 543 | endif 544 | call feedkeys(prefix, 'n') 545 | endif 546 | call feedkeys(substitute(a:map, '^', "\", '') . extra) 547 | endfunction 548 | 549 | function! plug#(repo, ...) 550 | if a:0 > 1 551 | return s:err('Invalid number of arguments (1..2)') 552 | endif 553 | 554 | try 555 | let repo = s:trim(a:repo) 556 | let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec 557 | let name = get(opts, 'as', fnamemodify(repo, ':t:s?\.git$??')) 558 | let spec = extend(s:infer_properties(name, repo), opts) 559 | if !has_key(g:plugs, name) 560 | call add(g:plugs_order, name) 561 | endif 562 | let g:plugs[name] = spec 563 | let s:loaded[name] = get(s:loaded, name, 0) 564 | catch 565 | return s:err(v:exception) 566 | endtry 567 | endfunction 568 | 569 | function! s:parse_options(arg) 570 | let opts = copy(s:base_spec) 571 | let type = type(a:arg) 572 | if type == s:TYPE.string 573 | let opts.tag = a:arg 574 | elseif type == s:TYPE.dict 575 | call extend(opts, a:arg) 576 | if has_key(opts, 'dir') 577 | let opts.dir = s:dirpath(expand(opts.dir)) 578 | endif 579 | else 580 | throw 'Invalid argument type (expected: string or dictionary)' 581 | endif 582 | return opts 583 | endfunction 584 | 585 | function! s:infer_properties(name, repo) 586 | let repo = a:repo 587 | if s:is_local_plug(repo) 588 | return { 'dir': s:dirpath(expand(repo)) } 589 | else 590 | if repo =~ ':' 591 | let uri = repo 592 | else 593 | if repo !~ '/' 594 | throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) 595 | endif 596 | let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') 597 | let uri = printf(fmt, repo) 598 | endif 599 | return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } 600 | endif 601 | endfunction 602 | 603 | function! s:install(force, names) 604 | call s:update_impl(0, a:force, a:names) 605 | endfunction 606 | 607 | function! s:update(force, names) 608 | call s:update_impl(1, a:force, a:names) 609 | endfunction 610 | 611 | function! plug#helptags() 612 | if !exists('g:plugs') 613 | return s:err('plug#begin was not called') 614 | endif 615 | for spec in values(g:plugs) 616 | let docd = join([s:rtp(spec), 'doc'], '/') 617 | if isdirectory(docd) 618 | silent! execute 'helptags' s:esc(docd) 619 | endif 620 | endfor 621 | return 1 622 | endfunction 623 | 624 | function! s:syntax() 625 | syntax clear 626 | syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber 627 | syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX 628 | syn match plugNumber /[0-9]\+[0-9.]*/ contained 629 | syn match plugBracket /[[\]]/ contained 630 | syn match plugX /x/ contained 631 | syn match plugDash /^-/ 632 | syn match plugPlus /^+/ 633 | syn match plugStar /^*/ 634 | syn match plugMessage /\(^- \)\@<=.*/ 635 | syn match plugName /\(^- \)\@<=[^ ]*:/ 636 | syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ 637 | syn match plugTag /(tag: [^)]\+)/ 638 | syn match plugInstall /\(^+ \)\@<=[^:]*/ 639 | syn match plugUpdate /\(^* \)\@<=[^:]*/ 640 | syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag 641 | syn match plugEdge /^ \X\+$/ 642 | syn match plugEdge /^ \X*/ contained nextgroup=plugSha 643 | syn match plugSha /[0-9a-f]\{7,9}/ contained 644 | syn match plugRelDate /([^)]*)$/ contained 645 | syn match plugNotLoaded /(not loaded)$/ 646 | syn match plugError /^x.*/ 647 | syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ 648 | syn match plugH2 /^.*:\n-\+$/ 649 | syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean 650 | hi def link plug1 Title 651 | hi def link plug2 Repeat 652 | hi def link plugH2 Type 653 | hi def link plugX Exception 654 | hi def link plugBracket Structure 655 | hi def link plugNumber Number 656 | 657 | hi def link plugDash Special 658 | hi def link plugPlus Constant 659 | hi def link plugStar Boolean 660 | 661 | hi def link plugMessage Function 662 | hi def link plugName Label 663 | hi def link plugInstall Function 664 | hi def link plugUpdate Type 665 | 666 | hi def link plugError Error 667 | hi def link plugDeleted Ignore 668 | hi def link plugRelDate Comment 669 | hi def link plugEdge PreProc 670 | hi def link plugSha Identifier 671 | hi def link plugTag Constant 672 | 673 | hi def link plugNotLoaded Comment 674 | endfunction 675 | 676 | function! s:lpad(str, len) 677 | return a:str . repeat(' ', a:len - len(a:str)) 678 | endfunction 679 | 680 | function! s:lines(msg) 681 | return split(a:msg, "[\r\n]") 682 | endfunction 683 | 684 | function! s:lastline(msg) 685 | return get(s:lines(a:msg), -1, '') 686 | endfunction 687 | 688 | function! s:new_window() 689 | execute get(g:, 'plug_window', 'vertical topleft new') 690 | endfunction 691 | 692 | function! s:plug_window_exists() 693 | let buflist = tabpagebuflist(s:plug_tab) 694 | return !empty(buflist) && index(buflist, s:plug_buf) >= 0 695 | endfunction 696 | 697 | function! s:switch_in() 698 | if !s:plug_window_exists() 699 | return 0 700 | endif 701 | 702 | if winbufnr(0) != s:plug_buf 703 | let s:pos = [tabpagenr(), winnr(), winsaveview()] 704 | execute 'normal!' s:plug_tab.'gt' 705 | let winnr = bufwinnr(s:plug_buf) 706 | execute winnr.'wincmd w' 707 | call add(s:pos, winsaveview()) 708 | else 709 | let s:pos = [winsaveview()] 710 | endif 711 | 712 | setlocal modifiable 713 | return 1 714 | endfunction 715 | 716 | function! s:switch_out(...) 717 | call winrestview(s:pos[-1]) 718 | setlocal nomodifiable 719 | if a:0 > 0 720 | execute a:1 721 | endif 722 | 723 | if len(s:pos) > 1 724 | execute 'normal!' s:pos[0].'gt' 725 | execute s:pos[1] 'wincmd w' 726 | call winrestview(s:pos[2]) 727 | endif 728 | endfunction 729 | 730 | function! s:finish_bindings() 731 | nnoremap R :call retry() 732 | nnoremap D :PlugDiff 733 | nnoremap S :PlugStatus 734 | nnoremap U :call status_update() 735 | xnoremap U :call status_update() 736 | nnoremap ]] :silent! call section('') 737 | nnoremap [[ :silent! call section('b') 738 | endfunction 739 | 740 | function! s:prepare(...) 741 | if empty(getcwd()) 742 | throw 'Invalid current working directory. Cannot proceed.' 743 | endif 744 | 745 | for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] 746 | if exists(evar) 747 | throw evar.' detected. Cannot proceed.' 748 | endif 749 | endfor 750 | 751 | call s:job_abort() 752 | if s:switch_in() 753 | if b:plug_preview == 1 754 | pc 755 | endif 756 | enew 757 | else 758 | call s:new_window() 759 | endif 760 | 761 | nnoremap q :if b:plug_preview==1pcendifbd 762 | if a:0 == 0 763 | call s:finish_bindings() 764 | endif 765 | let b:plug_preview = -1 766 | let s:plug_tab = tabpagenr() 767 | let s:plug_buf = winbufnr(0) 768 | call s:assign_name() 769 | 770 | for k in ['', 'L', 'o', 'X', 'd', 'dd'] 771 | execute 'silent! unmap ' k 772 | endfor 773 | setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell 774 | if exists('+colorcolumn') 775 | setlocal colorcolumn= 776 | endif 777 | setf vim-plug 778 | if exists('g:syntax_on') 779 | call s:syntax() 780 | endif 781 | endfunction 782 | 783 | function! s:assign_name() 784 | " Assign buffer name 785 | let prefix = '[Plugins]' 786 | let name = prefix 787 | let idx = 2 788 | while bufexists(name) 789 | let name = printf('%s (%s)', prefix, idx) 790 | let idx = idx + 1 791 | endwhile 792 | silent! execute 'f' fnameescape(name) 793 | endfunction 794 | 795 | function! s:chsh(swap) 796 | let prev = [&shell, &shellcmdflag, &shellredir] 797 | if s:is_win 798 | set shell=cmd.exe shellcmdflag=/c shellredir=>%s\ 2>&1 799 | elseif a:swap 800 | set shell=sh shellredir=>%s\ 2>&1 801 | endif 802 | return prev 803 | endfunction 804 | 805 | function! s:bang(cmd, ...) 806 | try 807 | let [sh, shellcmdflag, shrd] = s:chsh(a:0) 808 | " FIXME: Escaping is incomplete. We could use shellescape with eval, 809 | " but it won't work on Windows. 810 | let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd 811 | if s:is_win 812 | let batchfile = tempname().'.bat' 813 | call writefile(["@echo off\r", cmd . "\r"], batchfile) 814 | let cmd = s:shellesc(batchfile) 815 | endif 816 | let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') 817 | execute "normal! :execute g:_plug_bang\\" 818 | finally 819 | unlet g:_plug_bang 820 | let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] 821 | if s:is_win 822 | call delete(batchfile) 823 | endif 824 | endtry 825 | return v:shell_error ? 'Exit status: ' . v:shell_error : '' 826 | endfunction 827 | 828 | function! s:regress_bar() 829 | let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') 830 | call s:progress_bar(2, bar, len(bar)) 831 | endfunction 832 | 833 | function! s:is_updated(dir) 834 | return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir)) 835 | endfunction 836 | 837 | function! s:do(pull, force, todo) 838 | for [name, spec] in items(a:todo) 839 | if !isdirectory(spec.dir) 840 | continue 841 | endif 842 | let installed = has_key(s:update.new, name) 843 | let updated = installed ? 0 : 844 | \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) 845 | if a:force || installed || updated 846 | execute 'cd' s:esc(spec.dir) 847 | call append(3, '- Post-update hook for '. name .' ... ') 848 | let error = '' 849 | let type = type(spec.do) 850 | if type == s:TYPE.string 851 | if spec.do[0] == ':' 852 | if !get(s:loaded, name, 0) 853 | let s:loaded[name] = 1 854 | call s:reorg_rtp() 855 | endif 856 | call s:load_plugin(spec) 857 | try 858 | execute spec.do[1:] 859 | catch 860 | let error = v:exception 861 | endtry 862 | if !s:plug_window_exists() 863 | cd - 864 | throw 'Warning: vim-plug was terminated by the post-update hook of '.name 865 | endif 866 | else 867 | let error = s:bang(spec.do) 868 | endif 869 | elseif type == s:TYPE.funcref 870 | try 871 | let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') 872 | call spec.do({ 'name': name, 'status': status, 'force': a:force }) 873 | catch 874 | let error = v:exception 875 | endtry 876 | else 877 | let error = 'Invalid hook type' 878 | endif 879 | call s:switch_in() 880 | call setline(4, empty(error) ? (getline(4) . 'OK') 881 | \ : ('x' . getline(4)[1:] . error)) 882 | if !empty(error) 883 | call add(s:update.errors, name) 884 | call s:regress_bar() 885 | endif 886 | cd - 887 | endif 888 | endfor 889 | endfunction 890 | 891 | function! s:hash_match(a, b) 892 | return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 893 | endfunction 894 | 895 | function! s:checkout(spec) 896 | let sha = a:spec.commit 897 | let output = s:system('git rev-parse HEAD', a:spec.dir) 898 | if !v:shell_error && !s:hash_match(sha, s:lines(output)[0]) 899 | let output = s:system( 900 | \ 'git fetch --depth 999999 && git checkout '.s:esc(sha).' --', a:spec.dir) 901 | endif 902 | return output 903 | endfunction 904 | 905 | function! s:finish(pull) 906 | let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) 907 | if new_frozen 908 | let s = new_frozen > 1 ? 's' : '' 909 | call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) 910 | endif 911 | call append(3, '- Finishing ... ') | 4 912 | redraw 913 | call plug#helptags() 914 | call plug#end() 915 | call setline(4, getline(4) . 'Done!') 916 | redraw 917 | let msgs = [] 918 | if !empty(s:update.errors) 919 | call add(msgs, "Press 'R' to retry.") 920 | endif 921 | if a:pull && len(s:update.new) < len(filter(getline(5, '$'), 922 | \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) 923 | call add(msgs, "Press 'D' to see the updated changes.") 924 | endif 925 | echo join(msgs, ' ') 926 | call s:finish_bindings() 927 | endfunction 928 | 929 | function! s:retry() 930 | if empty(s:update.errors) 931 | return 932 | endif 933 | echo 934 | call s:update_impl(s:update.pull, s:update.force, 935 | \ extend(copy(s:update.errors), [s:update.threads])) 936 | endfunction 937 | 938 | function! s:is_managed(name) 939 | return has_key(g:plugs[a:name], 'uri') 940 | endfunction 941 | 942 | function! s:names(...) 943 | return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) 944 | endfunction 945 | 946 | function! s:check_ruby() 947 | silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") 948 | if !exists('g:plug_ruby') 949 | redraw! 950 | return s:warn('echom', 'Warning: Ruby interface is broken') 951 | endif 952 | let ruby_version = split(g:plug_ruby, '\.') 953 | unlet g:plug_ruby 954 | return s:version_requirement(ruby_version, [1, 8, 7]) 955 | endfunction 956 | 957 | function! s:update_impl(pull, force, args) abort 958 | let sync = index(a:args, '--sync') >= 0 || has('vim_starting') 959 | let args = filter(copy(a:args), 'v:val != "--sync"') 960 | let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? 961 | \ remove(args, -1) : get(g:, 'plug_threads', 16) 962 | 963 | let managed = filter(copy(g:plugs), 's:is_managed(v:key)') 964 | let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : 965 | \ filter(managed, 'index(args, v:key) >= 0') 966 | 967 | if empty(todo) 968 | return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) 969 | endif 970 | 971 | if !s:is_win && s:git_version_requirement(2, 3) 972 | let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' 973 | let $GIT_TERMINAL_PROMPT = 0 974 | for plug in values(todo) 975 | let plug.uri = substitute(plug.uri, 976 | \ '^https://git::@github\.com', 'https://github.com', '') 977 | endfor 978 | endif 979 | 980 | if !isdirectory(g:plug_home) 981 | try 982 | call mkdir(g:plug_home, 'p') 983 | catch 984 | return s:err(printf('Invalid plug directory: %s. '. 985 | \ 'Try to call plug#begin with a valid directory', g:plug_home)) 986 | endtry 987 | endif 988 | 989 | if has('nvim') && !exists('*jobwait') && threads > 1 990 | call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') 991 | endif 992 | 993 | let use_job = s:nvim || s:vim8 994 | let python = (has('python') || has('python3')) && !use_job 995 | 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() 996 | 997 | let s:update = { 998 | \ 'start': reltime(), 999 | \ 'all': todo, 1000 | \ 'todo': copy(todo), 1001 | \ 'errors': [], 1002 | \ 'pull': a:pull, 1003 | \ 'force': a:force, 1004 | \ 'new': {}, 1005 | \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, 1006 | \ 'bar': '', 1007 | \ 'fin': 0 1008 | \ } 1009 | 1010 | call s:prepare(1) 1011 | call append(0, ['', '']) 1012 | normal! 2G 1013 | silent! redraw 1014 | 1015 | let s:clone_opt = get(g:, 'plug_shallow', 1) ? 1016 | \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : '' 1017 | 1018 | if has('win32unix') 1019 | let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input' 1020 | endif 1021 | 1022 | let s:submodule_opt = s:git_version_requirement(2, 8) ? ' --jobs='.threads : '' 1023 | 1024 | " Python version requirement (>= 2.7) 1025 | if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 1026 | redir => pyv 1027 | silent python import platform; print platform.python_version() 1028 | redir END 1029 | let python = s:version_requirement( 1030 | \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) 1031 | endif 1032 | 1033 | if (python || ruby) && s:update.threads > 1 1034 | try 1035 | let imd = &imd 1036 | if s:mac_gui 1037 | set noimd 1038 | endif 1039 | if ruby 1040 | call s:update_ruby() 1041 | else 1042 | call s:update_python() 1043 | endif 1044 | catch 1045 | let lines = getline(4, '$') 1046 | let printed = {} 1047 | silent! 4,$d _ 1048 | for line in lines 1049 | let name = s:extract_name(line, '.', '') 1050 | if empty(name) || !has_key(printed, name) 1051 | call append('$', line) 1052 | if !empty(name) 1053 | let printed[name] = 1 1054 | if line[0] == 'x' && index(s:update.errors, name) < 0 1055 | call add(s:update.errors, name) 1056 | end 1057 | endif 1058 | endif 1059 | endfor 1060 | finally 1061 | let &imd = imd 1062 | call s:update_finish() 1063 | endtry 1064 | else 1065 | call s:update_vim() 1066 | while use_job && sync 1067 | sleep 100m 1068 | if s:update.fin 1069 | break 1070 | endif 1071 | endwhile 1072 | endif 1073 | endfunction 1074 | 1075 | function! s:log4(name, msg) 1076 | call setline(4, printf('- %s (%s)', a:msg, a:name)) 1077 | redraw 1078 | endfunction 1079 | 1080 | function! s:update_finish() 1081 | if exists('s:git_terminal_prompt') 1082 | let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt 1083 | endif 1084 | if s:switch_in() 1085 | call append(3, '- Updating ...') | 4 1086 | 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))')) 1087 | let [pos, _] = s:logpos(name) 1088 | if !pos 1089 | continue 1090 | endif 1091 | if has_key(spec, 'commit') 1092 | call s:log4(name, 'Checking out '.spec.commit) 1093 | let out = s:checkout(spec) 1094 | elseif has_key(spec, 'tag') 1095 | let tag = spec.tag 1096 | if tag =~ '\*' 1097 | let tags = s:lines(s:system('git tag --list '.s:shellesc(tag).' --sort -version:refname 2>&1', spec.dir)) 1098 | if !v:shell_error && !empty(tags) 1099 | let tag = tags[0] 1100 | call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) 1101 | call append(3, '') 1102 | endif 1103 | endif 1104 | call s:log4(name, 'Checking out '.tag) 1105 | let out = s:system('git checkout -q '.s:esc(tag).' -- 2>&1', spec.dir) 1106 | else 1107 | let branch = s:esc(get(spec, 'branch', 'master')) 1108 | call s:log4(name, 'Merging origin/'.branch) 1109 | let out = s:system('git checkout -q '.branch.' -- 2>&1' 1110 | \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir) 1111 | endif 1112 | if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && 1113 | \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) 1114 | call s:log4(name, 'Updating submodules. This may take a while.') 1115 | let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir) 1116 | endif 1117 | let msg = s:format_message(v:shell_error ? 'x': '-', name, out) 1118 | if v:shell_error 1119 | call add(s:update.errors, name) 1120 | call s:regress_bar() 1121 | silent execute pos 'd _' 1122 | call append(4, msg) | 4 1123 | elseif !empty(out) 1124 | call setline(pos, msg[0]) 1125 | endif 1126 | redraw 1127 | endfor 1128 | silent 4 d _ 1129 | try 1130 | 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")')) 1131 | catch 1132 | call s:warn('echom', v:exception) 1133 | call s:warn('echo', '') 1134 | return 1135 | endtry 1136 | call s:finish(s:update.pull) 1137 | call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') 1138 | call s:switch_out('normal! gg') 1139 | endif 1140 | endfunction 1141 | 1142 | function! s:job_abort() 1143 | if (!s:nvim && !s:vim8) || !exists('s:jobs') 1144 | return 1145 | endif 1146 | 1147 | for [name, j] in items(s:jobs) 1148 | if s:nvim 1149 | silent! call jobstop(j.jobid) 1150 | elseif s:vim8 1151 | silent! call job_stop(j.jobid) 1152 | endif 1153 | if j.new 1154 | call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir)) 1155 | endif 1156 | endfor 1157 | let s:jobs = {} 1158 | endfunction 1159 | 1160 | function! s:last_non_empty_line(lines) 1161 | let len = len(a:lines) 1162 | for idx in range(len) 1163 | let line = a:lines[len-idx-1] 1164 | if !empty(line) 1165 | return line 1166 | endif 1167 | endfor 1168 | return '' 1169 | endfunction 1170 | 1171 | function! s:job_out_cb(self, data) abort 1172 | let self = a:self 1173 | let data = remove(self.lines, -1) . a:data 1174 | let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') 1175 | call extend(self.lines, lines) 1176 | " To reduce the number of buffer updates 1177 | let self.tick = get(self, 'tick', -1) + 1 1178 | if !self.running || self.tick % len(s:jobs) == 0 1179 | let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-') 1180 | let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) 1181 | call s:log(bullet, self.name, result) 1182 | endif 1183 | endfunction 1184 | 1185 | function! s:job_exit_cb(self, data) abort 1186 | let a:self.running = 0 1187 | let a:self.error = a:data != 0 1188 | call s:reap(a:self.name) 1189 | call s:tick() 1190 | endfunction 1191 | 1192 | function! s:job_cb(fn, job, ch, data) 1193 | if !s:plug_window_exists() " plug window closed 1194 | return s:job_abort() 1195 | endif 1196 | call call(a:fn, [a:job, a:data]) 1197 | endfunction 1198 | 1199 | function! s:nvim_cb(job_id, data, event) dict abort 1200 | return a:event == 'stdout' ? 1201 | \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : 1202 | \ s:job_cb('s:job_exit_cb', self, 0, a:data) 1203 | endfunction 1204 | 1205 | function! s:spawn(name, cmd, opts) 1206 | let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''], 1207 | \ 'batchfile': (s:is_win && (s:nvim || s:vim8)) ? tempname().'.bat' : '', 1208 | \ 'new': get(a:opts, 'new', 0) } 1209 | let s:jobs[a:name] = job 1210 | let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd 1211 | if !empty(job.batchfile) 1212 | call writefile(["@echo off\r", cmd . "\r"], job.batchfile) 1213 | let cmd = s:shellesc(job.batchfile) 1214 | endif 1215 | let argv = add(s:is_win ? ['cmd', '/c'] : ['sh', '-c'], cmd) 1216 | 1217 | if s:nvim 1218 | call extend(job, { 1219 | \ 'on_stdout': function('s:nvim_cb'), 1220 | \ 'on_exit': function('s:nvim_cb'), 1221 | \ }) 1222 | let jid = jobstart(argv, job) 1223 | if jid > 0 1224 | let job.jobid = jid 1225 | else 1226 | let job.running = 0 1227 | let job.error = 1 1228 | let job.lines = [jid < 0 ? argv[0].' is not executable' : 1229 | \ 'Invalid arguments (or job table is full)'] 1230 | endif 1231 | elseif s:vim8 1232 | let jid = job_start(s:is_win ? join(argv, ' ') : argv, { 1233 | \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), 1234 | \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), 1235 | \ 'out_mode': 'raw' 1236 | \}) 1237 | if job_status(jid) == 'run' 1238 | let job.jobid = jid 1239 | else 1240 | let job.running = 0 1241 | let job.error = 1 1242 | let job.lines = ['Failed to start job'] 1243 | endif 1244 | else 1245 | let job.lines = s:lines(call('s:system', [cmd])) 1246 | let job.error = v:shell_error != 0 1247 | let job.running = 0 1248 | endif 1249 | endfunction 1250 | 1251 | function! s:reap(name) 1252 | let job = s:jobs[a:name] 1253 | if job.error 1254 | call add(s:update.errors, a:name) 1255 | elseif get(job, 'new', 0) 1256 | let s:update.new[a:name] = 1 1257 | endif 1258 | let s:update.bar .= job.error ? 'x' : '=' 1259 | 1260 | let bullet = job.error ? 'x' : '-' 1261 | let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) 1262 | call s:log(bullet, a:name, empty(result) ? 'OK' : result) 1263 | call s:bar() 1264 | 1265 | if has_key(job, 'batchfile') && !empty(job.batchfile) 1266 | call delete(job.batchfile) 1267 | endif 1268 | call remove(s:jobs, a:name) 1269 | endfunction 1270 | 1271 | function! s:bar() 1272 | if s:switch_in() 1273 | let total = len(s:update.all) 1274 | call setline(1, (s:update.pull ? 'Updating' : 'Installing'). 1275 | \ ' plugins ('.len(s:update.bar).'/'.total.')') 1276 | call s:progress_bar(2, s:update.bar, total) 1277 | call s:switch_out() 1278 | endif 1279 | endfunction 1280 | 1281 | function! s:logpos(name) 1282 | for i in range(4, line('$')) 1283 | if getline(i) =~# '^[-+x*] '.a:name.':' 1284 | for j in range(i + 1, line('$')) 1285 | if getline(j) !~ '^ ' 1286 | return [i, j - 1] 1287 | endif 1288 | endfor 1289 | return [i, i] 1290 | endif 1291 | endfor 1292 | return [0, 0] 1293 | endfunction 1294 | 1295 | function! s:log(bullet, name, lines) 1296 | if s:switch_in() 1297 | let [b, e] = s:logpos(a:name) 1298 | if b > 0 1299 | silent execute printf('%d,%d d _', b, e) 1300 | if b > winheight('.') 1301 | let b = 4 1302 | endif 1303 | else 1304 | let b = 4 1305 | endif 1306 | " FIXME For some reason, nomodifiable is set after :d in vim8 1307 | setlocal modifiable 1308 | call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) 1309 | call s:switch_out() 1310 | endif 1311 | endfunction 1312 | 1313 | function! s:update_vim() 1314 | let s:jobs = {} 1315 | 1316 | call s:bar() 1317 | call s:tick() 1318 | endfunction 1319 | 1320 | function! s:tick() 1321 | let pull = s:update.pull 1322 | let prog = s:progress_opt(s:nvim || s:vim8) 1323 | while 1 " Without TCO, Vim stack is bound to explode 1324 | if empty(s:update.todo) 1325 | if empty(s:jobs) && !s:update.fin 1326 | call s:update_finish() 1327 | let s:update.fin = 1 1328 | endif 1329 | return 1330 | endif 1331 | 1332 | let name = keys(s:update.todo)[0] 1333 | let spec = remove(s:update.todo, name) 1334 | let new = empty(globpath(spec.dir, '.git', 1)) 1335 | 1336 | call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') 1337 | redraw 1338 | 1339 | let has_tag = has_key(spec, 'tag') 1340 | if !new 1341 | let [error, _] = s:git_validate(spec, 0) 1342 | if empty(error) 1343 | if pull 1344 | let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : '' 1345 | call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir }) 1346 | else 1347 | let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } 1348 | endif 1349 | else 1350 | let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } 1351 | endif 1352 | else 1353 | call s:spawn(name, 1354 | \ printf('git clone %s %s %s %s 2>&1', 1355 | \ has_tag ? '' : s:clone_opt, 1356 | \ prog, 1357 | \ s:shellesc(spec.uri), 1358 | \ s:shellesc(s:trim(spec.dir))), { 'new': 1 }) 1359 | endif 1360 | 1361 | if !s:jobs[name].running 1362 | call s:reap(name) 1363 | endif 1364 | if len(s:jobs) >= s:update.threads 1365 | break 1366 | endif 1367 | endwhile 1368 | endfunction 1369 | 1370 | function! s:update_python() 1371 | let py_exe = has('python') ? 'python' : 'python3' 1372 | execute py_exe "<< EOF" 1373 | import datetime 1374 | import functools 1375 | import os 1376 | try: 1377 | import queue 1378 | except ImportError: 1379 | import Queue as queue 1380 | import random 1381 | import re 1382 | import shutil 1383 | import signal 1384 | import subprocess 1385 | import tempfile 1386 | import threading as thr 1387 | import time 1388 | import traceback 1389 | import vim 1390 | 1391 | G_NVIM = vim.eval("has('nvim')") == '1' 1392 | G_PULL = vim.eval('s:update.pull') == '1' 1393 | G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 1394 | G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) 1395 | G_CLONE_OPT = vim.eval('s:clone_opt') 1396 | G_PROGRESS = vim.eval('s:progress_opt(1)') 1397 | G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) 1398 | G_STOP = thr.Event() 1399 | G_IS_WIN = vim.eval('s:is_win') == '1' 1400 | 1401 | class PlugError(Exception): 1402 | def __init__(self, msg): 1403 | self.msg = msg 1404 | class CmdTimedOut(PlugError): 1405 | pass 1406 | class CmdFailed(PlugError): 1407 | pass 1408 | class InvalidURI(PlugError): 1409 | pass 1410 | class Action(object): 1411 | INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] 1412 | 1413 | class Buffer(object): 1414 | def __init__(self, lock, num_plugs, is_pull): 1415 | self.bar = '' 1416 | self.event = 'Updating' if is_pull else 'Installing' 1417 | self.lock = lock 1418 | self.maxy = int(vim.eval('winheight(".")')) 1419 | self.num_plugs = num_plugs 1420 | 1421 | def __where(self, name): 1422 | """ Find first line with name in current buffer. Return line num. """ 1423 | found, lnum = False, 0 1424 | matcher = re.compile('^[-+x*] {0}:'.format(name)) 1425 | for line in vim.current.buffer: 1426 | if matcher.search(line) is not None: 1427 | found = True 1428 | break 1429 | lnum += 1 1430 | 1431 | if not found: 1432 | lnum = -1 1433 | return lnum 1434 | 1435 | def header(self): 1436 | curbuf = vim.current.buffer 1437 | curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) 1438 | 1439 | num_spaces = self.num_plugs - len(self.bar) 1440 | curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') 1441 | 1442 | with self.lock: 1443 | vim.command('normal! 2G') 1444 | vim.command('redraw') 1445 | 1446 | def write(self, action, name, lines): 1447 | first, rest = lines[0], lines[1:] 1448 | msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] 1449 | msg.extend([' ' + line for line in rest]) 1450 | 1451 | try: 1452 | if action == Action.ERROR: 1453 | self.bar += 'x' 1454 | vim.command("call add(s:update.errors, '{0}')".format(name)) 1455 | elif action == Action.DONE: 1456 | self.bar += '=' 1457 | 1458 | curbuf = vim.current.buffer 1459 | lnum = self.__where(name) 1460 | if lnum != -1: # Found matching line num 1461 | del curbuf[lnum] 1462 | if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): 1463 | lnum = 3 1464 | else: 1465 | lnum = 3 1466 | curbuf.append(msg, lnum) 1467 | 1468 | self.header() 1469 | except vim.error: 1470 | pass 1471 | 1472 | class Command(object): 1473 | CD = 'cd /d' if G_IS_WIN else 'cd' 1474 | 1475 | def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): 1476 | self.cmd = cmd 1477 | if cmd_dir: 1478 | self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) 1479 | self.timeout = timeout 1480 | self.callback = cb if cb else (lambda msg: None) 1481 | self.clean = clean if clean else (lambda: None) 1482 | self.proc = None 1483 | 1484 | @property 1485 | def alive(self): 1486 | """ Returns true only if command still running. """ 1487 | return self.proc and self.proc.poll() is None 1488 | 1489 | def execute(self, ntries=3): 1490 | """ Execute the command with ntries if CmdTimedOut. 1491 | Returns the output of the command if no Exception. 1492 | """ 1493 | attempt, finished, limit = 0, False, self.timeout 1494 | 1495 | while not finished: 1496 | try: 1497 | attempt += 1 1498 | result = self.try_command() 1499 | finished = True 1500 | return result 1501 | except CmdTimedOut: 1502 | if attempt != ntries: 1503 | self.notify_retry() 1504 | self.timeout += limit 1505 | else: 1506 | raise 1507 | 1508 | def notify_retry(self): 1509 | """ Retry required for command, notify user. """ 1510 | for count in range(3, 0, -1): 1511 | if G_STOP.is_set(): 1512 | raise KeyboardInterrupt 1513 | msg = 'Timeout. Will retry in {0} second{1} ...'.format( 1514 | count, 's' if count != 1 else '') 1515 | self.callback([msg]) 1516 | time.sleep(1) 1517 | self.callback(['Retrying ...']) 1518 | 1519 | def try_command(self): 1520 | """ Execute a cmd & poll for callback. Returns list of output. 1521 | Raises CmdFailed -> return code for Popen isn't 0 1522 | Raises CmdTimedOut -> command exceeded timeout without new output 1523 | """ 1524 | first_line = True 1525 | 1526 | try: 1527 | tfile = tempfile.NamedTemporaryFile(mode='w+b') 1528 | preexec_fn = not G_IS_WIN and os.setsid or None 1529 | self.proc = subprocess.Popen(self.cmd, stdout=tfile, 1530 | stderr=subprocess.STDOUT, 1531 | stdin=subprocess.PIPE, shell=True, 1532 | preexec_fn=preexec_fn) 1533 | thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) 1534 | thrd.start() 1535 | 1536 | thread_not_started = True 1537 | while thread_not_started: 1538 | try: 1539 | thrd.join(0.1) 1540 | thread_not_started = False 1541 | except RuntimeError: 1542 | pass 1543 | 1544 | while self.alive: 1545 | if G_STOP.is_set(): 1546 | raise KeyboardInterrupt 1547 | 1548 | if first_line or random.random() < G_LOG_PROB: 1549 | first_line = False 1550 | line = '' if G_IS_WIN else nonblock_read(tfile.name) 1551 | if line: 1552 | self.callback([line]) 1553 | 1554 | time_diff = time.time() - os.path.getmtime(tfile.name) 1555 | if time_diff > self.timeout: 1556 | raise CmdTimedOut(['Timeout!']) 1557 | 1558 | thrd.join(0.5) 1559 | 1560 | tfile.seek(0) 1561 | result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] 1562 | 1563 | if self.proc.returncode != 0: 1564 | raise CmdFailed([''] + result) 1565 | 1566 | return result 1567 | except: 1568 | self.terminate() 1569 | raise 1570 | 1571 | def terminate(self): 1572 | """ Terminate process and cleanup. """ 1573 | if self.alive: 1574 | if G_IS_WIN: 1575 | os.kill(self.proc.pid, signal.SIGINT) 1576 | else: 1577 | os.killpg(self.proc.pid, signal.SIGTERM) 1578 | self.clean() 1579 | 1580 | class Plugin(object): 1581 | def __init__(self, name, args, buf_q, lock): 1582 | self.name = name 1583 | self.args = args 1584 | self.buf_q = buf_q 1585 | self.lock = lock 1586 | self.tag = args.get('tag', 0) 1587 | 1588 | def manage(self): 1589 | try: 1590 | if os.path.exists(self.args['dir']): 1591 | self.update() 1592 | else: 1593 | self.install() 1594 | with self.lock: 1595 | thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) 1596 | except PlugError as exc: 1597 | self.write(Action.ERROR, self.name, exc.msg) 1598 | except KeyboardInterrupt: 1599 | G_STOP.set() 1600 | self.write(Action.ERROR, self.name, ['Interrupted!']) 1601 | except: 1602 | # Any exception except those above print stack trace 1603 | msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) 1604 | self.write(Action.ERROR, self.name, msg.split('\n')) 1605 | raise 1606 | 1607 | def install(self): 1608 | target = self.args['dir'] 1609 | if target[-1] == '\\': 1610 | target = target[0:-1] 1611 | 1612 | def clean(target): 1613 | def _clean(): 1614 | try: 1615 | shutil.rmtree(target) 1616 | except OSError: 1617 | pass 1618 | return _clean 1619 | 1620 | self.write(Action.INSTALL, self.name, ['Installing ...']) 1621 | callback = functools.partial(self.write, Action.INSTALL, self.name) 1622 | cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( 1623 | '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], 1624 | esc(target)) 1625 | com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) 1626 | result = com.execute(G_RETRIES) 1627 | self.write(Action.DONE, self.name, result[-1:]) 1628 | 1629 | def repo_uri(self): 1630 | cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' 1631 | command = Command(cmd, self.args['dir'], G_TIMEOUT,) 1632 | result = command.execute(G_RETRIES) 1633 | return result[-1] 1634 | 1635 | def update(self): 1636 | actual_uri = self.repo_uri() 1637 | expect_uri = self.args['uri'] 1638 | regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') 1639 | ma = regex.match(actual_uri) 1640 | mb = regex.match(expect_uri) 1641 | if ma is None or mb is None or ma.groups() != mb.groups(): 1642 | msg = ['', 1643 | 'Invalid URI: {0}'.format(actual_uri), 1644 | 'Expected {0}'.format(expect_uri), 1645 | 'PlugClean required.'] 1646 | raise InvalidURI(msg) 1647 | 1648 | if G_PULL: 1649 | self.write(Action.UPDATE, self.name, ['Updating ...']) 1650 | callback = functools.partial(self.write, Action.UPDATE, self.name) 1651 | fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' 1652 | cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) 1653 | com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) 1654 | result = com.execute(G_RETRIES) 1655 | self.write(Action.DONE, self.name, result[-1:]) 1656 | else: 1657 | self.write(Action.DONE, self.name, ['Already installed']) 1658 | 1659 | def write(self, action, name, msg): 1660 | self.buf_q.put((action, name, msg)) 1661 | 1662 | class PlugThread(thr.Thread): 1663 | def __init__(self, tname, args): 1664 | super(PlugThread, self).__init__() 1665 | self.tname = tname 1666 | self.args = args 1667 | 1668 | def run(self): 1669 | thr.current_thread().name = self.tname 1670 | buf_q, work_q, lock = self.args 1671 | 1672 | try: 1673 | while not G_STOP.is_set(): 1674 | name, args = work_q.get_nowait() 1675 | plug = Plugin(name, args, buf_q, lock) 1676 | plug.manage() 1677 | work_q.task_done() 1678 | except queue.Empty: 1679 | pass 1680 | 1681 | class RefreshThread(thr.Thread): 1682 | def __init__(self, lock): 1683 | super(RefreshThread, self).__init__() 1684 | self.lock = lock 1685 | self.running = True 1686 | 1687 | def run(self): 1688 | while self.running: 1689 | with self.lock: 1690 | thread_vim_command('noautocmd normal! a') 1691 | time.sleep(0.33) 1692 | 1693 | def stop(self): 1694 | self.running = False 1695 | 1696 | if G_NVIM: 1697 | def thread_vim_command(cmd): 1698 | vim.session.threadsafe_call(lambda: vim.command(cmd)) 1699 | else: 1700 | def thread_vim_command(cmd): 1701 | vim.command(cmd) 1702 | 1703 | def esc(name): 1704 | return '"' + name.replace('"', '\"') + '"' 1705 | 1706 | def nonblock_read(fname): 1707 | """ Read a file with nonblock flag. Return the last line. """ 1708 | fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) 1709 | buf = os.read(fread, 100000).decode('utf-8', 'replace') 1710 | os.close(fread) 1711 | 1712 | line = buf.rstrip('\r\n') 1713 | left = max(line.rfind('\r'), line.rfind('\n')) 1714 | if left != -1: 1715 | left += 1 1716 | line = line[left:] 1717 | 1718 | return line 1719 | 1720 | def main(): 1721 | thr.current_thread().name = 'main' 1722 | nthreads = int(vim.eval('s:update.threads')) 1723 | plugs = vim.eval('s:update.todo') 1724 | mac_gui = vim.eval('s:mac_gui') == '1' 1725 | 1726 | lock = thr.Lock() 1727 | buf = Buffer(lock, len(plugs), G_PULL) 1728 | buf_q, work_q = queue.Queue(), queue.Queue() 1729 | for work in plugs.items(): 1730 | work_q.put(work) 1731 | 1732 | start_cnt = thr.active_count() 1733 | for num in range(nthreads): 1734 | tname = 'PlugT-{0:02}'.format(num) 1735 | thread = PlugThread(tname, (buf_q, work_q, lock)) 1736 | thread.start() 1737 | if mac_gui: 1738 | rthread = RefreshThread(lock) 1739 | rthread.start() 1740 | 1741 | while not buf_q.empty() or thr.active_count() != start_cnt: 1742 | try: 1743 | action, name, msg = buf_q.get(True, 0.25) 1744 | buf.write(action, name, ['OK'] if not msg else msg) 1745 | buf_q.task_done() 1746 | except queue.Empty: 1747 | pass 1748 | except KeyboardInterrupt: 1749 | G_STOP.set() 1750 | 1751 | if mac_gui: 1752 | rthread.stop() 1753 | rthread.join() 1754 | 1755 | main() 1756 | EOF 1757 | endfunction 1758 | 1759 | function! s:update_ruby() 1760 | ruby << EOF 1761 | module PlugStream 1762 | SEP = ["\r", "\n", nil] 1763 | def get_line 1764 | buffer = '' 1765 | loop do 1766 | char = readchar rescue return 1767 | if SEP.include? char.chr 1768 | buffer << $/ 1769 | break 1770 | else 1771 | buffer << char 1772 | end 1773 | end 1774 | buffer 1775 | end 1776 | end unless defined?(PlugStream) 1777 | 1778 | def esc arg 1779 | %["#{arg.gsub('"', '\"')}"] 1780 | end 1781 | 1782 | def killall pid 1783 | pids = [pid] 1784 | if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM 1785 | pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } 1786 | else 1787 | unless `which pgrep 2> /dev/null`.empty? 1788 | children = pids 1789 | until children.empty? 1790 | children = children.map { |pid| 1791 | `pgrep -P #{pid}`.lines.map { |l| l.chomp } 1792 | }.flatten 1793 | pids += children 1794 | end 1795 | end 1796 | pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } 1797 | end 1798 | end 1799 | 1800 | def compare_git_uri a, b 1801 | regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} 1802 | regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) 1803 | end 1804 | 1805 | require 'thread' 1806 | require 'fileutils' 1807 | require 'timeout' 1808 | running = true 1809 | iswin = VIM::evaluate('s:is_win').to_i == 1 1810 | pull = VIM::evaluate('s:update.pull').to_i == 1 1811 | base = VIM::evaluate('g:plug_home') 1812 | all = VIM::evaluate('s:update.todo') 1813 | limit = VIM::evaluate('get(g:, "plug_timeout", 60)') 1814 | tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 1815 | nthr = VIM::evaluate('s:update.threads').to_i 1816 | maxy = VIM::evaluate('winheight(".")').to_i 1817 | vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ 1818 | cd = iswin ? 'cd /d' : 'cd' 1819 | tot = VIM::evaluate('len(s:update.todo)') || 0 1820 | bar = '' 1821 | skip = 'Already installed' 1822 | mtx = Mutex.new 1823 | take1 = proc { mtx.synchronize { running && all.shift } } 1824 | logh = proc { 1825 | cnt = bar.length 1826 | $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" 1827 | $curbuf[2] = '[' + bar.ljust(tot) + ']' 1828 | VIM::command('normal! 2G') 1829 | VIM::command('redraw') 1830 | } 1831 | where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } 1832 | log = proc { |name, result, type| 1833 | mtx.synchronize do 1834 | ing = ![true, false].include?(type) 1835 | bar += type ? '=' : 'x' unless ing 1836 | b = case type 1837 | when :install then '+' when :update then '*' 1838 | when true, nil then '-' else 1839 | VIM::command("call add(s:update.errors, '#{name}')") 1840 | 'x' 1841 | end 1842 | result = 1843 | if type || type.nil? 1844 | ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] 1845 | elsif result =~ /^Interrupted|^Timeout/ 1846 | ["#{b} #{name}: #{result}"] 1847 | else 1848 | ["#{b} #{name}"] + result.lines.map { |l| " " << l } 1849 | end 1850 | if lnum = where.call(name) 1851 | $curbuf.delete lnum 1852 | lnum = 4 if ing && lnum > maxy 1853 | end 1854 | result.each_with_index do |line, offset| 1855 | $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) 1856 | end 1857 | logh.call 1858 | end 1859 | } 1860 | bt = proc { |cmd, name, type, cleanup| 1861 | tried = timeout = 0 1862 | begin 1863 | tried += 1 1864 | timeout += limit 1865 | fd = nil 1866 | data = '' 1867 | if iswin 1868 | Timeout::timeout(timeout) do 1869 | tmp = VIM::evaluate('tempname()') 1870 | system("(#{cmd}) > #{tmp}") 1871 | data = File.read(tmp).chomp 1872 | File.unlink tmp rescue nil 1873 | end 1874 | else 1875 | fd = IO.popen(cmd).extend(PlugStream) 1876 | first_line = true 1877 | log_prob = 1.0 / nthr 1878 | while line = Timeout::timeout(timeout) { fd.get_line } 1879 | data << line 1880 | log.call name, line.chomp, type if name && (first_line || rand < log_prob) 1881 | first_line = false 1882 | end 1883 | fd.close 1884 | end 1885 | [$? == 0, data.chomp] 1886 | rescue Timeout::Error, Interrupt => e 1887 | if fd && !fd.closed? 1888 | killall fd.pid 1889 | fd.close 1890 | end 1891 | cleanup.call if cleanup 1892 | if e.is_a?(Timeout::Error) && tried < tries 1893 | 3.downto(1) do |countdown| 1894 | s = countdown > 1 ? 's' : '' 1895 | log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type 1896 | sleep 1 1897 | end 1898 | log.call name, 'Retrying ...', type 1899 | retry 1900 | end 1901 | [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] 1902 | end 1903 | } 1904 | main = Thread.current 1905 | threads = [] 1906 | watcher = Thread.new { 1907 | if vim7 1908 | while VIM::evaluate('getchar(1)') 1909 | sleep 0.1 1910 | end 1911 | else 1912 | require 'io/console' # >= Ruby 1.9 1913 | nil until IO.console.getch == 3.chr 1914 | end 1915 | mtx.synchronize do 1916 | running = false 1917 | threads.each { |t| t.raise Interrupt } unless vim7 1918 | end 1919 | threads.each { |t| t.join rescue nil } 1920 | main.kill 1921 | } 1922 | refresh = Thread.new { 1923 | while true 1924 | mtx.synchronize do 1925 | break unless running 1926 | VIM::command('noautocmd normal! a') 1927 | end 1928 | sleep 0.2 1929 | end 1930 | } if VIM::evaluate('s:mac_gui') == 1 1931 | 1932 | clone_opt = VIM::evaluate('s:clone_opt') 1933 | progress = VIM::evaluate('s:progress_opt(1)') 1934 | nthr.times do 1935 | mtx.synchronize do 1936 | threads << Thread.new { 1937 | while pair = take1.call 1938 | name = pair.first 1939 | dir, uri, tag = pair.last.values_at *%w[dir uri tag] 1940 | exists = File.directory? dir 1941 | ok, result = 1942 | if exists 1943 | chdir = "#{cd} #{iswin ? dir : esc(dir)}" 1944 | ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil 1945 | current_uri = data.lines.to_a.last 1946 | if !ret 1947 | if data =~ /^Interrupted|^Timeout/ 1948 | [false, data] 1949 | else 1950 | [false, [data.chomp, "PlugClean required."].join($/)] 1951 | end 1952 | elsif !compare_git_uri(current_uri, uri) 1953 | [false, ["Invalid URI: #{current_uri}", 1954 | "Expected: #{uri}", 1955 | "PlugClean required."].join($/)] 1956 | else 1957 | if pull 1958 | log.call name, 'Updating ...', :update 1959 | fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' 1960 | bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil 1961 | else 1962 | [true, skip] 1963 | end 1964 | end 1965 | else 1966 | d = esc dir.sub(%r{[\\/]+$}, '') 1967 | log.call name, 'Installing ...', :install 1968 | bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { 1969 | FileUtils.rm_rf dir 1970 | } 1971 | end 1972 | mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok 1973 | log.call name, result, ok 1974 | end 1975 | } if running 1976 | end 1977 | end 1978 | threads.each { |t| t.join rescue nil } 1979 | logh.call 1980 | refresh.kill if refresh 1981 | watcher.kill 1982 | EOF 1983 | endfunction 1984 | 1985 | function! s:shellesc_cmd(arg) 1986 | let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g') 1987 | let escaped = substitute(escaped, '%', '%%', 'g') 1988 | let escaped = substitute(escaped, '"', '\\^&', 'g') 1989 | let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g') 1990 | return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"' 1991 | endfunction 1992 | 1993 | function! s:shellesc(arg) 1994 | if &shell =~# 'cmd.exe$' 1995 | return s:shellesc_cmd(a:arg) 1996 | endif 1997 | return shellescape(a:arg) 1998 | endfunction 1999 | 2000 | function! s:glob_dir(path) 2001 | return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') 2002 | endfunction 2003 | 2004 | function! s:progress_bar(line, bar, total) 2005 | call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') 2006 | endfunction 2007 | 2008 | function! s:compare_git_uri(a, b) 2009 | " See `git help clone' 2010 | " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] 2011 | " [git@] github.com[:port] : junegunn/vim-plug [.git] 2012 | " file:// / junegunn/vim-plug [/] 2013 | " / junegunn/vim-plug [/] 2014 | let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' 2015 | let ma = matchlist(a:a, pat) 2016 | let mb = matchlist(a:b, pat) 2017 | return ma[1:2] ==# mb[1:2] 2018 | endfunction 2019 | 2020 | function! s:format_message(bullet, name, message) 2021 | if a:bullet != 'x' 2022 | return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] 2023 | else 2024 | let lines = map(s:lines(a:message), '" ".v:val') 2025 | return extend([printf('x %s:', a:name)], lines) 2026 | endif 2027 | endfunction 2028 | 2029 | function! s:with_cd(cmd, dir) 2030 | return printf('cd%s %s && %s', s:is_win ? ' /d' : '', s:shellesc(a:dir), a:cmd) 2031 | endfunction 2032 | 2033 | function! s:system(cmd, ...) 2034 | try 2035 | let [sh, shellcmdflag, shrd] = s:chsh(1) 2036 | let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd 2037 | if s:is_win 2038 | let batchfile = tempname().'.bat' 2039 | call writefile(["@echo off\r", cmd . "\r"], batchfile) 2040 | let cmd = s:shellesc(batchfile) 2041 | endif 2042 | return system(cmd) 2043 | finally 2044 | let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] 2045 | if s:is_win 2046 | call delete(batchfile) 2047 | endif 2048 | endtry 2049 | endfunction 2050 | 2051 | function! s:system_chomp(...) 2052 | let ret = call('s:system', a:000) 2053 | return v:shell_error ? '' : substitute(ret, '\n$', '', '') 2054 | endfunction 2055 | 2056 | function! s:git_validate(spec, check_branch) 2057 | let err = '' 2058 | if isdirectory(a:spec.dir) 2059 | let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url', a:spec.dir)) 2060 | let remote = result[-1] 2061 | if v:shell_error 2062 | let err = join([remote, 'PlugClean required.'], "\n") 2063 | elseif !s:compare_git_uri(remote, a:spec.uri) 2064 | let err = join(['Invalid URI: '.remote, 2065 | \ 'Expected: '.a:spec.uri, 2066 | \ 'PlugClean required.'], "\n") 2067 | elseif a:check_branch && has_key(a:spec, 'commit') 2068 | let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir)) 2069 | let sha = result[-1] 2070 | if v:shell_error 2071 | let err = join(add(result, 'PlugClean required.'), "\n") 2072 | elseif !s:hash_match(sha, a:spec.commit) 2073 | let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', 2074 | \ a:spec.commit[:6], sha[:6]), 2075 | \ 'PlugUpdate required.'], "\n") 2076 | endif 2077 | elseif a:check_branch 2078 | let branch = result[0] 2079 | " Check tag 2080 | if has_key(a:spec, 'tag') 2081 | let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) 2082 | if a:spec.tag !=# tag && a:spec.tag !~ '\*' 2083 | let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', 2084 | \ (empty(tag) ? 'N/A' : tag), a:spec.tag) 2085 | endif 2086 | " Check branch 2087 | elseif a:spec.branch !=# branch 2088 | let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', 2089 | \ branch, a:spec.branch) 2090 | endif 2091 | if empty(err) 2092 | let [ahead, behind] = split(s:lastline(s:system(printf( 2093 | \ 'git rev-list --count --left-right HEAD...origin/%s', 2094 | \ a:spec.branch), a:spec.dir)), '\t') 2095 | if !v:shell_error && ahead 2096 | if behind 2097 | " Only mention PlugClean if diverged, otherwise it's likely to be 2098 | " pushable (and probably not that messed up). 2099 | let err = printf( 2100 | \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" 2101 | \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind) 2102 | else 2103 | let err = printf("Ahead of origin/%s by %d commit(s).\n" 2104 | \ .'Cannot update until local changes are pushed.', 2105 | \ a:spec.branch, ahead) 2106 | endif 2107 | endif 2108 | endif 2109 | endif 2110 | else 2111 | let err = 'Not found' 2112 | endif 2113 | return [err, err =~# 'PlugClean'] 2114 | endfunction 2115 | 2116 | function! s:rm_rf(dir) 2117 | if isdirectory(a:dir) 2118 | call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(a:dir)) 2119 | endif 2120 | endfunction 2121 | 2122 | function! s:clean(force) 2123 | call s:prepare() 2124 | call append(0, 'Searching for invalid plugins in '.g:plug_home) 2125 | call append(1, '') 2126 | 2127 | " List of valid directories 2128 | let dirs = [] 2129 | let errs = {} 2130 | let [cnt, total] = [0, len(g:plugs)] 2131 | for [name, spec] in items(g:plugs) 2132 | if !s:is_managed(name) 2133 | call add(dirs, spec.dir) 2134 | else 2135 | let [err, clean] = s:git_validate(spec, 1) 2136 | if clean 2137 | let errs[spec.dir] = s:lines(err)[0] 2138 | else 2139 | call add(dirs, spec.dir) 2140 | endif 2141 | endif 2142 | let cnt += 1 2143 | call s:progress_bar(2, repeat('=', cnt), total) 2144 | normal! 2G 2145 | redraw 2146 | endfor 2147 | 2148 | let allowed = {} 2149 | for dir in dirs 2150 | let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1 2151 | let allowed[dir] = 1 2152 | for child in s:glob_dir(dir) 2153 | let allowed[child] = 1 2154 | endfor 2155 | endfor 2156 | 2157 | let todo = [] 2158 | let found = sort(s:glob_dir(g:plug_home)) 2159 | while !empty(found) 2160 | let f = remove(found, 0) 2161 | if !has_key(allowed, f) && isdirectory(f) 2162 | call add(todo, f) 2163 | call append(line('$'), '- ' . f) 2164 | if has_key(errs, f) 2165 | call append(line('$'), ' ' . errs[f]) 2166 | endif 2167 | let found = filter(found, 'stridx(v:val, f) != 0') 2168 | end 2169 | endwhile 2170 | 2171 | 4 2172 | redraw 2173 | if empty(todo) 2174 | call append(line('$'), 'Already clean.') 2175 | else 2176 | let s:clean_count = 0 2177 | call append(3, ['Directories to delete:', '']) 2178 | redraw! 2179 | if a:force || s:ask_no_interrupt('Delete all directories?') 2180 | call s:delete([6, line('$')], 1) 2181 | else 2182 | call setline(4, 'Cancelled.') 2183 | nnoremap d :set opfunc=delete_opg@ 2184 | nmap dd d_ 2185 | xnoremap d :call delete_op(visualmode(), 1) 2186 | echo 'Delete the lines (d{motion}) to delete the corresponding directories' 2187 | endif 2188 | endif 2189 | 4 2190 | setlocal nomodifiable 2191 | endfunction 2192 | 2193 | function! s:delete_op(type, ...) 2194 | call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) 2195 | endfunction 2196 | 2197 | function! s:delete(range, force) 2198 | let [l1, l2] = a:range 2199 | let force = a:force 2200 | while l1 <= l2 2201 | let line = getline(l1) 2202 | if line =~ '^- ' && isdirectory(line[2:]) 2203 | execute l1 2204 | redraw! 2205 | let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) 2206 | let force = force || answer > 1 2207 | if answer 2208 | call s:rm_rf(line[2:]) 2209 | setlocal modifiable 2210 | call setline(l1, '~'.line[1:]) 2211 | let s:clean_count += 1 2212 | call setline(4, printf('Removed %d directories.', s:clean_count)) 2213 | setlocal nomodifiable 2214 | endif 2215 | endif 2216 | let l1 += 1 2217 | endwhile 2218 | endfunction 2219 | 2220 | function! s:upgrade() 2221 | echo 'Downloading the latest version of vim-plug' 2222 | redraw 2223 | let tmp = tempname() 2224 | let new = tmp . '/plug.vim' 2225 | 2226 | try 2227 | let out = s:system(printf('git clone --depth 1 %s %s', s:shellesc(s:plug_src), s:shellesc(tmp))) 2228 | if v:shell_error 2229 | return s:err('Error upgrading vim-plug: '. out) 2230 | endif 2231 | 2232 | if readfile(s:me) ==# readfile(new) 2233 | echo 'vim-plug is already up-to-date' 2234 | return 0 2235 | else 2236 | call rename(s:me, s:me . '.old') 2237 | call rename(new, s:me) 2238 | unlet g:loaded_plug 2239 | echo 'vim-plug has been upgraded' 2240 | return 1 2241 | endif 2242 | finally 2243 | silent! call s:rm_rf(tmp) 2244 | endtry 2245 | endfunction 2246 | 2247 | function! s:upgrade_specs() 2248 | for spec in values(g:plugs) 2249 | let spec.frozen = get(spec, 'frozen', 0) 2250 | endfor 2251 | endfunction 2252 | 2253 | function! s:status() 2254 | call s:prepare() 2255 | call append(0, 'Checking plugins') 2256 | call append(1, '') 2257 | 2258 | let ecnt = 0 2259 | let unloaded = 0 2260 | let [cnt, total] = [0, len(g:plugs)] 2261 | for [name, spec] in items(g:plugs) 2262 | let is_dir = isdirectory(spec.dir) 2263 | if has_key(spec, 'uri') 2264 | if is_dir 2265 | let [err, _] = s:git_validate(spec, 1) 2266 | let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] 2267 | else 2268 | let [valid, msg] = [0, 'Not found. Try PlugInstall.'] 2269 | endif 2270 | else 2271 | if is_dir 2272 | let [valid, msg] = [1, 'OK'] 2273 | else 2274 | let [valid, msg] = [0, 'Not found.'] 2275 | endif 2276 | endif 2277 | let cnt += 1 2278 | let ecnt += !valid 2279 | " `s:loaded` entry can be missing if PlugUpgraded 2280 | if is_dir && get(s:loaded, name, -1) == 0 2281 | let unloaded = 1 2282 | let msg .= ' (not loaded)' 2283 | endif 2284 | call s:progress_bar(2, repeat('=', cnt), total) 2285 | call append(3, s:format_message(valid ? '-' : 'x', name, msg)) 2286 | normal! 2G 2287 | redraw 2288 | endfor 2289 | call setline(1, 'Finished. '.ecnt.' error(s).') 2290 | normal! gg 2291 | setlocal nomodifiable 2292 | if unloaded 2293 | echo "Press 'L' on each line to load plugin, or 'U' to update" 2294 | nnoremap L :call status_load(line('.')) 2295 | xnoremap L :call status_load(line('.')) 2296 | end 2297 | endfunction 2298 | 2299 | function! s:extract_name(str, prefix, suffix) 2300 | return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') 2301 | endfunction 2302 | 2303 | function! s:status_load(lnum) 2304 | let line = getline(a:lnum) 2305 | let name = s:extract_name(line, '-', '(not loaded)') 2306 | if !empty(name) 2307 | call plug#load(name) 2308 | setlocal modifiable 2309 | call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) 2310 | setlocal nomodifiable 2311 | endif 2312 | endfunction 2313 | 2314 | function! s:status_update() range 2315 | let lines = getline(a:firstline, a:lastline) 2316 | let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') 2317 | if !empty(names) 2318 | echo 2319 | execute 'PlugUpdate' join(names) 2320 | endif 2321 | endfunction 2322 | 2323 | function! s:is_preview_window_open() 2324 | silent! wincmd P 2325 | if &previewwindow 2326 | wincmd p 2327 | return 1 2328 | endif 2329 | endfunction 2330 | 2331 | function! s:find_name(lnum) 2332 | for lnum in reverse(range(1, a:lnum)) 2333 | let line = getline(lnum) 2334 | if empty(line) 2335 | return '' 2336 | endif 2337 | let name = s:extract_name(line, '-', '') 2338 | if !empty(name) 2339 | return name 2340 | endif 2341 | endfor 2342 | return '' 2343 | endfunction 2344 | 2345 | function! s:preview_commit() 2346 | if b:plug_preview < 0 2347 | let b:plug_preview = !s:is_preview_window_open() 2348 | endif 2349 | 2350 | let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') 2351 | if empty(sha) 2352 | return 2353 | endif 2354 | 2355 | let name = s:find_name(line('.')) 2356 | if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) 2357 | return 2358 | endif 2359 | 2360 | if exists('g:plug_pwindow') && !s:is_preview_window_open() 2361 | execute g:plug_pwindow 2362 | execute 'e' sha 2363 | else 2364 | execute 'pedit' sha 2365 | wincmd P 2366 | endif 2367 | setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable 2368 | try 2369 | let [sh, shellcmdflag, shrd] = s:chsh(1) 2370 | let cmd = 'cd '.s:shellesc(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha 2371 | if s:is_win 2372 | let batchfile = tempname().'.bat' 2373 | call writefile(["@echo off\r", cmd . "\r"], batchfile) 2374 | let cmd = batchfile 2375 | endif 2376 | execute 'silent %!' cmd 2377 | finally 2378 | let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] 2379 | if s:is_win 2380 | call delete(batchfile) 2381 | endif 2382 | endtry 2383 | setlocal nomodifiable 2384 | nnoremap q :q 2385 | wincmd p 2386 | endfunction 2387 | 2388 | function! s:section(flags) 2389 | call search('\(^[x-] \)\@<=[^:]\+:', a:flags) 2390 | endfunction 2391 | 2392 | function! s:format_git_log(line) 2393 | let indent = ' ' 2394 | let tokens = split(a:line, nr2char(1)) 2395 | if len(tokens) != 5 2396 | return indent.substitute(a:line, '\s*$', '', '') 2397 | endif 2398 | let [graph, sha, refs, subject, date] = tokens 2399 | let tag = matchstr(refs, 'tag: [^,)]\+') 2400 | let tag = empty(tag) ? ' ' : ' ('.tag.') ' 2401 | return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date) 2402 | endfunction 2403 | 2404 | function! s:append_ul(lnum, text) 2405 | call append(a:lnum, ['', a:text, repeat('-', len(a:text))]) 2406 | endfunction 2407 | 2408 | function! s:diff() 2409 | call s:prepare() 2410 | call append(0, ['Collecting changes ...', '']) 2411 | let cnts = [0, 0] 2412 | let bar = '' 2413 | let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)') 2414 | call s:progress_bar(2, bar, len(total)) 2415 | for origin in [1, 0] 2416 | let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))')))) 2417 | if empty(plugs) 2418 | continue 2419 | endif 2420 | call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:') 2421 | for [k, v] in plugs 2422 | let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..' 2423 | let cmd = 'git log --graph --color=never '.join(map(['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range], 's:shellesc(v:val)')) 2424 | if has_key(v, 'rtp') 2425 | let cmd .= ' -- '.s:shellesc(v.rtp) 2426 | endif 2427 | let diff = s:system_chomp(cmd, v.dir) 2428 | if !empty(diff) 2429 | let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : '' 2430 | call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)'))) 2431 | let cnts[origin] += 1 2432 | endif 2433 | let bar .= '=' 2434 | call s:progress_bar(2, bar, len(total)) 2435 | normal! 2G 2436 | redraw 2437 | endfor 2438 | if !cnts[origin] 2439 | call append(5, ['', 'N/A']) 2440 | endif 2441 | endfor 2442 | call setline(1, printf('%d plugin(s) updated.', cnts[0]) 2443 | \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : '')) 2444 | 2445 | if cnts[0] || cnts[1] 2446 | nnoremap (plug-preview) :silent! call preview_commit() 2447 | if empty(maparg("\", 'n')) 2448 | nmap (plug-preview) 2449 | endif 2450 | if empty(maparg('o', 'n')) 2451 | nmap o (plug-preview) 2452 | endif 2453 | endif 2454 | if cnts[0] 2455 | nnoremap X :call revert() 2456 | echo "Press 'X' on each block to revert the update" 2457 | endif 2458 | normal! gg 2459 | setlocal nomodifiable 2460 | endfunction 2461 | 2462 | function! s:revert() 2463 | if search('^Pending updates', 'bnW') 2464 | return 2465 | endif 2466 | 2467 | let name = s:find_name(line('.')) 2468 | if empty(name) || !has_key(g:plugs, name) || 2469 | \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' 2470 | return 2471 | endif 2472 | 2473 | call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch).' --', g:plugs[name].dir) 2474 | setlocal modifiable 2475 | normal! "_dap 2476 | setlocal nomodifiable 2477 | echo 'Reverted' 2478 | endfunction 2479 | 2480 | function! s:snapshot(force, ...) abort 2481 | call s:prepare() 2482 | setf vim 2483 | call append(0, ['" Generated by vim-plug', 2484 | \ '" '.strftime("%c"), 2485 | \ '" :source this file in vim to restore the snapshot', 2486 | \ '" or execute: vim -S snapshot.vim', 2487 | \ '', '', 'PlugUpdate!']) 2488 | 1 2489 | let anchor = line('$') - 3 2490 | let names = sort(keys(filter(copy(g:plugs), 2491 | \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)'))) 2492 | for name in reverse(names) 2493 | let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir) 2494 | if !empty(sha) 2495 | call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha)) 2496 | redraw 2497 | endif 2498 | endfor 2499 | 2500 | if a:0 > 0 2501 | let fn = expand(a:1) 2502 | if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?')) 2503 | return 2504 | endif 2505 | call writefile(getline(1, '$'), fn) 2506 | echo 'Saved as '.a:1 2507 | silent execute 'e' s:esc(fn) 2508 | setf vim 2509 | endif 2510 | endfunction 2511 | 2512 | function! s:split_rtp() 2513 | return split(&rtp, '\\\@