├── README.md ├── setup.sh ├── colors └── molokai.vim ├── vimrc └── autoload ├── plug.vim.old └── plug.vim /README.md: -------------------------------------------------------------------------------- 1 | vim 2 | === 3 | vim 配置,私人订制,停止更新, 已转spaceVim 4 | 5 | ### 说明 6 | 兼容Linux、Mac 7 | 8 | 为了更好的支持airline状态栏插件,请安装字体 [powerline-fonts](https://github.com/Lokaltog/powerline-fonts) 9 | 10 | ### 安装 11 | 12 | ``` 13 | git clone https://github.com/Fechin/oh-my-vim.git ~/.vim 14 | ``` 15 | 16 | ``` 17 | ./setup.sh 18 | ``` 19 | 可选参数:启动vim的命令,默认为“vim”,如./setup.sh gvim 或 ./setup.sh MacVim 20 | 21 | #### 快捷键 22 | ``` 23 | nnoremap w :w! 24 | inoremap w :w! 25 | " ,W 26 | nnoremap W :w !sudo tee > /dev/null % 27 | inoremap W :w !sudo tee > /dev/null % 28 | " ,r 运行 29 | nnoremap r :call CompileAndRun() 30 | inoremap r :call CompileAndRun() 31 | " ,e 文件浏览器 32 | nnoremap e :NERDTreeToggle 33 | " ,s 使vimrc生效 34 | nnoremap ss :source ~/.vim/vimrc 35 | " ,p 切换粘贴模式 36 | nnoremap p :set invpaste paste? 37 | " ,m/,M 切换Buffer 38 | nnoremap m :bNext 39 | nnoremap M :bprevious 40 | " ,a 全选 41 | nnoremap a ggVG" 42 | 43 | " J/K 移动选中内容 44 | vnoremap J :m '>+1gv=gv 45 | vnoremap K :m '<-2gv=gv 46 | 47 | map tn :tabnext 48 | map tp :tabprevious 49 | 50 | ``` 51 | 52 | #### 插件 53 | --- 54 | ``` 55 | " 状态栏 56 | Plug 'vim-airline/vim-airline' 57 | Plug 'vim-airline/vim-airline-themes' 58 | " 文件目录树 59 | Plug 'vim-scripts/The-NERD-tree' 60 | " HTML/CSS代码快速生成 61 | Plug 'mattn/emmet-vim' 62 | " 搜索文件 63 | Plug 'kien/ctrlp.vim' 64 | " 新建文件自动加载模板 65 | Plug 'aperezdc/vim-template' 66 | " 文本更衣 67 | Plug 'tpope/vim-surround' 68 | " 括号自动补全 69 | Plug 'vim-scripts/Auto-Pairs' 70 | " 语法检查 71 | Plug 'scrooloose/syntastic' 72 | " 代码补全 73 | Plug 'Valloric/YouCompleteMe' 74 | " 模板生成补全 75 | Plug 'SirVer/ultisnips' 76 | " snippets 77 | Plug 'honza/vim-snippets' 78 | " 平滑滚动 79 | Plug 'joeytwiddle/sexy_scroller.vim' 80 | " 缩进对齐线 81 | Plug 'Yggdroot/indentLine' 82 | " 文本智能对齐 83 | Plug 'junegunn/vim-easy-align' 84 | " 代码注释 85 | Plug 'tomtom/tcomment_vim' 86 | " 华丽的代码截图Web版 87 | Plug 'mattr555/vim-instacode' 88 | " 神级跳转 89 | Plug 'easymotion/vim-easymotion' 90 | " 优雅的光标 91 | Plug 'terryma/vim-multiple-cursors' 92 | " 优雅的格式化 93 | Plug 'maksimr/vim-jsbeautify' 94 | " 优雅的Git管理 95 | Plug 'tpope/vim-fugitive' 96 | ``` 97 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # setup.sh [vim] 4 | # 参数:启动vim的命令,默认为“vim” 5 | # Copyright (C) 2014 Fechin 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | set -o errexit 10 | 11 | # 系统检测 12 | OS=$(uname) 13 | 14 | if [ ${OS} != "Darwin" ] && [ ${OS} != "Linux" ]; then 15 | echo -e "暂时不支持${OS}系统环境!" 16 | exit 1 17 | fi 18 | 19 | # 取第一个参数作为启动vim命令,如果为空默认“vim” 20 | VIM_COMMAND=${1:-vim} 21 | 22 | # 插件目录,跟vimrc中的保持一致 23 | PLUG_DIR=${HOME}/.vim/plugged 24 | 25 | echo -e "-----------------------------------------------------------" 26 | echo -e "---\t视网络情况,安装过程可能比较漫长,请耐心等待!\t---" 27 | echo -e "-----------------------------------------------------------\v" 28 | echo -e "将为你安装依赖包:" 29 | dependsmsg() { 30 | echo -e "-----------------------------\t---------------------------" 31 | echo -e "\t[*] build-essential\tDetails of package information" 32 | echo -e "\t[*] cmake \tCross-Platform Makefile Generator" 33 | echo -e "\t[*] python-dev \tDevelopment tools for building Python modules" 34 | echo -e "\t[*] pyflakes \tSimple Python 2 source checker" 35 | echo -e "\t[*] node.js \tEvent-driven I/O server-side JavaScript environment based on V8." 36 | echo -e "\t[*] markdown \tConvert text to HTML" 37 | echo -e "\t[*] git \tPerl interface to the Git version control system" 38 | echo -e "\t[*] easy_install \tManage Python packages\v" 39 | };dependsmsg 40 | 41 | notify() { 42 | echo -e "\e[30;48;5;82m Message: \e[0m \e[40;38;5;82m $1 \e[0m " 43 | if hash notify-send 2>/dev/null; then 44 | notify-send -i "notification-message-im" "Oh-my-vim 提示" "$1" 45 | fi 46 | } 47 | 48 | # Linux 系统安装 49 | if hash apt-get 2>/dev/null; then 50 | sudo apt-get install -y build-essential cmake python-dev pyflakes node npm markdown git libxml2 51 | elif hash yum 2>/dev/null; then 52 | sudo yum install -y build-essential cmake python-dev pyflakes node markdown git libxml2 53 | fi 54 | 55 | # Mac 系统安装 56 | if hash brew 2>/dev/null; then 57 | sudo easy_install pyflakes 58 | brew install cmake node markdown libxml2 59 | fi 60 | 61 | # 安装JS语法检查工具 62 | JSHINT=/usr/local/bin/jshint 63 | hash jshint 2>/dev/null || { 64 | sudo npm install jshint -g 65 | if [ -e $JSHINT ]; then 66 | echo '' 67 | # sed -i "/env /s/node$/nodejs/g" $JSHINT 68 | fi 69 | } 70 | 71 | PLUGINSTALL="PlugInstall" 72 | 73 | if hash ${VIM_COMMAND} 2>/dev/null; then 74 | ${VIM_COMMAND} -c "PlugUpgrade" -c "${PLUGINSTALL}" -c "q" -c "q" 75 | else 76 | notify "没找到命令: ${VIM_COMMAND},请检查参数" 77 | exit 1 78 | fi 79 | 80 | # 定时检测vim是否退出 81 | if [ $? -eq 0 ]; then 82 | while true; do 83 | ps -ef | grep -v grep | grep -v "setup.sh" | grep "${PLUGINSTALL}" > /dev/null || break 84 | echo -ne "正在安装插件:[>>>>> ]\r" 85 | sleep 0.5 86 | echo -ne "正在安装插件:[>>>>>>>>>>>>> ]\r" 87 | sleep 0.5 88 | echo -ne "正在安装插件:[>>>>>>>>>>>>>>>>>>>>>>>]\r" 89 | sleep 0.5 90 | done 91 | else 92 | notify "command [${VIM_COMMAND}] exception!" 93 | exit 1 94 | fi 95 | 96 | # do something 97 | if [ -d "${PLUG_DIR}/YouCompleteMe/" ]; then 98 | YCM_DIR=${PLUG_DIR}/YouCompleteMe/third_party/ycmd 99 | if [ ! -e "${YCM_DIR}/build.sh" ]; then 100 | cd ${YCM_DIR} && git submodule update --init --recursive 101 | fi 102 | 103 | cd ${PLUG_DIR}/YouCompleteMe 104 | ./install.sh 105 | #./install.sh --clang-completer 106 | else 107 | notify "YouCompleteMe 安装失败,请尝试重新执行 install.sh" 108 | exit 1 109 | fi 110 | notify "Successfully,Never stop the beat, Enjoy it please :-)" 111 | -------------------------------------------------------------------------------- /colors/molokai.vim: -------------------------------------------------------------------------------- 1 | " Vim color file 2 | " 3 | " Author: Tomas Restrepo 4 | " https://github.com/tomasr/molokai 5 | " 6 | " Note: Based on the Monokai theme for TextMate 7 | " by Wimer Hazenberg and its darker variant 8 | " by Hamish Stuart Macpherson 9 | " 10 | 11 | hi clear 12 | 13 | if version > 580 14 | " no guarantees for version 5.8 and below, but this makes it stop 15 | " complaining 16 | hi clear 17 | if exists("syntax_on") 18 | syntax reset 19 | endif 20 | endif 21 | let g:colors_name="molokai" 22 | 23 | if exists("g:molokai_original") 24 | let s:molokai_original = g:molokai_original 25 | else 26 | let s:molokai_original = 0 27 | endif 28 | 29 | 30 | hi Boolean guifg=#AE81FF 31 | hi Character guifg=#E6DB74 32 | hi Number guifg=#AE81FF 33 | hi String guifg=#E6DB74 34 | hi Conditional guifg=#F92672 gui=bold 35 | hi Constant guifg=#AE81FF gui=bold 36 | hi Cursor guifg=#000000 guibg=#F8F8F0 37 | hi iCursor guifg=#000000 guibg=#F8F8F0 38 | hi Debug guifg=#BCA3A3 gui=bold 39 | hi Define guifg=#66D9EF 40 | hi Delimiter guifg=#8F8F8F 41 | hi DiffAdd guibg=#13354A 42 | hi DiffChange guifg=#89807D guibg=#4C4745 43 | hi DiffDelete guifg=#960050 guibg=#1E0010 44 | hi DiffText guibg=#4C4745 gui=italic,bold 45 | 46 | hi Directory guifg=#A6E22E gui=bold 47 | hi Error guifg=#E6DB74 guibg=#1E0010 48 | hi ErrorMsg guifg=#F92672 guibg=#232526 gui=bold 49 | hi Exception guifg=#A6E22E gui=bold 50 | hi Float guifg=#AE81FF 51 | hi FoldColumn guifg=#465457 guibg=#000000 52 | hi Folded guifg=#465457 guibg=#000000 53 | hi Function guifg=#A6E22E 54 | hi Identifier guifg=#FD971F 55 | hi Ignore guifg=#808080 guibg=bg 56 | hi IncSearch guifg=#C4BE89 guibg=#000000 57 | 58 | hi Keyword guifg=#F92672 gui=bold 59 | hi Label guifg=#E6DB74 gui=none 60 | hi Macro guifg=#C4BE89 gui=italic 61 | hi SpecialKey guifg=#66D9EF gui=italic 62 | 63 | hi MatchParen guifg=#000000 guibg=#FD971F gui=bold 64 | hi ModeMsg guifg=#E6DB74 65 | hi MoreMsg guifg=#E6DB74 66 | hi Operator guifg=#F92672 67 | 68 | " complete menu 69 | hi Pmenu guifg=#66D9EF guibg=#000000 70 | hi PmenuSel guibg=#808080 71 | hi PmenuSbar guibg=#080808 72 | hi PmenuThumb guifg=#66D9EF 73 | 74 | hi PreCondit guifg=#A6E22E gui=bold 75 | hi PreProc guifg=#A6E22E 76 | hi Question guifg=#66D9EF 77 | hi Repeat guifg=#F92672 gui=bold 78 | hi Search guifg=#000000 guibg=#FFE792 79 | " marks 80 | hi SignColumn guifg=#A6E22E guibg=#232526 81 | hi SpecialChar guifg=#F92672 gui=bold 82 | hi SpecialComment guifg=#7E8E91 gui=bold 83 | hi Special guifg=#66D9EF guibg=bg gui=italic 84 | if has("spell") 85 | hi SpellBad guisp=#FF0000 gui=undercurl 86 | hi SpellCap guisp=#7070F0 gui=undercurl 87 | hi SpellLocal guisp=#70F0F0 gui=undercurl 88 | hi SpellRare guisp=#FFFFFF gui=undercurl 89 | endif 90 | hi Statement guifg=#F92672 gui=bold 91 | hi StatusLine guifg=#455354 guibg=fg 92 | hi StatusLineNC guifg=#808080 guibg=#080808 93 | hi StorageClass guifg=#FD971F gui=italic 94 | hi Structure guifg=#66D9EF 95 | hi Tag guifg=#F92672 gui=italic 96 | hi Title guifg=#ef5939 97 | hi Todo guifg=#FFFFFF guibg=bg gui=bold 98 | 99 | hi Typedef guifg=#66D9EF 100 | hi Type guifg=#66D9EF gui=none 101 | hi Underlined guifg=#808080 gui=underline 102 | 103 | hi VertSplit guifg=#808080 guibg=#080808 gui=bold 104 | hi VisualNOS guibg=#403D3D 105 | hi Visual guibg=#403D3D 106 | hi WarningMsg guifg=#FFFFFF guibg=#333333 gui=bold 107 | hi WildMenu guifg=#66D9EF guibg=#000000 108 | 109 | hi TabLineFill guifg=#1B1D1E guibg=#1B1D1E 110 | hi TabLine guibg=#1B1D1E guifg=#808080 gui=none 111 | 112 | if s:molokai_original == 1 113 | hi Normal guifg=#F8F8F2 guibg=#272822 114 | hi Comment guifg=#75715E 115 | hi CursorLine guibg=#3E3D32 116 | hi CursorLineNr guifg=#FD971F gui=none 117 | hi CursorColumn guibg=#3E3D32 118 | hi ColorColumn guibg=#3B3A32 119 | hi LineNr guifg=#BCBCBC guibg=#3B3A32 120 | hi NonText guifg=#75715E 121 | hi SpecialKey guifg=#75715E 122 | else 123 | hi Normal guifg=#F8F8F2 guibg=#1B1D1E 124 | hi Comment guifg=#7E8E91 125 | hi CursorLine guibg=#293739 126 | hi CursorLineNr guifg=#FD971F gui=none 127 | hi CursorColumn guibg=#293739 128 | hi ColorColumn guibg=#232526 129 | hi LineNr guifg=#465457 guibg=#232526 130 | hi NonText guifg=#465457 131 | hi SpecialKey guifg=#465457 132 | end 133 | 134 | " 135 | " Support for 256-color terminal 136 | " 137 | if &t_Co > 255 138 | if s:molokai_original == 1 139 | hi Normal ctermbg=234 140 | hi CursorLine ctermbg=235 cterm=none 141 | hi CursorLineNr ctermfg=208 cterm=none 142 | else 143 | hi Normal ctermfg=252 ctermbg=233 144 | hi CursorLine ctermbg=234 cterm=none 145 | hi CursorLineNr ctermfg=208 cterm=none 146 | endif 147 | hi Boolean ctermfg=135 148 | hi Character ctermfg=144 149 | hi Number ctermfg=135 150 | hi String ctermfg=144 151 | hi Conditional ctermfg=161 cterm=bold 152 | hi Constant ctermfg=135 cterm=bold 153 | hi Cursor ctermfg=16 ctermbg=253 154 | hi Debug ctermfg=225 cterm=bold 155 | hi Define ctermfg=81 156 | hi Delimiter ctermfg=241 157 | 158 | hi DiffAdd ctermbg=24 159 | hi DiffChange ctermfg=181 ctermbg=239 160 | hi DiffDelete ctermfg=162 ctermbg=53 161 | hi DiffText ctermbg=102 cterm=bold 162 | 163 | hi Directory ctermfg=118 cterm=bold 164 | hi Error ctermfg=219 ctermbg=89 165 | hi ErrorMsg ctermfg=199 ctermbg=16 cterm=bold 166 | hi Exception ctermfg=118 cterm=bold 167 | hi Float ctermfg=135 168 | hi FoldColumn ctermfg=67 ctermbg=16 169 | hi Folded ctermfg=67 ctermbg=16 170 | hi Function ctermfg=118 171 | hi Identifier ctermfg=208 cterm=none 172 | hi Ignore ctermfg=244 ctermbg=232 173 | hi IncSearch ctermfg=193 ctermbg=16 174 | 175 | hi keyword ctermfg=161 cterm=bold 176 | hi Label ctermfg=229 cterm=none 177 | hi Macro ctermfg=193 178 | hi SpecialKey ctermfg=81 179 | 180 | hi MatchParen ctermfg=233 ctermbg=208 cterm=bold 181 | hi ModeMsg ctermfg=229 182 | hi MoreMsg ctermfg=229 183 | hi Operator ctermfg=161 184 | 185 | " complete menu 186 | hi Pmenu ctermfg=81 ctermbg=16 187 | hi PmenuSel ctermfg=255 ctermbg=242 188 | hi PmenuSbar ctermbg=232 189 | hi PmenuThumb ctermfg=81 190 | 191 | hi PreCondit ctermfg=118 cterm=bold 192 | hi PreProc ctermfg=118 193 | hi Question ctermfg=81 194 | hi Repeat ctermfg=161 cterm=bold 195 | hi Search ctermfg=0 ctermbg=222 cterm=NONE 196 | 197 | " marks column 198 | hi SignColumn ctermfg=118 ctermbg=235 199 | hi SpecialChar ctermfg=161 cterm=bold 200 | hi SpecialComment ctermfg=245 cterm=bold 201 | hi Special ctermfg=81 202 | if has("spell") 203 | hi SpellBad ctermbg=52 204 | hi SpellCap ctermbg=17 205 | hi SpellLocal ctermbg=17 206 | hi SpellRare ctermfg=none ctermbg=none cterm=reverse 207 | endif 208 | hi Statement ctermfg=161 cterm=bold 209 | hi StatusLine ctermfg=238 ctermbg=253 210 | hi StatusLineNC ctermfg=244 ctermbg=232 211 | hi StorageClass ctermfg=208 212 | hi Structure ctermfg=81 213 | hi Tag ctermfg=161 214 | hi Title ctermfg=166 215 | hi Todo ctermfg=231 ctermbg=232 cterm=bold 216 | 217 | hi Typedef ctermfg=81 218 | hi Type ctermfg=81 cterm=none 219 | hi Underlined ctermfg=244 cterm=underline 220 | 221 | hi VertSplit ctermfg=244 ctermbg=232 cterm=bold 222 | hi VisualNOS ctermbg=238 223 | hi Visual ctermbg=235 224 | hi WarningMsg ctermfg=231 ctermbg=238 cterm=bold 225 | hi WildMenu ctermfg=81 ctermbg=16 226 | 227 | hi Comment ctermfg=59 228 | hi CursorColumn ctermbg=236 229 | hi ColorColumn ctermbg=236 230 | hi LineNr ctermfg=250 ctermbg=236 231 | hi NonText ctermfg=59 232 | 233 | hi SpecialKey ctermfg=59 234 | 235 | if exists("g:rehash256") && g:rehash256 == 1 236 | hi Normal ctermfg=252 ctermbg=234 237 | hi CursorLine ctermbg=236 cterm=none 238 | hi CursorLineNr ctermfg=208 cterm=none 239 | 240 | hi Boolean ctermfg=141 241 | hi Character ctermfg=222 242 | hi Number ctermfg=141 243 | hi String ctermfg=222 244 | hi Conditional ctermfg=197 cterm=bold 245 | hi Constant ctermfg=141 cterm=bold 246 | 247 | hi DiffDelete ctermfg=125 ctermbg=233 248 | 249 | hi Directory ctermfg=154 cterm=bold 250 | hi Error ctermfg=222 ctermbg=233 251 | hi Exception ctermfg=154 cterm=bold 252 | hi Float ctermfg=141 253 | hi Function ctermfg=154 254 | hi Identifier ctermfg=208 255 | 256 | hi Keyword ctermfg=197 cterm=bold 257 | hi Operator ctermfg=197 258 | hi PreCondit ctermfg=154 cterm=bold 259 | hi PreProc ctermfg=154 260 | hi Repeat ctermfg=197 cterm=bold 261 | 262 | hi Statement ctermfg=197 cterm=bold 263 | hi Tag ctermfg=197 264 | hi Title ctermfg=203 265 | hi Visual ctermbg=238 266 | 267 | hi Comment ctermfg=244 268 | hi LineNr ctermfg=239 ctermbg=235 269 | hi NonText ctermfg=239 270 | hi SpecialKey ctermfg=239 271 | endif 272 | end 273 | 274 | " Must be at the end, because of ctermbg=234 bug. 275 | " https://groups.google.com/forum/#!msg/vim_dev/afPqwAFNdrU/nqh6tOM87QUJ 276 | set background=dark 277 | -------------------------------------------------------------------------------- /vimrc: -------------------------------------------------------------------------------- 1 | " oh-my-vim .vimrc 私人订制 2 | " 3 | " Date : 2014-07-09 4 | " Author : Fechin 5 | " E-mail : hedge.pike02@icloud.com 6 | " URL : https://github.com/Fechin/oh-my-vim.git 7 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 8 | 9 | "--> 系统检测 10 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 11 | let g:osName = "" 12 | let g:osDictionary = {"Linux":"linux","Darwin":"mac"} 13 | if has("unix") 14 | let s:uname = system("echo -n $(uname)") 15 | let g:osName = !v:shell_error ? g:osDictionary[s:uname] : "" 16 | endif 17 | 18 | "--> vim-template 19 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 20 | let g:username = "Fechin" 21 | let g:email = "hedge.pike02@icloud.com" 22 | 23 | "--> 基本设置 24 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 25 | syntax enable " 打开语法高亮 26 | syntax on " 允许按指定主题进行语法高亮,而非默认高亮主题 27 | filetype plugin indent on " 不同文件类型加载对应的插件,使用对应的缩进 28 | set nocompatible " 关闭Vi兼容模式 29 | set autoindent " 自动对齐 30 | set tabstop=4 " 设置Tab为4个空格 31 | set shiftwidth=4 " 自动缩减空格长度 32 | set smartindent " 智能自动缩进 33 | set smarttab " 智能Tab 34 | set mousemodel=popup " 允许鼠标右键 35 | set ruler " 右下角显示光标位置的状态行 36 | set incsearch " 开启实时搜索功能 37 | set hlsearch " 开启高亮显示结果 38 | set ignorecase " 搜索忽略大小写 39 | set shortmess=atI " 不显示乌干达儿童捐助提示 40 | set nowrapscan " 搜索到文件两端时不重新搜索 41 | set hidden " 有未保存的更改时可以切换缓冲区 42 | set laststatus=2 " 开启状态栏信息 43 | set nobackup " 不生成备份文件 44 | set noswapfile " 不生成交换文件 45 | set expandtab " 将Tab转换成空格[需输入真正的tab时,使用Ctrl+V, Tab] 46 | set showmatch " 显示括号配对情况 47 | set showcmd " 显示命令 48 | set noerrorbells " 关闭提示音 49 | set wildmenu " 在状态栏显示匹配命令 50 | set iskeyword+=_,$,@,%,#,- " 关键字不换行 51 | set t_Co=256 " 开启终端256色 52 | set number " 显示行号 53 | set cursorline " 高亮当前行 54 | let g:rehash256 = 1 " 配色高亮 55 | colorscheme molokai " 指定配色方案 56 | " 设置文件编码和文件格式 57 | set encoding =utf-8 58 | set termencoding =utf-8 59 | set fileencoding =utf-8 60 | set fileencodings =ucs-bom,utf-8,cp936,gb18030,big5,euc-jp,euc-kr,latin1 61 | 62 | " 全局忽略文件 63 | set wildignore+=*.png,*.jpg,*.bmp,*.gif " Binary images 64 | set wildignore+=*.pyc " Python byte code 65 | set wildignore+=*.sw? " Vim swap files 66 | set wildignore+=*.git,*.svn " Version control tool 67 | set wildignore+=*.DS_Store " OSX bullshit 68 | set wildignore+=*.tar.gz,*.zip,*.rar " Compressed file 69 | 70 | " 全局忽略目录 71 | set wildignore+=classes 72 | set wildignore+=lib 73 | 74 | "--> AirLine 75 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 76 | let g:airline_theme = "badwolf" 77 | let g:airline_powerline_fonts = 1 78 | let g:Powerline_symbols = "fancy" 79 | let g:airline#extensions#whitespace#enabled = 0 80 | let g:airline#extensions#tabline#enabled = 0 81 | let g:airline#extensions#tabline#show_tab_nr = 0 82 | let g:airline_section_b = "NEVER STOP THE BEAT" 83 | 84 | "--> YouCompleteMe 85 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 86 | let g:ycm_confirm_extra_conf = 0 " 打开vim时不再询问是否加载ycm_extra_conf.py配置 87 | let g:ycm_cache_omnifunc = 0 " 禁止缓存匹配项,每次都重新生成匹配项 88 | let g:ycm_seed_identifiers_with_syntax = 1 " 开启语义补全 89 | let g:ycm_complete_in_comments = 1 " 在注释中也可以补全 90 | let g:ycm_min_num_of_chars_for_completion = 1 " 输入第一个字符就开始补全 91 | 92 | "--> UltiSnips模板生成 93 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 94 | let g:UltiSnipsExpandTrigger = "" 95 | let g:UltiSnipsJumpForwardTrigger = "" 96 | let g:UltiSnipsJumpBackwardTrigger = "" 97 | let g:UltiSnipsSnippetDirectories = ["UltiSnips"] 98 | 99 | function! g:UltiSnips_Complete() 100 | call UltiSnips#ExpandSnippet() 101 | if g:ulti_expand_res == 0 102 | if pumvisible() 103 | return "\" 104 | else 105 | call UltiSnips#JumpForwards() 106 | if g:ulti_jump_forwards_res == 0 107 | return "\" 108 | endif 109 | endif 110 | endif 111 | return "" 112 | endfunction 113 | autocmd BufNewFile,BufRead *.snippets setf snippets 114 | " 自动调用 UltiSnipsAddFileTypes filetype 115 | " autocmd FileType * call UltiSnips#FileTypeChanged() 116 | autocmd InsertEnter * exec "inoremap " . g:UltiSnipsExpandTrigger . " =g:UltiSnips_Complete()" 117 | augroup Filetype_Specific 118 | autocmd FileType javascript nnoremap j :call JsBeautify() 119 | autocmd FileType html nnoremap j :call HtmlBeautify() 120 | autocmd FileType css nnoremap j :call CSSBeautify() 121 | autocmd FileType json nnoremap j :call JsonBeautify() 122 | autocmd FileType javascript vnoremap j :call RangeJsBeautify() 123 | autocmd FileType html vnoremap j :call RangeHtmlBeautify() 124 | autocmd FileType css vnoremap j :call RangeCSSBeautify() 125 | autocmd FileType json vnoremap j :call RangeCSSBeautify() 126 | augroup END 127 | 128 | 129 | "--> 自动执行 130 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 131 | augroup defaults 132 | " Clear augroup 133 | autocmd! 134 | " 离开插入模式后自动关闭预览窗口 135 | autocmd InsertLeave * if pumvisible() == 0|pclose|endif 136 | " 浅色高亮当前行 137 | autocmd InsertEnter * se cul 138 | autocmd BufRead,BufNewFile *.{md,mdown,mkd,mkdn,markdown,mdwn} set filetype=markdown 139 | " 打开文件时,自动跳转到光标最后所在的位置 140 | if has("autocmd") 141 | autocmd BufReadPost * if line("'\"") > 0 && line("'\"") <= line("$") 142 | \| exe "normal! g'\"" | endif 143 | endif 144 | augroup END 145 | 146 | "--> 语法检查 147 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 148 | let g:syntastic_always_populate_loc_list = 1 149 | let g:syntastic_check_on_open = 1 150 | let g:syntastic_enable_signs = 1 151 | let g:syntastic_javascript_checkers = ["jshint"] 152 | let g:syntastic_python_checkers = ["pyflakes"] 153 | let g:syntastic_html_checkers = ["jshint"] 154 | let g:syntastic_error_symbol = "✗" 155 | let g:syntastic_warning_symbol = "⚠" 156 | 157 | "--> Tcomment 158 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 159 | vmap (EasyAlign) 160 | nmap ga (EasyAlign) 161 | 162 | "--> NERDTree 163 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 164 | let NERDTreeAutoCenter = 1 " 窗口居中 165 | let NERDTreeShowBookmarks = 1 " 显示书签 166 | let NERDChristmasTree = 1 " 让树更好看 167 | let NERDTreeMinimalUI = 1 " 不显示帮助面板 168 | let NERDTreeCaseSensitiveSort = 1 " 让文件排列更有序 169 | let NERDTreeChDirMode = 1 " 改变tree目录的同时改变工程的目录 170 | let NERDTreeHijackNetrw = 1 " 当输入 [:e filename]不再显示netrw,而是显示nerdtree 171 | let NERDTreeBookmarksFile = $VIM."\Data\NerdBookmarks.txt" 172 | 173 | if exists("loaded_nerd_tree") 174 | autocmd VimEnter * NERDTree 175 | endif 176 | "当打开vim且没有文件时自动打开NERDTree 177 | autocmd vimenter * if !argc() | NERDTree | endif 178 | " 只剩 NERDTree时自动关闭 179 | autocmd bufenter * if (winnr("$") == 1 && exists("b:NERDTreeType") && b:NERDTreeType == "primary") | q | endif 180 | 181 | "--> 按键映射 182 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 183 | let g:mapleader = "," 184 | map :tabnew " 新tab 185 | map j " 窗口切换下 186 | map k " 窗口切换下 187 | map h " 窗口切换左 188 | map l " 窗口切换右 189 | map pj :%!python -m json.tool " 格式化JSON 190 | map px :%!xmllint % --encode UTF-8 --format " 格式化XMl 191 | map ps :%!sqlformat % -r -k upper -i lower --encoding UTF-8 " 格式化SQL 192 | 193 | " ,q 退出 194 | nnoremap q :qa! 195 | inoremap q :qa! 196 | " ,w 保存 197 | nnoremap w :w! 198 | inoremap w :w! 199 | " ,W 200 | nnoremap W :w !sudo tee > /dev/null % 201 | inoremap W :w !sudo tee > /dev/null % 202 | " ,r 运行 203 | nnoremap r :call CompileAndRun() 204 | inoremap r :call CompileAndRun() 205 | " ,e 文件浏览器 206 | nnoremap e :NERDTreeToggle 207 | " ,s 使vimrc生效 208 | nnoremap ss :source ~/.vim/vimrc 209 | " ,p 切换粘贴模式 210 | nnoremap p :set invpaste paste? 211 | " ,m/,M 切换Buffer 212 | nnoremap m :bNext 213 | nnoremap M :bprevious 214 | " J/K 移动选中内容 215 | vnoremap J :m '>+1gv=gv 216 | vnoremap K :m '<-2gv=gv 217 | " 缩进时依然选中 218 | vnoremap < >gv 220 | " ,a 全选 221 | nnoremap a ggVG 222 | 223 | map tn :tabnext 224 | map tp :tabprevious 225 | 226 | nnoremap :call CompileAndRun() 227 | inoremap 228 | 229 | if has('macunix') 230 | " pbcopy for OSX copy/paste 231 | vnoremap :!pbcopy 232 | vnoremap :w !pbcopy 233 | endif 234 | 235 | "--> 按F5编译运行 236 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 237 | function! CompileAndRun() 238 | exec "w" 239 | if &filetype == "java" 240 | exec "!javac %:t && java %:r" 241 | elseif &filetype == "c" 242 | exec "!gcc -Wall -std=c11 -o %:r %:t && ./%:r" 243 | elseif &filetype == "cpp" 244 | exec "!g++ -Wall -std=c++11 -o %:r %:t && ./%:r" 245 | elseif &filetype == "go" 246 | exec "!go build %:t && ./%:r" 247 | elseif &filetype == "sh" 248 | exec "!bash %:t" 249 | elseif &filetype == "lua" 250 | exec "!lua %:t" 251 | elseif &filetype == "perl" 252 | exec "!perl %:t" 253 | elseif &filetype == "php" 254 | exec "!php %:t" 255 | elseif &filetype == "python" 256 | exec "!python %:t" 257 | elseif &filetype == "ruby" 258 | exec "!ruby %:t" 259 | elseif &filetype == "html" 260 | if g:osName == "linux" 261 | exec "!gnome-open % > /dev/null 2>&1" 262 | elseif g:osName == "mac" 263 | exec "!open % > /dev/null 2>&1&" 264 | endif 265 | call feedkeys("\") 266 | elseif &filetype == "markdown" 267 | if g:osName == "linux" 268 | exec "!markdown % > %.html && gnome-open %.html > /dev/null 2>&1" 269 | elseif g:osName == "mac" 270 | exec "!markdown % > %.html && open %.html > /dev/null 2>&1" 271 | endif 272 | call feedkeys("\") 273 | endif 274 | endfunction 275 | 276 | "--> 图形界面配置 277 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 278 | if has("gui_running") 279 | set guioptions-=m " 隐藏菜单栏 280 | set guioptions-=T " 隐藏工具栏 281 | set guioptions-=L " 隐藏左侧滚动条 282 | set guioptions-=r " 隐藏右侧滚动条 283 | set guioptions-=b " 隐藏底部滚动条 284 | 285 | if has("gui_macvim") 286 | set imdisable " Set input method off 287 | set autochdir " 自动切换到文件当前目录 288 | endif 289 | else 290 | set ambiwidth=single 291 | endif 292 | 293 | " 字体设置 294 | if exists("&guifont") 295 | if g:osName == "linux" 296 | set guifont=Droid\ Sans\ Mono\ for\ Powerline\ 12 297 | elseif g:osName == "mac" 298 | set guifont=Source_Code_Pro_for_Powerline:h14 299 | end 300 | endif 301 | 302 | "--> VIM-PLUG插件管理 303 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 304 | call plug#begin('~/.vim/plugged') 305 | let g:plug_window='topleft' 306 | 307 | " 状态栏 308 | Plug 'vim-airline/vim-airline' 309 | Plug 'vim-airline/vim-airline-themes' 310 | " 文件目录树 311 | Plug 'vim-scripts/The-NERD-tree' 312 | " HTML/CSS代码快速生成 313 | Plug 'mattn/emmet-vim' 314 | " 搜索文件 315 | Plug 'kien/ctrlp.vim' 316 | " 新建文件自动加载模板 317 | Plug 'aperezdc/vim-template' 318 | " 文本更衣 319 | Plug 'tpope/vim-surround' 320 | " 括号自动补全 321 | Plug 'vim-scripts/Auto-Pairs' 322 | " 语法检查 323 | Plug 'scrooloose/syntastic' 324 | " 代码补全 325 | Plug 'Valloric/YouCompleteMe' 326 | " 模板生成补全 327 | Plug 'SirVer/ultisnips' 328 | " snippets 329 | Plug 'honza/vim-snippets' 330 | " 平滑滚动 331 | Plug 'joeytwiddle/sexy_scroller.vim' 332 | " 缩进对齐线 333 | Plug 'Yggdroot/indentLine' 334 | " 文本智能对齐 335 | Plug 'junegunn/vim-easy-align' 336 | " 代码注释 337 | Plug 'tomtom/tcomment_vim' 338 | " 华丽的代码截图Web版 339 | Plug 'mattr555/vim-instacode' 340 | " 神级跳转 341 | Plug 'easymotion/vim-easymotion' 342 | " 优雅的光标 343 | Plug 'terryma/vim-multiple-cursors' 344 | " 优雅的格式化 345 | Plug 'maksimr/vim-jsbeautify' 346 | " 优雅的Git管理 347 | Plug 'tpope/vim-fugitive' 348 | 349 | call plug#end() 350 | " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 351 | -------------------------------------------------------------------------------- /autoload/plug.vim.old: -------------------------------------------------------------------------------- 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 | " Plug 'junegunn/seoul256.vim' 15 | " Plug 'junegunn/vim-easy-align' 16 | " 17 | " " Group dependencies, vim-snippets depends on ultisnips 18 | " Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets' 19 | " 20 | " " On-demand loading 21 | " Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' } 22 | " Plug 'tpope/vim-fireplace', { 'for': 'clojure' } 23 | " 24 | " " Using git URL 25 | " Plug 'https://github.com/junegunn/vim-github-dashboard.git' 26 | " 27 | " " Using a non-master branch 28 | " Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' } 29 | 30 | " " Plugin options 31 | " Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' } 32 | " 33 | " " Plugin outside ~/.vim/plugged with post-update hook 34 | " Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } 35 | " 36 | " " Unmanaged plugin (manually installed and updated) 37 | " Plug '~/my-prototype-plugin' 38 | " 39 | " " Add plugins to &runtimepath 40 | " call plug#end() 41 | " 42 | " Then reload .vimrc and :PlugInstall to install plugins. 43 | " Visit https://github.com/junegunn/vim-plug for more information. 44 | " 45 | " 46 | " Copyright (c) 2015 Junegunn Choi 47 | " 48 | " MIT License 49 | " 50 | " Permission is hereby granted, free of charge, to any person obtaining 51 | " a copy of this software and associated documentation files (the 52 | " "Software"), to deal in the Software without restriction, including 53 | " without limitation the rights to use, copy, modify, merge, publish, 54 | " distribute, sublicense, and/or sell copies of the Software, and to 55 | " permit persons to whom the Software is furnished to do so, subject to 56 | " the following conditions: 57 | " 58 | " The above copyright notice and this permission notice shall be 59 | " included in all copies or substantial portions of the Software. 60 | " 61 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 62 | " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 63 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 64 | " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 65 | " LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 66 | " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 67 | " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 68 | 69 | if exists('g:loaded_plug') 70 | finish 71 | endif 72 | let g:loaded_plug = 1 73 | 74 | let s:cpo_save = &cpo 75 | set cpo&vim 76 | 77 | let s:plug_src = 'https://github.com/junegunn/vim-plug.git' 78 | let s:plug_tab = get(s:, 'plug_tab', -1) 79 | let s:plug_buf = get(s:, 'plug_buf', -1) 80 | let s:mac_gui = has('gui_macvim') && has('gui_running') 81 | let s:is_win = has('win32') || has('win64') 82 | let s:nvim = has('nvim') && exists('*jobwait') && !s:is_win 83 | let s:me = resolve(expand(':p')) 84 | let s:base_spec = { 'branch': 'master', 'frozen': 0 } 85 | let s:TYPE = { 86 | \ 'string': type(''), 87 | \ 'list': type([]), 88 | \ 'dict': type({}), 89 | \ 'funcref': type(function('call')) 90 | \ } 91 | let s:loaded = get(s:, 'loaded', {}) 92 | let s:triggers = get(s:, 'triggers', {}) 93 | 94 | function! plug#begin(...) 95 | if a:0 > 0 96 | let s:plug_home_org = a:1 97 | let home = s:path(fnamemodify(expand(a:1), ':p')) 98 | elseif exists('g:plug_home') 99 | let home = s:path(g:plug_home) 100 | elseif !empty(&rtp) 101 | let home = s:path(split(&rtp, ',')[0]) . '/plugged' 102 | else 103 | return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.') 104 | endif 105 | 106 | let g:plug_home = home 107 | let g:plugs = {} 108 | let g:plugs_order = [] 109 | let s:triggers = {} 110 | 111 | call s:define_commands() 112 | return 1 113 | endfunction 114 | 115 | function! s:define_commands() 116 | command! -nargs=+ -bar Plug call s:add() 117 | if !executable('git') 118 | return s:err('`git` executable not found. vim-plug requires git.') 119 | endif 120 | command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install('' == '!', []) 121 | command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update('' == '!', []) 122 | command! -nargs=0 -bar -bang PlugClean call s:clean('' == '!') 123 | command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif 124 | command! -nargs=0 -bar PlugStatus call s:status() 125 | command! -nargs=0 -bar PlugDiff call s:diff() 126 | command! -nargs=? -bar PlugSnapshot call s:snapshot() 127 | endfunction 128 | 129 | function! s:to_a(v) 130 | return type(a:v) == s:TYPE.list ? a:v : [a:v] 131 | endfunction 132 | 133 | function! s:to_s(v) 134 | return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" 135 | endfunction 136 | 137 | function! s:source(from, ...) 138 | for pattern in a:000 139 | for vim in s:lines(globpath(a:from, pattern)) 140 | execute 'source' s:esc(vim) 141 | endfor 142 | endfor 143 | endfunction 144 | 145 | function! s:assoc(dict, key, val) 146 | let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) 147 | endfunction 148 | 149 | function! plug#end() 150 | if !exists('g:plugs') 151 | return s:err('Call plug#begin() first') 152 | endif 153 | 154 | if exists('#PlugLOD') 155 | augroup PlugLOD 156 | autocmd! 157 | augroup END 158 | augroup! PlugLOD 159 | endif 160 | let lod = { 'ft': {}, 'map': {}, 'cmd': {} } 161 | 162 | filetype off 163 | for name in g:plugs_order 164 | let plug = g:plugs[name] 165 | if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for') 166 | let s:loaded[name] = 1 167 | continue 168 | endif 169 | 170 | if has_key(plug, 'on') 171 | let s:triggers[name] = { 'map': [], 'cmd': [] } 172 | for cmd in s:to_a(plug.on) 173 | if cmd =~? '^.\+' 174 | if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) 175 | call s:assoc(lod.map, cmd, name) 176 | endif 177 | call add(s:triggers[name].map, cmd) 178 | elseif cmd =~ '^[A-Z]' 179 | if exists(':'.cmd) != 2 180 | call s:assoc(lod.cmd, cmd, name) 181 | endif 182 | call add(s:triggers[name].cmd, cmd) 183 | endif 184 | endfor 185 | endif 186 | 187 | if has_key(plug, 'for') 188 | let types = s:to_a(plug.for) 189 | if !empty(types) 190 | call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') 191 | endif 192 | for type in types 193 | call s:assoc(lod.ft, type, name) 194 | endfor 195 | endif 196 | endfor 197 | 198 | for [cmd, names] in items(lod.cmd) 199 | execute printf( 200 | \ 'command! -nargs=* -range -bang %s call s:lod_cmd(%s, "", , , , %s)', 201 | \ cmd, string(cmd), string(names)) 202 | endfor 203 | 204 | for [map, names] in items(lod.map) 205 | for [mode, map_prefix, key_prefix] in 206 | \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] 207 | execute printf( 208 | \ '%snoremap %s %s:call lod_map(%s, %s, "%s")', 209 | \ mode, map, map_prefix, string(map), string(names), key_prefix) 210 | endfor 211 | endfor 212 | 213 | for [ft, names] in items(lod.ft) 214 | augroup PlugLOD 215 | execute printf('autocmd FileType %s call lod_ft(%s, %s)', 216 | \ ft, string(ft), string(names)) 217 | augroup END 218 | endfor 219 | 220 | call s:reorg_rtp() 221 | filetype plugin indent on 222 | if has('vim_starting') 223 | syntax enable 224 | else 225 | call s:reload() 226 | endif 227 | endfunction 228 | 229 | function! s:loaded_names() 230 | return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') 231 | endfunction 232 | 233 | function! s:reload() 234 | for name in s:loaded_names() 235 | call s:source(s:rtp(g:plugs[name]), 'plugin/**/*.vim', 'after/plugin/**/*.vim') 236 | endfor 237 | endfunction 238 | 239 | function! s:trim(str) 240 | return substitute(a:str, '[\/]\+$', '', '') 241 | endfunction 242 | 243 | function! s:version_requirement(val, min) 244 | for idx in range(0, len(a:min) - 1) 245 | let v = get(a:val, idx, 0) 246 | if v < a:min[idx] | return 0 247 | elseif v > a:min[idx] | return 1 248 | endif 249 | endfor 250 | return 1 251 | endfunction 252 | 253 | function! s:git_version_requirement(...) 254 | let s:git_version = get(s:, 'git_version', 255 | \ map(split(split(s:system('git --version'))[-1], '\.'), 'str2nr(v:val)')) 256 | return s:version_requirement(s:git_version, a:000) 257 | endfunction 258 | 259 | function! s:progress_opt(base) 260 | return a:base && !s:is_win && 261 | \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' 262 | endfunction 263 | 264 | if s:is_win 265 | function! s:rtp(spec) 266 | return s:path(a:spec.dir . get(a:spec, 'rtp', '')) 267 | endfunction 268 | 269 | function! s:path(path) 270 | return s:trim(substitute(a:path, '/', '\', 'g')) 271 | endfunction 272 | 273 | function! s:dirpath(path) 274 | return s:path(a:path) . '\' 275 | endfunction 276 | 277 | function! s:is_local_plug(repo) 278 | return a:repo =~? '^[a-z]:\|^[%~]' 279 | endfunction 280 | else 281 | function! s:rtp(spec) 282 | return s:dirpath(a:spec.dir . get(a:spec, 'rtp', '')) 283 | endfunction 284 | 285 | function! s:path(path) 286 | return s:trim(a:path) 287 | endfunction 288 | 289 | function! s:dirpath(path) 290 | return substitute(a:path, '[/\\]*$', '/', '') 291 | endfunction 292 | 293 | function! s:is_local_plug(repo) 294 | return a:repo[0] =~ '[/$~]' 295 | endfunction 296 | endif 297 | 298 | function! s:err(msg) 299 | echohl ErrorMsg 300 | echom a:msg 301 | echohl None 302 | return 0 303 | endfunction 304 | 305 | function! s:esc(path) 306 | return escape(a:path, ' ') 307 | endfunction 308 | 309 | function! s:escrtp(path) 310 | return escape(a:path, ' ,') 311 | endfunction 312 | 313 | function! s:remove_rtp() 314 | for name in s:loaded_names() 315 | let rtp = s:rtp(g:plugs[name]) 316 | execute 'set rtp-='.s:escrtp(rtp) 317 | let after = globpath(rtp, 'after') 318 | if isdirectory(after) 319 | execute 'set rtp-='.s:escrtp(after) 320 | endif 321 | endfor 322 | endfunction 323 | 324 | function! s:reorg_rtp() 325 | if !empty(s:first_rtp) 326 | execute 'set rtp-='.s:first_rtp 327 | execute 'set rtp-='.s:last_rtp 328 | endif 329 | 330 | " &rtp is modified from outside 331 | if exists('s:prtp') && s:prtp !=# &rtp 332 | call s:remove_rtp() 333 | unlet! s:middle 334 | endif 335 | 336 | let s:middle = get(s:, 'middle', &rtp) 337 | let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') 338 | let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), 'isdirectory(v:val)') 339 | let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') 340 | \ . ','.s:middle.',' 341 | \ . join(map(afters, 'escape(v:val, ",")'), ',') 342 | let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') 343 | let s:prtp = &rtp 344 | 345 | if !empty(s:first_rtp) 346 | execute 'set rtp^='.s:first_rtp 347 | execute 'set rtp+='.s:last_rtp 348 | endif 349 | endfunction 350 | 351 | function! plug#load(...) 352 | if a:0 == 0 353 | return s:err('Argument missing: plugin name(s) required') 354 | endif 355 | if !exists('g:plugs') 356 | return s:err('plug#begin was not called') 357 | endif 358 | let unknowns = filter(copy(a:000), '!has_key(g:plugs, v:val)') 359 | if !empty(unknowns) 360 | let s = len(unknowns) > 1 ? 's' : '' 361 | return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) 362 | end 363 | for name in a:000 364 | call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 365 | endfor 366 | if exists('#BufRead') 367 | doautocmd BufRead 368 | endif 369 | return 1 370 | endfunction 371 | 372 | function! s:remove_triggers(name) 373 | if !has_key(s:triggers, a:name) 374 | return 375 | endif 376 | for cmd in s:triggers[a:name].cmd 377 | execute 'silent! delc' cmd 378 | endfor 379 | for map in s:triggers[a:name].map 380 | execute 'silent! unmap' map 381 | execute 'silent! iunmap' map 382 | endfor 383 | call remove(s:triggers, a:name) 384 | endfunction 385 | 386 | function! s:lod(names, types) 387 | for name in a:names 388 | call s:remove_triggers(name) 389 | let s:loaded[name] = 1 390 | endfor 391 | call s:reorg_rtp() 392 | 393 | for name in a:names 394 | let rtp = s:rtp(g:plugs[name]) 395 | for dir in a:types 396 | call s:source(rtp, dir.'/**/*.vim') 397 | endfor 398 | if exists('#User#'.name) 399 | execute 'doautocmd User' name 400 | endif 401 | endfor 402 | endfunction 403 | 404 | function! s:lod_ft(pat, names) 405 | call s:lod(a:names, ['plugin', 'after/plugin', 'syntax', 'after/syntax']) 406 | execute 'autocmd! PlugLOD FileType' a:pat 407 | if exists('#filetypeplugin#FileType') 408 | doautocmd filetypeplugin FileType 409 | endif 410 | if exists('#filetypeindent#FileType') 411 | doautocmd filetypeindent FileType 412 | endif 413 | endfunction 414 | 415 | function! s:lod_cmd(cmd, bang, l1, l2, args, names) 416 | call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 417 | execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) 418 | endfunction 419 | 420 | function! s:lod_map(map, names, prefix) 421 | call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 422 | let extra = '' 423 | while 1 424 | let c = getchar(0) 425 | if c == 0 426 | break 427 | endif 428 | let extra .= nr2char(c) 429 | endwhile 430 | call feedkeys(a:prefix . substitute(a:map, '^', "\", '') . extra) 431 | endfunction 432 | 433 | function! s:add(repo, ...) 434 | if a:0 > 1 435 | return s:err('Invalid number of arguments (1..2)') 436 | endif 437 | 438 | try 439 | let repo = s:trim(a:repo) 440 | let name = fnamemodify(repo, ':t:s?\.git$??') 441 | let spec = extend(s:infer_properties(name, repo), 442 | \ a:0 == 1 ? s:parse_options(a:1) : s:base_spec) 443 | if !has_key(g:plugs, name) 444 | call add(g:plugs_order, name) 445 | endif 446 | let g:plugs[name] = spec 447 | let s:loaded[name] = get(s:loaded, name, 0) 448 | catch 449 | return s:err(v:exception) 450 | endtry 451 | endfunction 452 | 453 | function! s:parse_options(arg) 454 | let opts = copy(s:base_spec) 455 | let type = type(a:arg) 456 | if type == s:TYPE.string 457 | let opts.tag = a:arg 458 | elseif type == s:TYPE.dict 459 | call extend(opts, a:arg) 460 | if has_key(opts, 'dir') 461 | let opts.dir = s:dirpath(expand(opts.dir)) 462 | endif 463 | else 464 | throw 'Invalid argument type (expected: string or dictionary)' 465 | endif 466 | return opts 467 | endfunction 468 | 469 | function! s:infer_properties(name, repo) 470 | let repo = a:repo 471 | if s:is_local_plug(repo) 472 | return { 'dir': s:dirpath(expand(repo)) } 473 | else 474 | if repo =~ ':' 475 | let uri = repo 476 | else 477 | if repo !~ '/' 478 | let repo = 'vim-scripts/'. repo 479 | endif 480 | let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') 481 | let uri = printf(fmt, repo) 482 | endif 483 | let dir = s:dirpath( fnamemodify(join([g:plug_home, a:name], '/'), ':p') ) 484 | return { 'dir': dir, 'uri': uri } 485 | endif 486 | endfunction 487 | 488 | function! s:install(force, names) 489 | call s:update_impl(0, a:force, a:names) 490 | endfunction 491 | 492 | function! s:update(force, names) 493 | call s:update_impl(1, a:force, a:names) 494 | endfunction 495 | 496 | function! plug#helptags() 497 | if !exists('g:plugs') 498 | return s:err('plug#begin was not called') 499 | endif 500 | for spec in values(g:plugs) 501 | let docd = join([spec.dir, 'doc'], '/') 502 | if isdirectory(docd) 503 | silent! execute 'helptags' s:esc(docd) 504 | endif 505 | endfor 506 | return 1 507 | endfunction 508 | 509 | function! s:syntax() 510 | syntax clear 511 | syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber 512 | syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX 513 | syn match plugNumber /[0-9]\+[0-9.]*/ contained 514 | syn match plugBracket /[[\]]/ contained 515 | syn match plugX /x/ contained 516 | syn match plugDash /^-/ 517 | syn match plugPlus /^+/ 518 | syn match plugStar /^*/ 519 | syn match plugMessage /\(^- \)\@<=.*/ 520 | syn match plugName /\(^- \)\@<=[^ ]*:/ 521 | syn match plugInstall /\(^+ \)\@<=[^:]*/ 522 | syn match plugUpdate /\(^* \)\@<=[^:]*/ 523 | syn match plugCommit /^ [0-9a-z]\{7} .*/ contains=plugRelDate,plugSha 524 | syn match plugSha /\(^ \)\@<=[0-9a-z]\{7}/ contained 525 | syn match plugRelDate /([^)]*)$/ contained 526 | syn match plugNotLoaded /(not loaded)$/ 527 | syn match plugError /^x.*/ 528 | syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean 529 | hi def link plug1 Title 530 | hi def link plug2 Repeat 531 | hi def link plugX Exception 532 | hi def link plugBracket Structure 533 | hi def link plugNumber Number 534 | 535 | hi def link plugDash Special 536 | hi def link plugPlus Constant 537 | hi def link plugStar Boolean 538 | 539 | hi def link plugMessage Function 540 | hi def link plugName Label 541 | hi def link plugInstall Function 542 | hi def link plugUpdate Type 543 | 544 | hi def link plugError Error 545 | hi def link plugRelDate Comment 546 | hi def link plugSha Identifier 547 | 548 | hi def link plugNotLoaded Comment 549 | endfunction 550 | 551 | function! s:lpad(str, len) 552 | return a:str . repeat(' ', a:len - len(a:str)) 553 | endfunction 554 | 555 | function! s:lines(msg) 556 | return split(a:msg, "[\r\n]") 557 | endfunction 558 | 559 | function! s:lastline(msg) 560 | return get(s:lines(a:msg), -1, '') 561 | endfunction 562 | 563 | function! s:new_window() 564 | execute get(g:, 'plug_window', 'vertical topleft new') 565 | endfunction 566 | 567 | function! s:plug_window_exists() 568 | let buflist = tabpagebuflist(s:plug_tab) 569 | return !empty(buflist) && index(buflist, s:plug_buf) >= 0 570 | endfunction 571 | 572 | function! s:switch_in() 573 | if !s:plug_window_exists() 574 | return 0 575 | endif 576 | 577 | if winbufnr(0) != s:plug_buf 578 | let s:pos = [tabpagenr(), winnr(), winsaveview()] 579 | execute 'normal!' s:plug_tab.'gt' 580 | let winnr = bufwinnr(s:plug_buf) 581 | execute winnr.'wincmd w' 582 | call add(s:pos, winsaveview()) 583 | else 584 | let s:pos = [winsaveview()] 585 | endif 586 | 587 | setlocal modifiable 588 | return 1 589 | endfunction 590 | 591 | function! s:switch_out(...) 592 | call winrestview(s:pos[-1]) 593 | setlocal nomodifiable 594 | if a:0 > 0 595 | execute a:1 596 | endif 597 | 598 | if len(s:pos) > 1 599 | execute 'normal!' s:pos[0].'gt' 600 | execute s:pos[1] 'wincmd w' 601 | call winrestview(s:pos[2]) 602 | endif 603 | endfunction 604 | 605 | function! s:prepare() 606 | call s:job_abort() 607 | if s:switch_in() 608 | silent %d _ 609 | else 610 | call s:new_window() 611 | nnoremap q :if b:plug_preview==1pcendifechoq 612 | nnoremap R :silent! call retry() 613 | nnoremap D :PlugDiff 614 | nnoremap S :PlugStatus 615 | nnoremap U :call status_update() 616 | xnoremap U :call status_update() 617 | nnoremap ]] :silent! call section('') 618 | nnoremap [[ :silent! call section('b') 619 | let b:plug_preview = -1 620 | let s:plug_tab = tabpagenr() 621 | let s:plug_buf = winbufnr(0) 622 | call s:assign_name() 623 | endif 624 | silent! unmap 625 | silent! unmap L 626 | silent! unmap o 627 | silent! unmap X 628 | setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline modifiable 629 | setf vim-plug 630 | call s:syntax() 631 | endfunction 632 | 633 | function! s:assign_name() 634 | " Assign buffer name 635 | let prefix = '[Plugins]' 636 | let name = prefix 637 | let idx = 2 638 | while bufexists(name) 639 | let name = printf('%s (%s)', prefix, idx) 640 | let idx = idx + 1 641 | endwhile 642 | silent! execute 'f' fnameescape(name) 643 | endfunction 644 | 645 | function! s:do(pull, force, todo) 646 | for [name, spec] in items(a:todo) 647 | if !isdirectory(spec.dir) 648 | continue 649 | endif 650 | let installed = has_key(s:update.new, name) 651 | let updated = installed ? 0 : 652 | \ (a:pull && index(s:update.errors, name) < 0 && !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', spec.dir))) 653 | if a:force || installed || updated 654 | execute 'cd' s:esc(spec.dir) 655 | call append(3, '- Post-update hook for '. name .' ... ') 656 | let type = type(spec.do) 657 | if type == s:TYPE.string 658 | try 659 | " FIXME: Escaping is incomplete. We could use shellescape with eval, 660 | " but it won't work on Windows. 661 | let g:_plug_do = '!'.escape(spec.do, '#!%') 662 | execute "normal! :execute g:_plug_do\\" 663 | finally 664 | let result = v:shell_error ? ('Exit status: '.v:shell_error) : 'Done!' 665 | unlet g:_plug_do 666 | endtry 667 | elseif type == s:TYPE.funcref 668 | try 669 | let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') 670 | call spec.do({ 'name': name, 'status': status, 'force': a:force }) 671 | let result = 'Done!' 672 | catch 673 | let result = 'Error: ' . v:exception 674 | endtry 675 | else 676 | let result = 'Error: Invalid type!' 677 | endif 678 | call setline(4, getline(4) . result) 679 | cd - 680 | endif 681 | endfor 682 | endfunction 683 | 684 | function! s:finish(pull) 685 | let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) 686 | if new_frozen 687 | let s = new_frozen > 1 ? 's' : '' 688 | call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) 689 | endif 690 | call append(3, '- Finishing ... ') 691 | redraw 692 | call plug#helptags() 693 | call plug#end() 694 | call setline(4, getline(4) . 'Done!') 695 | redraw 696 | let msgs = [] 697 | if !empty(s:update.errors) 698 | call add(msgs, "Press 'R' to retry.") 699 | endif 700 | if a:pull && len(s:update.new) < len(filter(getline(5, '$'), 701 | \ "v:val =~ '^- ' && stridx(v:val, 'Already up-to-date') < 0")) 702 | call add(msgs, "Press 'D' to see the updated changes.") 703 | endif 704 | echo join(msgs, ' ') 705 | endfunction 706 | 707 | function! s:retry() 708 | if empty(s:update.errors) 709 | return 710 | endif 711 | call s:update_impl(s:update.pull, s:update.force, 712 | \ extend(copy(s:update.errors), [s:update.threads])) 713 | endfunction 714 | 715 | function! s:is_managed(name) 716 | return has_key(g:plugs[a:name], 'uri') 717 | endfunction 718 | 719 | function! s:names(...) 720 | return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) 721 | endfunction 722 | 723 | function! s:update_impl(pull, force, args) abort 724 | let args = copy(a:args) 725 | let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? 726 | \ remove(args, -1) : get(g:, 'plug_threads', s:is_win ? 1 : 16) 727 | 728 | let managed = filter(copy(g:plugs), 's:is_managed(v:key)') 729 | let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : 730 | \ filter(managed, 'index(args, v:key) >= 0') 731 | 732 | if empty(todo) 733 | echohl WarningMsg 734 | echo 'No plugin to '. (a:pull ? 'update' : 'install') . '.' 735 | echohl None 736 | return 737 | endif 738 | 739 | if !s:is_win && s:git_version_requirement(2, 3) 740 | let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' 741 | let $GIT_TERMINAL_PROMPT = 0 742 | for plug in values(todo) 743 | let plug.uri = substitute(plug.uri, 744 | \ '^https://git::@github\.com', 'https://github.com', '') 745 | endfor 746 | endif 747 | 748 | if !isdirectory(g:plug_home) 749 | try 750 | call mkdir(g:plug_home, 'p') 751 | catch 752 | return s:err(printf('Invalid plug directory: %s. '. 753 | \ 'Try to call plug#begin with a valid directory', g:plug_home)) 754 | endtry 755 | endif 756 | 757 | if has('nvim') && !exists('*jobwait') && threads > 1 758 | echohl WarningMsg 759 | echomsg 'vim-plug: update Neovim for parallel installer' 760 | echohl None 761 | endif 762 | 763 | let python = (has('python') || has('python3')) && !s:is_win && !has('win32unix') 764 | \ && (!s:nvim || has('vim_starting')) 765 | let ruby = has('ruby') && !s:nvim && (v:version >= 703 || v:version == 702 && has('patch374')) 766 | 767 | let s:update = { 768 | \ 'start': reltime(), 769 | \ 'all': todo, 770 | \ 'todo': copy(todo), 771 | \ 'errors': [], 772 | \ 'pull': a:pull, 773 | \ 'force': a:force, 774 | \ 'new': {}, 775 | \ 'threads': (python || ruby || s:nvim) ? min([len(todo), threads]) : 1, 776 | \ 'bar': '', 777 | \ 'fin': 0 778 | \ } 779 | 780 | call s:prepare() 781 | call append(0, ['', '']) 782 | normal! 2G 783 | silent! redraw 784 | 785 | let s:clone_opt = get(g:, 'plug_shallow', 1) ? 786 | \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : '' 787 | 788 | " Python version requirement (>= 2.7) 789 | if python && !has('python3') && !ruby && !s:nvim && s:update.threads > 1 790 | redir => pyv 791 | silent python import platform; print(platform.python_version()) 792 | redir END 793 | let python = s:version_requirement( 794 | \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) 795 | endif 796 | 797 | if (python || ruby) && s:update.threads > 1 798 | try 799 | let imd = &imd 800 | if s:mac_gui 801 | set noimd 802 | endif 803 | if ruby 804 | call s:update_ruby() 805 | else 806 | call s:update_python() 807 | endif 808 | catch 809 | let lines = getline(4, '$') 810 | let printed = {} 811 | silent! 4,$d _ 812 | for line in lines 813 | let name = s:extract_name(line, '.', '') 814 | if empty(name) || !has_key(printed, name) 815 | call append('$', line) 816 | if !empty(name) 817 | let printed[name] = 1 818 | if line[0] == 'x' && index(s:update.errors, name) < 0 819 | call add(s:update.errors, name) 820 | end 821 | endif 822 | endif 823 | endfor 824 | finally 825 | let &imd = imd 826 | call s:update_finish() 827 | endtry 828 | else 829 | call s:update_vim() 830 | endif 831 | endfunction 832 | 833 | function! s:update_finish() 834 | if exists('s:git_terminal_prompt') 835 | let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt 836 | endif 837 | if s:switch_in() 838 | call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'has_key(v:val, "do")')) 839 | call s:finish(s:update.pull) 840 | call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') 841 | call s:switch_out('normal! gg') 842 | endif 843 | endfunction 844 | 845 | function! s:job_abort() 846 | if !s:nvim || !exists('s:jobs') 847 | return 848 | endif 849 | 850 | for [name, j] in items(s:jobs) 851 | silent! call jobstop(j.jobid) 852 | if j.new 853 | call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir)) 854 | endif 855 | endfor 856 | let s:jobs = {} 857 | endfunction 858 | 859 | " When a:event == 'stdout', data = list of strings 860 | " When a:event == 'exit', data = returncode 861 | function! s:job_handler(job_id, data, event) abort 862 | if !s:plug_window_exists() " plug window closed 863 | return s:job_abort() 864 | endif 865 | 866 | if a:event == 'stdout' 867 | let self.result .= substitute(s:to_s(a:data), '[\r\n]', '', 'g') . "\n" 868 | " To reduce the number of buffer updates 869 | let self.tick = get(self, 'tick', -1) + 1 870 | if self.tick % len(s:jobs) == 0 871 | call s:log(self.new ? '+' : '*', self.name, self.result) 872 | endif 873 | elseif a:event == 'exit' 874 | let self.running = 0 875 | if a:data != 0 876 | let self.error = 1 877 | endif 878 | call s:reap(self.name) 879 | call s:tick() 880 | endif 881 | endfunction 882 | 883 | function! s:spawn(name, cmd, opts) 884 | let job = { 'name': a:name, 'running': 1, 'error': 0, 'result': '', 885 | \ 'new': get(a:opts, 'new', 0), 886 | \ 'on_stdout': function('s:job_handler'), 887 | \ 'on_exit' : function('s:job_handler'), 888 | \ } 889 | let s:jobs[a:name] = job 890 | 891 | if s:nvim 892 | let argv = [ 'sh', '-c', 893 | \ (has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd) ] 894 | let jid = jobstart(argv, job) 895 | if jid > 0 896 | let job.jobid = jid 897 | else 898 | let job.running = 0 899 | let job.error = 1 900 | let job.result = jid < 0 ? 'sh is not executable' : 901 | \ 'Invalid arguments (or job table is full)' 902 | endif 903 | else 904 | let params = has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd] 905 | let job.result = call('s:system', params) 906 | let job.error = v:shell_error != 0 907 | let job.running = 0 908 | endif 909 | endfunction 910 | 911 | function! s:reap(name) 912 | let job = s:jobs[a:name] 913 | if job.error 914 | call add(s:update.errors, a:name) 915 | elseif get(job, 'new', 0) 916 | let s:update.new[a:name] = 1 917 | endif 918 | let s:update.bar .= job.error ? 'x' : '=' 919 | 920 | call s:log(job.error ? 'x' : '-', a:name, job.result) 921 | call s:bar() 922 | 923 | call remove(s:jobs, a:name) 924 | endfunction 925 | 926 | function! s:bar() 927 | if s:switch_in() 928 | let total = len(s:update.all) 929 | call setline(1, (s:update.pull ? 'Updating' : 'Installing'). 930 | \ ' plugins ('.len(s:update.bar).'/'.total.')') 931 | call s:progress_bar(2, s:update.bar, total) 932 | call s:switch_out() 933 | endif 934 | endfunction 935 | 936 | function! s:logpos(name) 937 | for i in range(1, line('$')) 938 | if getline(i) =~# '^[-+x*] '.a:name.':' 939 | return i 940 | endif 941 | endfor 942 | return 0 943 | endfunction 944 | 945 | function! s:log(bullet, name, lines) 946 | if s:switch_in() 947 | let pos = s:logpos(a:name) 948 | if pos > 0 949 | execute pos 'd _' 950 | if pos > winheight('.') 951 | let pos = 4 952 | endif 953 | else 954 | let pos = 4 955 | endif 956 | call append(pos - 1, s:format_message(a:bullet, a:name, a:lines)) 957 | call s:switch_out() 958 | endif 959 | endfunction 960 | 961 | function! s:update_vim() 962 | let s:jobs = {} 963 | 964 | call s:bar() 965 | call s:tick() 966 | endfunction 967 | 968 | function! s:tick() 969 | let pull = s:update.pull 970 | let prog = s:progress_opt(s:nvim) 971 | while 1 " Without TCO, Vim stack is bound to explode 972 | if empty(s:update.todo) 973 | if empty(s:jobs) && !s:update.fin 974 | let s:update.fin = 1 975 | call s:update_finish() 976 | endif 977 | return 978 | endif 979 | 980 | let name = keys(s:update.todo)[0] 981 | let spec = remove(s:update.todo, name) 982 | let new = !isdirectory(spec.dir) 983 | 984 | call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') 985 | redraw 986 | 987 | let has_tag = has_key(spec, 'tag') 988 | let checkout = s:shellesc(has_tag ? spec.tag : spec.branch) 989 | let merge = s:shellesc(has_tag ? spec.tag : 'origin/'.spec.branch) 990 | 991 | if !new 992 | let [valid, msg] = s:git_valid(spec, 0) 993 | if valid 994 | if pull 995 | let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : '' 996 | call s:spawn(name, 997 | \ printf('(git fetch %s %s 2>&1 && git checkout -q %s 2>&1 && git merge --ff-only %s 2>&1 && git submodule update --init --recursive 2>&1)', 998 | \ fetch_opt, prog, checkout, merge), { 'dir': spec.dir }) 999 | else 1000 | let s:jobs[name] = { 'running': 0, 'result': 'Already installed', 'error': 0 } 1001 | endif 1002 | else 1003 | let s:jobs[name] = { 'running': 0, 'result': msg, 'error': 1 } 1004 | endif 1005 | else 1006 | call s:spawn(name, 1007 | \ printf('git clone %s %s --recursive %s -b %s %s 2>&1', 1008 | \ has_tag ? '' : s:clone_opt, 1009 | \ prog, 1010 | \ s:shellesc(spec.uri), 1011 | \ checkout, 1012 | \ s:shellesc(s:trim(spec.dir))), { 'new': 1 }) 1013 | endif 1014 | 1015 | if !s:jobs[name].running 1016 | call s:reap(name) 1017 | endif 1018 | if len(s:jobs) >= s:update.threads 1019 | break 1020 | endif 1021 | endwhile 1022 | endfunction 1023 | 1024 | function! s:update_python() 1025 | let py_exe = has('python3') ? 'python3' : 'python' 1026 | execute py_exe "<< EOF" 1027 | """ Due to use of signals this function is POSIX only. """ 1028 | import datetime 1029 | import functools 1030 | import os 1031 | try: 1032 | import queue 1033 | except ImportError: 1034 | import Queue as queue 1035 | import random 1036 | import re 1037 | import shutil 1038 | import signal 1039 | import subprocess 1040 | import tempfile 1041 | import threading as thr 1042 | import time 1043 | import traceback 1044 | import vim 1045 | 1046 | G_NVIM = vim.eval("has('nvim')") == '1' 1047 | G_PULL = vim.eval('s:update.pull') == '1' 1048 | G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 1049 | G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) 1050 | G_CLONE_OPT = vim.eval('s:clone_opt') 1051 | G_PROGRESS = vim.eval('s:progress_opt(1)') 1052 | G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) 1053 | G_STOP = thr.Event() 1054 | G_THREADS = {} 1055 | 1056 | class PlugError(Exception): 1057 | def __init__(self, msg): 1058 | self._msg = msg 1059 | @property 1060 | def msg(self): 1061 | return self._msg 1062 | class CmdTimedOut(PlugError): 1063 | pass 1064 | class CmdFailed(PlugError): 1065 | pass 1066 | class InvalidURI(PlugError): 1067 | pass 1068 | class Action(object): 1069 | INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] 1070 | 1071 | class Buffer(object): 1072 | def __init__(self, lock, num_plugs, is_pull, is_win): 1073 | self.bar = '' 1074 | self.event = 'Updating' if is_pull else 'Installing' 1075 | self.is_win = is_win 1076 | self.lock = lock 1077 | self.maxy = int(vim.eval('winheight(".")')) 1078 | self.num_plugs = num_plugs 1079 | 1080 | def __where(self, name): 1081 | """ Find first line with name in current buffer. Return line num. """ 1082 | found, lnum = False, 0 1083 | matcher = re.compile('^[-+x*] {0}:'.format(name)) 1084 | for line in vim.current.buffer: 1085 | if matcher.search(line) is not None: 1086 | found = True 1087 | break 1088 | lnum += 1 1089 | 1090 | if not found: 1091 | lnum = -1 1092 | return lnum 1093 | 1094 | def header(self): 1095 | curbuf = vim.current.buffer 1096 | curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) 1097 | 1098 | num_spaces = self.num_plugs - len(self.bar) 1099 | curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') 1100 | 1101 | with self.lock: 1102 | vim.command('normal! 2G') 1103 | if not self.is_win: 1104 | vim.command('redraw') 1105 | 1106 | def write(self, action, name, lines): 1107 | first, rest = lines[0], lines[1:] 1108 | msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] 1109 | msg.extend([' ' + line for line in rest]) 1110 | 1111 | try: 1112 | if action == Action.ERROR: 1113 | self.bar += 'x' 1114 | vim.command("call add(s:update.errors, '{0}')".format(name)) 1115 | elif action == Action.DONE: 1116 | self.bar += '=' 1117 | 1118 | curbuf = vim.current.buffer 1119 | lnum = self.__where(name) 1120 | if lnum != -1: # Found matching line num 1121 | del curbuf[lnum] 1122 | if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): 1123 | lnum = 3 1124 | else: 1125 | lnum = 3 1126 | curbuf.append(msg, lnum) 1127 | 1128 | self.header() 1129 | except vim.error: 1130 | pass 1131 | 1132 | class Command(object): 1133 | def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): 1134 | self.cmd = cmd 1135 | self.cmd_dir = cmd_dir 1136 | self.timeout = timeout 1137 | self.callback = cb if cb else (lambda msg: None) 1138 | self.clean = clean if clean else (lambda: None) 1139 | self.proc = None 1140 | 1141 | @property 1142 | def alive(self): 1143 | """ Returns true only if command still running. """ 1144 | return self.proc and self.proc.poll() is None 1145 | 1146 | def execute(self, ntries=3): 1147 | """ Execute the command with ntries if CmdTimedOut. 1148 | Returns the output of the command if no Exception. 1149 | """ 1150 | attempt, finished, limit = 0, False, self.timeout 1151 | 1152 | while not finished: 1153 | try: 1154 | attempt += 1 1155 | result = self.try_command() 1156 | finished = True 1157 | return result 1158 | except CmdTimedOut: 1159 | if attempt != ntries: 1160 | self.notify_retry() 1161 | self.timeout += limit 1162 | else: 1163 | raise 1164 | 1165 | def notify_retry(self): 1166 | """ Retry required for command, notify user. """ 1167 | for count in range(3, 0, -1): 1168 | if G_STOP.is_set(): 1169 | raise KeyboardInterrupt 1170 | msg = 'Timeout. Will retry in {0} second{1} ...'.format( 1171 | count, 's' if count != 1 else '') 1172 | self.callback([msg]) 1173 | time.sleep(1) 1174 | self.callback(['Retrying ...']) 1175 | 1176 | def try_command(self): 1177 | """ Execute a cmd & poll for callback. Returns list of output. 1178 | Raises CmdFailed -> return code for Popen isn't 0 1179 | Raises CmdTimedOut -> command exceeded timeout without new output 1180 | """ 1181 | first_line = True 1182 | 1183 | try: 1184 | tfile = tempfile.NamedTemporaryFile(mode='w+b') 1185 | self.proc = subprocess.Popen(self.cmd, cwd=self.cmd_dir, stdout=tfile, 1186 | stderr=subprocess.STDOUT, shell=True, 1187 | preexec_fn=os.setsid) 1188 | thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) 1189 | thrd.start() 1190 | 1191 | thread_not_started = True 1192 | while thread_not_started: 1193 | try: 1194 | thrd.join(0.1) 1195 | thread_not_started = False 1196 | except RuntimeError: 1197 | pass 1198 | 1199 | while self.alive: 1200 | if G_STOP.is_set(): 1201 | raise KeyboardInterrupt 1202 | 1203 | if first_line or random.random() < G_LOG_PROB: 1204 | first_line = False 1205 | line = nonblock_read(tfile.name) 1206 | if line: 1207 | self.callback([line]) 1208 | 1209 | time_diff = time.time() - os.path.getmtime(tfile.name) 1210 | if time_diff > self.timeout: 1211 | raise CmdTimedOut(['Timeout!']) 1212 | 1213 | thrd.join(0.5) 1214 | 1215 | tfile.seek(0) 1216 | result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] 1217 | 1218 | if self.proc.returncode != 0: 1219 | raise CmdFailed([''] + result) 1220 | 1221 | return result 1222 | except: 1223 | self.terminate() 1224 | raise 1225 | 1226 | def terminate(self): 1227 | """ Terminate process and cleanup. """ 1228 | if self.alive: 1229 | os.killpg(self.proc.pid, signal.SIGTERM) 1230 | self.clean() 1231 | 1232 | class Plugin(object): 1233 | def __init__(self, name, args, buf_q, lock): 1234 | self.name = name 1235 | self.args = args 1236 | self.buf_q = buf_q 1237 | self.lock = lock 1238 | tag = args.get('tag', 0) 1239 | self.checkout = esc(tag if tag else args['branch']) 1240 | self.merge = esc(tag if tag else 'origin/' + args['branch']) 1241 | self.tag = tag 1242 | 1243 | def manage(self): 1244 | try: 1245 | if os.path.exists(self.args['dir']): 1246 | self.update() 1247 | else: 1248 | self.install() 1249 | with self.lock: 1250 | thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) 1251 | except PlugError as exc: 1252 | self.write(Action.ERROR, self.name, exc.msg) 1253 | except KeyboardInterrupt: 1254 | G_STOP.set() 1255 | self.write(Action.ERROR, self.name, ['Interrupted!']) 1256 | except: 1257 | # Any exception except those above print stack trace 1258 | msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) 1259 | self.write(Action.ERROR, self.name, msg.split('\n')) 1260 | raise 1261 | 1262 | def install(self): 1263 | target = self.args['dir'] 1264 | 1265 | def clean(target): 1266 | def _clean(): 1267 | try: 1268 | shutil.rmtree(target) 1269 | except OSError: 1270 | pass 1271 | return _clean 1272 | 1273 | self.write(Action.INSTALL, self.name, ['Installing ...']) 1274 | callback = functools.partial(self.write, Action.INSTALL, self.name) 1275 | cmd = 'git clone {0} {1} --recursive {2} -b {3} {4} 2>&1'.format( 1276 | '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], 1277 | self.checkout, esc(target)) 1278 | com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) 1279 | result = com.execute(G_RETRIES) 1280 | self.write(Action.DONE, self.name, result[-1:]) 1281 | 1282 | def repo_uri(self): 1283 | cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url' 1284 | command = Command(cmd, self.args['dir'], G_TIMEOUT,) 1285 | result = command.execute(G_RETRIES) 1286 | return result[-1] 1287 | 1288 | def update(self): 1289 | match = re.compile(r'git::?@') 1290 | actual_uri = re.sub(match, '', self.repo_uri()) 1291 | expect_uri = re.sub(match, '', self.args['uri']) 1292 | if actual_uri != expect_uri: 1293 | msg = ['', 1294 | 'Invalid URI: {0}'.format(actual_uri), 1295 | 'Expected {0}'.format(expect_uri), 1296 | 'PlugClean required.'] 1297 | raise InvalidURI(msg) 1298 | 1299 | if G_PULL: 1300 | self.write(Action.UPDATE, self.name, ['Updating ...']) 1301 | callback = functools.partial(self.write, Action.UPDATE, self.name) 1302 | fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' 1303 | cmds = ['git fetch {0} {1}'.format(fetch_opt, G_PROGRESS), 1304 | 'git checkout -q {0}'.format(self.checkout), 1305 | 'git merge --ff-only {0}'.format(self.merge), 1306 | 'git submodule update --init --recursive'] 1307 | cmd = ' 2>&1 && '.join(cmds) 1308 | com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) 1309 | result = com.execute(G_RETRIES) 1310 | self.write(Action.DONE, self.name, result[-1:]) 1311 | else: 1312 | self.write(Action.DONE, self.name, ['Already installed']) 1313 | 1314 | def write(self, action, name, msg): 1315 | self.buf_q.put((action, name, msg)) 1316 | 1317 | class PlugThread(thr.Thread): 1318 | def __init__(self, tname, args): 1319 | super(PlugThread, self).__init__() 1320 | self.tname = tname 1321 | self.args = args 1322 | 1323 | def run(self): 1324 | thr.current_thread().name = self.tname 1325 | buf_q, work_q, lock = self.args 1326 | 1327 | try: 1328 | while not G_STOP.is_set(): 1329 | name, args = work_q.get_nowait() 1330 | plug = Plugin(name, args, buf_q, lock) 1331 | plug.manage() 1332 | work_q.task_done() 1333 | except queue.Empty: 1334 | pass 1335 | finally: 1336 | global G_THREADS 1337 | with lock: 1338 | del G_THREADS[thr.current_thread().name] 1339 | 1340 | class RefreshThread(thr.Thread): 1341 | def __init__(self, lock): 1342 | super(RefreshThread, self).__init__() 1343 | self.lock = lock 1344 | self.running = True 1345 | 1346 | def run(self): 1347 | while self.running: 1348 | with self.lock: 1349 | thread_vim_command('noautocmd normal! a') 1350 | time.sleep(0.33) 1351 | 1352 | def stop(self): 1353 | self.running = False 1354 | 1355 | if G_NVIM: 1356 | def thread_vim_command(cmd): 1357 | vim.session.threadsafe_call(lambda: vim.command(cmd)) 1358 | else: 1359 | def thread_vim_command(cmd): 1360 | vim.command(cmd) 1361 | 1362 | def esc(name): 1363 | return '"' + name.replace('"', '\"') + '"' 1364 | 1365 | def nonblock_read(fname): 1366 | """ Read a file with nonblock flag. Return the last line. """ 1367 | fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) 1368 | buf = os.read(fread, 100000).decode('utf-8', 'replace') 1369 | os.close(fread) 1370 | 1371 | line = buf.rstrip('\r\n') 1372 | left = max(line.rfind('\r'), line.rfind('\n')) 1373 | if left != -1: 1374 | left += 1 1375 | line = line[left:] 1376 | 1377 | return line 1378 | 1379 | def main(): 1380 | thr.current_thread().name = 'main' 1381 | nthreads = int(vim.eval('s:update.threads')) 1382 | plugs = vim.eval('s:update.todo') 1383 | mac_gui = vim.eval('s:mac_gui') == '1' 1384 | is_win = vim.eval('s:is_win') == '1' 1385 | 1386 | lock = thr.Lock() 1387 | buf = Buffer(lock, len(plugs), G_PULL, is_win) 1388 | buf_q, work_q = queue.Queue(), queue.Queue() 1389 | for work in plugs.items(): 1390 | work_q.put(work) 1391 | 1392 | global G_THREADS 1393 | for num in range(nthreads): 1394 | tname = 'PlugT-{0:02}'.format(num) 1395 | thread = PlugThread(tname, (buf_q, work_q, lock)) 1396 | thread.start() 1397 | G_THREADS[tname] = thread 1398 | if mac_gui: 1399 | rthread = RefreshThread(lock) 1400 | rthread.start() 1401 | 1402 | while not buf_q.empty() or len(G_THREADS) != 0: 1403 | try: 1404 | action, name, msg = buf_q.get(True, 0.25) 1405 | buf.write(action, name, msg) 1406 | buf_q.task_done() 1407 | except queue.Empty: 1408 | pass 1409 | except KeyboardInterrupt: 1410 | G_STOP.set() 1411 | 1412 | if mac_gui: 1413 | rthread.stop() 1414 | rthread.join() 1415 | 1416 | main() 1417 | EOF 1418 | endfunction 1419 | 1420 | function! s:update_ruby() 1421 | ruby << EOF 1422 | module PlugStream 1423 | SEP = ["\r", "\n", nil] 1424 | def get_line 1425 | buffer = '' 1426 | loop do 1427 | char = readchar rescue return 1428 | if SEP.include? char.chr 1429 | buffer << $/ 1430 | break 1431 | else 1432 | buffer << char 1433 | end 1434 | end 1435 | buffer 1436 | end 1437 | end unless defined?(PlugStream) 1438 | 1439 | def esc arg 1440 | %["#{arg.gsub('"', '\"')}"] 1441 | end 1442 | 1443 | def killall pid 1444 | pids = [pid] 1445 | unless `which pgrep 2> /dev/null`.empty? 1446 | children = pids 1447 | until children.empty? 1448 | children = children.map { |pid| 1449 | `pgrep -P #{pid}`.lines.map { |l| l.chomp } 1450 | }.flatten 1451 | pids += children 1452 | end 1453 | end 1454 | pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } 1455 | end 1456 | 1457 | require 'thread' 1458 | require 'fileutils' 1459 | require 'timeout' 1460 | running = true 1461 | iswin = VIM::evaluate('s:is_win').to_i == 1 1462 | pull = VIM::evaluate('s:update.pull').to_i == 1 1463 | base = VIM::evaluate('g:plug_home') 1464 | all = VIM::evaluate('s:update.todo') 1465 | limit = VIM::evaluate('get(g:, "plug_timeout", 60)') 1466 | tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 1467 | nthr = VIM::evaluate('s:update.threads').to_i 1468 | maxy = VIM::evaluate('winheight(".")').to_i 1469 | cd = iswin ? 'cd /d' : 'cd' 1470 | tot = VIM::evaluate('len(s:update.todo)') || 0 1471 | bar = '' 1472 | skip = 'Already installed' 1473 | mtx = Mutex.new 1474 | take1 = proc { mtx.synchronize { running && all.shift } } 1475 | logh = proc { 1476 | cnt = bar.length 1477 | $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" 1478 | $curbuf[2] = '[' + bar.ljust(tot) + ']' 1479 | VIM::command('normal! 2G') 1480 | VIM::command('redraw') unless iswin 1481 | } 1482 | where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } 1483 | log = proc { |name, result, type| 1484 | mtx.synchronize do 1485 | ing = ![true, false].include?(type) 1486 | bar += type ? '=' : 'x' unless ing 1487 | b = case type 1488 | when :install then '+' when :update then '*' 1489 | when true, nil then '-' else 1490 | VIM::command("call add(s:update.errors, '#{name}')") 1491 | 'x' 1492 | end 1493 | result = 1494 | if type || type.nil? 1495 | ["#{b} #{name}: #{result.lines.to_a.last}"] 1496 | elsif result =~ /^Interrupted|^Timeout/ 1497 | ["#{b} #{name}: #{result}"] 1498 | else 1499 | ["#{b} #{name}"] + result.lines.map { |l| " " << l } 1500 | end 1501 | if lnum = where.call(name) 1502 | $curbuf.delete lnum 1503 | lnum = 4 if ing && lnum > maxy 1504 | end 1505 | result.each_with_index do |line, offset| 1506 | $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) 1507 | end 1508 | logh.call 1509 | end 1510 | } 1511 | bt = proc { |cmd, name, type, cleanup| 1512 | tried = timeout = 0 1513 | begin 1514 | tried += 1 1515 | timeout += limit 1516 | fd = nil 1517 | data = '' 1518 | if iswin 1519 | Timeout::timeout(timeout) do 1520 | tmp = VIM::evaluate('tempname()') 1521 | system("(#{cmd}) > #{tmp}") 1522 | data = File.read(tmp).chomp 1523 | File.unlink tmp rescue nil 1524 | end 1525 | else 1526 | fd = IO.popen(cmd).extend(PlugStream) 1527 | first_line = true 1528 | log_prob = 1.0 / nthr 1529 | while line = Timeout::timeout(timeout) { fd.get_line } 1530 | data << line 1531 | log.call name, line.chomp, type if name && (first_line || rand < log_prob) 1532 | first_line = false 1533 | end 1534 | fd.close 1535 | end 1536 | [$? == 0, data.chomp] 1537 | rescue Timeout::Error, Interrupt => e 1538 | if fd && !fd.closed? 1539 | killall fd.pid 1540 | fd.close 1541 | end 1542 | cleanup.call if cleanup 1543 | if e.is_a?(Timeout::Error) && tried < tries 1544 | 3.downto(1) do |countdown| 1545 | s = countdown > 1 ? 's' : '' 1546 | log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type 1547 | sleep 1 1548 | end 1549 | log.call name, 'Retrying ...', type 1550 | retry 1551 | end 1552 | [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] 1553 | end 1554 | } 1555 | main = Thread.current 1556 | threads = [] 1557 | watcher = Thread.new { 1558 | while VIM::evaluate('getchar(1)') 1559 | sleep 0.1 1560 | end 1561 | mtx.synchronize do 1562 | running = false 1563 | threads.each { |t| t.raise Interrupt } 1564 | end 1565 | threads.each { |t| t.join rescue nil } 1566 | main.kill 1567 | } 1568 | refresh = Thread.new { 1569 | while true 1570 | mtx.synchronize do 1571 | break unless running 1572 | VIM::command('noautocmd normal! a') 1573 | end 1574 | sleep 0.2 1575 | end 1576 | } if VIM::evaluate('s:mac_gui') == 1 1577 | 1578 | clone_opt = VIM::evaluate('s:clone_opt') 1579 | progress = VIM::evaluate('s:progress_opt(1)') 1580 | nthr.times do 1581 | mtx.synchronize do 1582 | threads << Thread.new { 1583 | while pair = take1.call 1584 | name = pair.first 1585 | dir, uri, branch, tag = pair.last.values_at *%w[dir uri branch tag] 1586 | checkout = esc(tag ? tag : branch) 1587 | merge = esc(tag ? tag : "origin/#{branch}") 1588 | subm = "git submodule update --init --recursive 2>&1" 1589 | exists = File.directory? dir 1590 | ok, result = 1591 | if exists 1592 | dir = iswin ? dir : esc(dir) 1593 | ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url", nil, nil, nil 1594 | current_uri = data.lines.to_a.last 1595 | if !ret 1596 | if data =~ /^Interrupted|^Timeout/ 1597 | [false, data] 1598 | else 1599 | [false, [data.chomp, "PlugClean required."].join($/)] 1600 | end 1601 | elsif current_uri.sub(/git::?@/, '') != uri.sub(/git::?@/, '') 1602 | [false, ["Invalid URI: #{current_uri}", 1603 | "Expected: #{uri}", 1604 | "PlugClean required."].join($/)] 1605 | else 1606 | if pull 1607 | log.call name, 'Updating ...', :update 1608 | fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' 1609 | bt.call "#{cd} #{dir} && git fetch #{fetch_opt} #{progress} 2>&1 && git checkout -q #{checkout} 2>&1 && git merge --ff-only #{merge} 2>&1 && #{subm}", name, :update, nil 1610 | else 1611 | [true, skip] 1612 | end 1613 | end 1614 | else 1615 | d = esc dir.sub(%r{[\\/]+$}, '') 1616 | log.call name, 'Installing ...', :install 1617 | bt.call "git clone #{clone_opt unless tag} #{progress} --recursive #{uri} -b #{checkout} #{d} 2>&1", name, :install, proc { 1618 | FileUtils.rm_rf dir 1619 | } 1620 | end 1621 | mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok 1622 | log.call name, result, ok 1623 | end 1624 | } if running 1625 | end 1626 | end 1627 | threads.each { |t| t.join rescue nil } 1628 | logh.call 1629 | refresh.kill if refresh 1630 | watcher.kill 1631 | EOF 1632 | endfunction 1633 | 1634 | function! s:shellesc(arg) 1635 | return '"'.escape(a:arg, '"').'"' 1636 | endfunction 1637 | 1638 | function! s:glob_dir(path) 1639 | return map(filter(s:lines(globpath(a:path, '**')), 'isdirectory(v:val)'), 's:dirpath(v:val)') 1640 | endfunction 1641 | 1642 | function! s:progress_bar(line, bar, total) 1643 | call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') 1644 | endfunction 1645 | 1646 | function! s:compare_git_uri(a, b) 1647 | let a = substitute(a:a, 'git:\{1,2}@', '', '') 1648 | let b = substitute(a:b, 'git:\{1,2}@', '', '') 1649 | return a ==# b 1650 | endfunction 1651 | 1652 | function! s:format_message(bullet, name, message) 1653 | if a:bullet != 'x' 1654 | return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] 1655 | else 1656 | let lines = map(s:lines(a:message), '" ".v:val') 1657 | return extend([printf('x %s:', a:name)], lines) 1658 | endif 1659 | endfunction 1660 | 1661 | function! s:with_cd(cmd, dir) 1662 | return printf('cd%s %s && %s', s:is_win ? ' /d' : '', s:shellesc(a:dir), a:cmd) 1663 | endfunction 1664 | 1665 | function! s:system(cmd, ...) 1666 | try 1667 | let [sh, shrd] = [&shell, &shellredir] 1668 | if !s:is_win 1669 | set shell=sh shellredir=>%s\ 2>&1 1670 | endif 1671 | let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd 1672 | return system(s:is_win ? '('.cmd.')' : cmd) 1673 | finally 1674 | let [&shell, &shellredir] = [sh, shrd] 1675 | endtry 1676 | endfunction 1677 | 1678 | function! s:system_chomp(...) 1679 | let ret = call('s:system', a:000) 1680 | return v:shell_error ? '' : substitute(ret, '\n$', '', '') 1681 | endfunction 1682 | 1683 | function! s:git_valid(spec, check_branch) 1684 | let ret = 1 1685 | let msg = 'OK' 1686 | if isdirectory(a:spec.dir) 1687 | let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url', a:spec.dir)) 1688 | let remote = result[-1] 1689 | if v:shell_error 1690 | let msg = join([remote, 'PlugClean required.'], "\n") 1691 | let ret = 0 1692 | elseif !s:compare_git_uri(remote, a:spec.uri) 1693 | let msg = join(['Invalid URI: '.remote, 1694 | \ 'Expected: '.a:spec.uri, 1695 | \ 'PlugClean required.'], "\n") 1696 | let ret = 0 1697 | elseif a:check_branch 1698 | let branch = result[0] 1699 | " Check tag 1700 | if has_key(a:spec, 'tag') 1701 | let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) 1702 | if a:spec.tag !=# tag 1703 | let msg = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', 1704 | \ (empty(tag) ? 'N/A' : tag), a:spec.tag) 1705 | let ret = 0 1706 | endif 1707 | " Check branch 1708 | elseif a:spec.branch !=# branch 1709 | let msg = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', 1710 | \ branch, a:spec.branch) 1711 | let ret = 0 1712 | endif 1713 | endif 1714 | else 1715 | let msg = 'Not found' 1716 | let ret = 0 1717 | endif 1718 | return [ret, msg] 1719 | endfunction 1720 | 1721 | function! s:rm_rf(dir) 1722 | if isdirectory(a:dir) 1723 | call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(a:dir)) 1724 | endif 1725 | endfunction 1726 | 1727 | function! s:clean(force) 1728 | call s:prepare() 1729 | call append(0, 'Searching for unused plugins in '.g:plug_home) 1730 | call append(1, '') 1731 | 1732 | " List of valid directories 1733 | let dirs = [] 1734 | let [cnt, total] = [0, len(g:plugs)] 1735 | for [name, spec] in items(g:plugs) 1736 | if !s:is_managed(name) || s:git_valid(spec, 0)[0] 1737 | call add(dirs, spec.dir) 1738 | endif 1739 | let cnt += 1 1740 | call s:progress_bar(2, repeat('=', cnt), total) 1741 | normal! 2G 1742 | redraw 1743 | endfor 1744 | 1745 | let allowed = {} 1746 | for dir in dirs 1747 | let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1 1748 | let allowed[dir] = 1 1749 | for child in s:glob_dir(dir) 1750 | let allowed[child] = 1 1751 | endfor 1752 | endfor 1753 | 1754 | let todo = [] 1755 | let found = sort(s:glob_dir(g:plug_home)) 1756 | while !empty(found) 1757 | let f = remove(found, 0) 1758 | if !has_key(allowed, f) && isdirectory(f) 1759 | call add(todo, f) 1760 | call append(line('$'), '- ' . f) 1761 | let found = filter(found, 'stridx(v:val, f) != 0') 1762 | end 1763 | endwhile 1764 | 1765 | normal! G 1766 | redraw 1767 | if empty(todo) 1768 | call append(line('$'), 'Already clean.') 1769 | else 1770 | call inputsave() 1771 | let yes = a:force || (input('Proceed? (y/N) ') =~? '^y') 1772 | call inputrestore() 1773 | if yes 1774 | for dir in todo 1775 | call s:rm_rf(dir) 1776 | endfor 1777 | call append(line('$'), 'Removed.') 1778 | else 1779 | call append(line('$'), 'Cancelled.') 1780 | endif 1781 | endif 1782 | normal! G 1783 | endfunction 1784 | 1785 | function! s:upgrade() 1786 | echo 'Downloading the latest version of vim-plug' 1787 | redraw 1788 | let tmp = tempname() 1789 | let new = tmp . '/plug.vim' 1790 | 1791 | try 1792 | let out = s:system(printf('git clone --depth 1 %s %s', s:plug_src, tmp)) 1793 | if v:shell_error 1794 | return s:err('Error upgrading vim-plug: '. out) 1795 | endif 1796 | 1797 | if readfile(s:me) ==# readfile(new) 1798 | echo 'vim-plug is already up-to-date' 1799 | return 0 1800 | else 1801 | call rename(s:me, s:me . '.old') 1802 | call rename(new, s:me) 1803 | unlet g:loaded_plug 1804 | echo 'vim-plug has been upgraded' 1805 | return 1 1806 | endif 1807 | finally 1808 | silent! call s:rm_rf(tmp) 1809 | endtry 1810 | endfunction 1811 | 1812 | function! s:upgrade_specs() 1813 | for spec in values(g:plugs) 1814 | let spec.frozen = get(spec, 'frozen', 0) 1815 | endfor 1816 | endfunction 1817 | 1818 | function! s:status() 1819 | call s:prepare() 1820 | call append(0, 'Checking plugins') 1821 | call append(1, '') 1822 | 1823 | let ecnt = 0 1824 | let unloaded = 0 1825 | let [cnt, total] = [0, len(g:plugs)] 1826 | for [name, spec] in items(g:plugs) 1827 | if has_key(spec, 'uri') 1828 | if isdirectory(spec.dir) 1829 | let [valid, msg] = s:git_valid(spec, 1) 1830 | else 1831 | let [valid, msg] = [0, 'Not found. Try PlugInstall.'] 1832 | endif 1833 | else 1834 | if isdirectory(spec.dir) 1835 | let [valid, msg] = [1, 'OK'] 1836 | else 1837 | let [valid, msg] = [0, 'Not found.'] 1838 | endif 1839 | endif 1840 | let cnt += 1 1841 | let ecnt += !valid 1842 | " `s:loaded` entry can be missing if PlugUpgraded 1843 | if valid && get(s:loaded, name, -1) == 0 1844 | let unloaded = 1 1845 | let msg .= ' (not loaded)' 1846 | endif 1847 | call s:progress_bar(2, repeat('=', cnt), total) 1848 | call append(3, s:format_message(valid ? '-' : 'x', name, msg)) 1849 | normal! 2G 1850 | redraw 1851 | endfor 1852 | call setline(1, 'Finished. '.ecnt.' error(s).') 1853 | normal! gg 1854 | setlocal nomodifiable 1855 | if unloaded 1856 | echo "Press 'L' on each line to load plugin, or 'U' to update" 1857 | nnoremap L :call status_load(line('.')) 1858 | xnoremap L :call status_load(line('.')) 1859 | end 1860 | endfunction 1861 | 1862 | function! s:extract_name(str, prefix, suffix) 1863 | return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') 1864 | endfunction 1865 | 1866 | function! s:status_load(lnum) 1867 | let line = getline(a:lnum) 1868 | let name = s:extract_name(line, '-', '(not loaded)') 1869 | if !empty(name) 1870 | call plug#load(name) 1871 | setlocal modifiable 1872 | call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) 1873 | setlocal nomodifiable 1874 | endif 1875 | endfunction 1876 | 1877 | function! s:status_update() range 1878 | let lines = getline(a:firstline, a:lastline) 1879 | let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') 1880 | if !empty(names) 1881 | echo 1882 | execute 'PlugUpdate' join(names) 1883 | endif 1884 | endfunction 1885 | 1886 | function! s:is_preview_window_open() 1887 | silent! wincmd P 1888 | if &previewwindow 1889 | wincmd p 1890 | return 1 1891 | endif 1892 | return 0 1893 | endfunction 1894 | 1895 | function! s:find_name(lnum) 1896 | for lnum in reverse(range(1, a:lnum)) 1897 | let line = getline(lnum) 1898 | if empty(line) 1899 | return '' 1900 | endif 1901 | let name = s:extract_name(line, '-', '') 1902 | if !empty(name) 1903 | return name 1904 | endif 1905 | endfor 1906 | return '' 1907 | endfunction 1908 | 1909 | function! s:preview_commit() 1910 | if b:plug_preview < 0 1911 | let b:plug_preview = !s:is_preview_window_open() 1912 | endif 1913 | 1914 | let sha = matchstr(getline('.'), '\(^ \)\@<=[0-9a-z]\{7}') 1915 | if empty(sha) 1916 | return 1917 | endif 1918 | 1919 | let name = s:find_name(line('.')) 1920 | if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) 1921 | return 1922 | endif 1923 | 1924 | execute 'pedit' sha 1925 | wincmd P 1926 | setlocal filetype=git buftype=nofile nobuflisted modifiable 1927 | execute 'silent read !cd' s:shellesc(g:plugs[name].dir) '&& git show --pretty=medium' sha 1928 | normal! gg"_dd 1929 | setlocal nomodifiable 1930 | nnoremap q :q 1931 | wincmd p 1932 | endfunction 1933 | 1934 | function! s:section(flags) 1935 | call search('\(^[x-] \)\@<=[^:]\+:', a:flags) 1936 | endfunction 1937 | 1938 | function! s:diff() 1939 | call s:prepare() 1940 | call append(0, 'Collecting updated changes ...') 1941 | normal! gg 1942 | redraw 1943 | 1944 | let cnt = 0 1945 | for [k, v] in items(g:plugs) 1946 | if !isdirectory(v.dir) || !s:is_managed(k) 1947 | continue 1948 | endif 1949 | 1950 | let diff = s:system_chomp('git log --pretty=format:"%h %s (%cr)" "HEAD...HEAD@{1}"', v.dir) 1951 | if !empty(diff) 1952 | call append(1, '') 1953 | call append(2, '- '.k.':') 1954 | call append(3, map(s:lines(diff), '" ". v:val')) 1955 | let cnt += 1 1956 | normal! gg 1957 | redraw 1958 | endif 1959 | endfor 1960 | 1961 | call setline(1, cnt == 0 ? 'No updates.' : 'Last update:') 1962 | nnoremap :silent! call preview_commit() 1963 | nnoremap o :silent! call preview_commit() 1964 | nnoremap X :call revert() 1965 | normal! gg 1966 | setlocal nomodifiable 1967 | if cnt > 0 1968 | echo "Press 'X' on each block to revert the update" 1969 | endif 1970 | endfunction 1971 | 1972 | function! s:revert() 1973 | let name = s:find_name(line('.')) 1974 | if empty(name) || !has_key(g:plugs, name) || 1975 | \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' 1976 | return 1977 | endif 1978 | 1979 | call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch), g:plugs[name].dir) 1980 | setlocal modifiable 1981 | normal! "_dap 1982 | setlocal nomodifiable 1983 | echo 'Reverted.' 1984 | endfunction 1985 | 1986 | function! s:snapshot(...) abort 1987 | let home = get(s:, 'plug_home_org', g:plug_home) 1988 | let [type, var, header] = s:is_win ? 1989 | \ ['dosbatch', '%PLUG_HOME%', 1990 | \ ['@echo off', ':: Generated by vim-plug', ':: '.strftime("%c"), '', 1991 | \ ':: Make sure to PlugUpdate first', '', 'set PLUG_HOME='.home]] : 1992 | \ ['sh', '$PLUG_HOME', 1993 | \ ['#!/bin/sh', '# Generated by vim-plug', '# '.strftime("%c"), '', 1994 | \ 'vim +PlugUpdate +qa', '', 'PLUG_HOME='.s:esc(home)]] 1995 | 1996 | call s:prepare() 1997 | execute 'setf' type 1998 | call append(0, header) 1999 | call append('$', '') 2000 | 1 2001 | redraw 2002 | 2003 | let dirs = sort(map(values(filter(copy(g:plugs), 2004 | \'has_key(v:val, "uri") && isdirectory(v:val.dir)')), 'v:val.dir')) 2005 | let anchor = line('$') - 1 2006 | for dir in reverse(dirs) 2007 | let sha = s:system_chomp('git rev-parse --short HEAD', dir) 2008 | if !empty(sha) 2009 | call append(anchor, printf('cd %s && git reset --hard %s', 2010 | \ substitute(dir, '^\V'.escape(g:plug_home, '\'), var, ''), sha)) 2011 | redraw 2012 | endif 2013 | endfor 2014 | 2015 | if a:0 > 0 2016 | let fn = expand(a:1) 2017 | let fne = s:esc(fn) 2018 | call writefile(getline(1, '$'), fn) 2019 | if !s:is_win | call s:system('chmod +x ' . fne) | endif 2020 | echo 'Saved to '.a:1 2021 | silent execute 'e' fne 2022 | endif 2023 | endfunction 2024 | 2025 | function! s:split_rtp() 2026 | return split(&rtp, '\\\@`-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') || has('win64') 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! plug#end() 197 | if !exists('g:plugs') 198 | return s:err('Call plug#begin() first') 199 | endif 200 | 201 | if exists('#PlugLOD') 202 | augroup PlugLOD 203 | autocmd! 204 | augroup END 205 | augroup! PlugLOD 206 | endif 207 | let lod = { 'ft': {}, 'map': {}, 'cmd': {} } 208 | 209 | if exists('g:did_load_filetypes') 210 | filetype off 211 | endif 212 | for name in g:plugs_order 213 | if !has_key(g:plugs, name) 214 | continue 215 | endif 216 | let plug = g:plugs[name] 217 | if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for') 218 | let s:loaded[name] = 1 219 | continue 220 | endif 221 | 222 | if has_key(plug, 'on') 223 | let s:triggers[name] = { 'map': [], 'cmd': [] } 224 | for cmd in s:to_a(plug.on) 225 | if cmd =~? '^.\+' 226 | if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) 227 | call s:assoc(lod.map, cmd, name) 228 | endif 229 | call add(s:triggers[name].map, cmd) 230 | elseif cmd =~# '^[A-Z]' 231 | let cmd = substitute(cmd, '!*$', '', '') 232 | if exists(':'.cmd) != 2 233 | call s:assoc(lod.cmd, cmd, name) 234 | endif 235 | call add(s:triggers[name].cmd, cmd) 236 | else 237 | call s:err('Invalid `on` option: '.cmd. 238 | \ '. Should start with an uppercase letter or ``.') 239 | endif 240 | endfor 241 | endif 242 | 243 | if has_key(plug, 'for') 244 | let types = s:to_a(plug.for) 245 | if !empty(types) 246 | augroup filetypedetect 247 | call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') 248 | augroup END 249 | endif 250 | for type in types 251 | call s:assoc(lod.ft, type, name) 252 | endfor 253 | endif 254 | endfor 255 | 256 | for [cmd, names] in items(lod.cmd) 257 | execute printf( 258 | \ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "", , , , %s)', 259 | \ cmd, string(cmd), string(names)) 260 | endfor 261 | 262 | for [map, names] in items(lod.map) 263 | for [mode, map_prefix, key_prefix] in 264 | \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] 265 | execute printf( 266 | \ '%snoremap %s %s:call lod_map(%s, %s, %s, "%s")', 267 | \ mode, map, map_prefix, string(map), string(names), mode != 'i', key_prefix) 268 | endfor 269 | endfor 270 | 271 | for [ft, names] in items(lod.ft) 272 | augroup PlugLOD 273 | execute printf('autocmd FileType %s call lod_ft(%s, %s)', 274 | \ ft, string(ft), string(names)) 275 | augroup END 276 | endfor 277 | 278 | call s:reorg_rtp() 279 | filetype plugin indent on 280 | if has('vim_starting') 281 | if has('syntax') && !exists('g:syntax_on') 282 | syntax enable 283 | end 284 | else 285 | call s:reload_plugins() 286 | endif 287 | endfunction 288 | 289 | function! s:loaded_names() 290 | return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)') 291 | endfunction 292 | 293 | function! s:load_plugin(spec) 294 | call s:source(s:rtp(a:spec), 'plugin/**/*.vim', 'after/plugin/**/*.vim') 295 | endfunction 296 | 297 | function! s:reload_plugins() 298 | for name in s:loaded_names() 299 | call s:load_plugin(g:plugs[name]) 300 | endfor 301 | endfunction 302 | 303 | function! s:trim(str) 304 | return substitute(a:str, '[\/]\+$', '', '') 305 | endfunction 306 | 307 | function! s:version_requirement(val, min) 308 | for idx in range(0, len(a:min) - 1) 309 | let v = get(a:val, idx, 0) 310 | if v < a:min[idx] | return 0 311 | elseif v > a:min[idx] | return 1 312 | endif 313 | endfor 314 | return 1 315 | endfunction 316 | 317 | function! s:git_version_requirement(...) 318 | if !exists('s:git_version') 319 | let s:git_version = map(split(split(s:system('git --version'))[2], '\.'), 'str2nr(v:val)') 320 | endif 321 | return s:version_requirement(s:git_version, a:000) 322 | endfunction 323 | 324 | function! s:progress_opt(base) 325 | return a:base && !s:is_win && 326 | \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' 327 | endfunction 328 | 329 | if s:is_win 330 | function! s:rtp(spec) 331 | return s:path(a:spec.dir . get(a:spec, 'rtp', '')) 332 | endfunction 333 | 334 | function! s:path(path) 335 | return s:trim(substitute(a:path, '/', '\', 'g')) 336 | endfunction 337 | 338 | function! s:dirpath(path) 339 | return s:path(a:path) . '\' 340 | endfunction 341 | 342 | function! s:is_local_plug(repo) 343 | return a:repo =~? '^[a-z]:\|^[%~]' 344 | endfunction 345 | else 346 | function! s:rtp(spec) 347 | return s:dirpath(a:spec.dir . get(a:spec, 'rtp', '')) 348 | endfunction 349 | 350 | function! s:path(path) 351 | return s:trim(a:path) 352 | endfunction 353 | 354 | function! s:dirpath(path) 355 | return substitute(a:path, '[/\\]*$', '/', '') 356 | endfunction 357 | 358 | function! s:is_local_plug(repo) 359 | return a:repo[0] =~ '[/$~]' 360 | endfunction 361 | endif 362 | 363 | function! s:err(msg) 364 | echohl ErrorMsg 365 | echom '[vim-plug] '.a:msg 366 | echohl None 367 | endfunction 368 | 369 | function! s:warn(cmd, msg) 370 | echohl WarningMsg 371 | execute a:cmd 'a:msg' 372 | echohl None 373 | endfunction 374 | 375 | function! s:esc(path) 376 | return escape(a:path, ' ') 377 | endfunction 378 | 379 | function! s:escrtp(path) 380 | return escape(a:path, ' ,') 381 | endfunction 382 | 383 | function! s:remove_rtp() 384 | for name in s:loaded_names() 385 | let rtp = s:rtp(g:plugs[name]) 386 | execute 'set rtp-='.s:escrtp(rtp) 387 | let after = globpath(rtp, 'after') 388 | if isdirectory(after) 389 | execute 'set rtp-='.s:escrtp(after) 390 | endif 391 | endfor 392 | endfunction 393 | 394 | function! s:reorg_rtp() 395 | if !empty(s:first_rtp) 396 | execute 'set rtp-='.s:first_rtp 397 | execute 'set rtp-='.s:last_rtp 398 | endif 399 | 400 | " &rtp is modified from outside 401 | if exists('s:prtp') && s:prtp !=# &rtp 402 | call s:remove_rtp() 403 | unlet! s:middle 404 | endif 405 | 406 | let s:middle = get(s:, 'middle', &rtp) 407 | let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') 408 | let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), '!empty(v:val)') 409 | let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') 410 | \ . ','.s:middle.',' 411 | \ . join(map(afters, 'escape(v:val, ",")'), ',') 412 | let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') 413 | let s:prtp = &rtp 414 | 415 | if !empty(s:first_rtp) 416 | execute 'set rtp^='.s:first_rtp 417 | execute 'set rtp+='.s:last_rtp 418 | endif 419 | endfunction 420 | 421 | function! s:doautocmd(...) 422 | if exists('#'.join(a:000, '#')) 423 | execute 'doautocmd' ((v:version > 703 || has('patch442')) ? '' : '') join(a:000) 424 | endif 425 | endfunction 426 | 427 | function! s:dobufread(names) 428 | for name in a:names 429 | let path = s:rtp(g:plugs[name]).'/**' 430 | for dir in ['ftdetect', 'ftplugin'] 431 | if len(finddir(dir, path)) 432 | if exists('#BufRead') 433 | doautocmd BufRead 434 | endif 435 | return 436 | endif 437 | endfor 438 | endfor 439 | endfunction 440 | 441 | function! plug#load(...) 442 | if a:0 == 0 443 | return s:err('Argument missing: plugin name(s) required') 444 | endif 445 | if !exists('g:plugs') 446 | return s:err('plug#begin was not called') 447 | endif 448 | let names = a:0 == 1 && type(a:1) == s:TYPE.list ? a:1 : a:000 449 | let unknowns = filter(copy(names), '!has_key(g:plugs, v:val)') 450 | if !empty(unknowns) 451 | let s = len(unknowns) > 1 ? 's' : '' 452 | return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', '))) 453 | end 454 | let unloaded = filter(copy(names), '!get(s:loaded, v:val, 0)') 455 | if !empty(unloaded) 456 | for name in unloaded 457 | call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 458 | endfor 459 | call s:dobufread(unloaded) 460 | return 1 461 | end 462 | return 0 463 | endfunction 464 | 465 | function! s:remove_triggers(name) 466 | if !has_key(s:triggers, a:name) 467 | return 468 | endif 469 | for cmd in s:triggers[a:name].cmd 470 | execute 'silent! delc' cmd 471 | endfor 472 | for map in s:triggers[a:name].map 473 | execute 'silent! unmap' map 474 | execute 'silent! iunmap' map 475 | endfor 476 | call remove(s:triggers, a:name) 477 | endfunction 478 | 479 | function! s:lod(names, types, ...) 480 | for name in a:names 481 | call s:remove_triggers(name) 482 | let s:loaded[name] = 1 483 | endfor 484 | call s:reorg_rtp() 485 | 486 | for name in a:names 487 | let rtp = s:rtp(g:plugs[name]) 488 | for dir in a:types 489 | call s:source(rtp, dir.'/**/*.vim') 490 | endfor 491 | if a:0 492 | if !s:source(rtp, a:1) && !empty(s:glob(rtp, a:2)) 493 | execute 'runtime' a:1 494 | endif 495 | call s:source(rtp, a:2) 496 | endif 497 | call s:doautocmd('User', name) 498 | endfor 499 | endfunction 500 | 501 | function! s:lod_ft(pat, names) 502 | let syn = 'syntax/'.a:pat.'.vim' 503 | call s:lod(a:names, ['plugin', 'after/plugin'], syn, 'after/'.syn) 504 | execute 'autocmd! PlugLOD FileType' a:pat 505 | call s:doautocmd('filetypeplugin', 'FileType') 506 | call s:doautocmd('filetypeindent', 'FileType') 507 | endfunction 508 | 509 | function! s:lod_cmd(cmd, bang, l1, l2, args, names) 510 | call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 511 | call s:dobufread(a:names) 512 | execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) 513 | endfunction 514 | 515 | function! s:lod_map(map, names, with_prefix, prefix) 516 | call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) 517 | call s:dobufread(a:names) 518 | let extra = '' 519 | while 1 520 | let c = getchar(0) 521 | if c == 0 522 | break 523 | endif 524 | let extra .= nr2char(c) 525 | endwhile 526 | 527 | if a:with_prefix 528 | let prefix = v:count ? v:count : '' 529 | let prefix .= '"'.v:register.a:prefix 530 | if mode(1) == 'no' 531 | if v:operator == 'c' 532 | let prefix = "\" . prefix 533 | endif 534 | let prefix .= v:operator 535 | endif 536 | call feedkeys(prefix, 'n') 537 | endif 538 | call feedkeys(substitute(a:map, '^', "\", '') . extra) 539 | endfunction 540 | 541 | function! plug#(repo, ...) 542 | if a:0 > 1 543 | return s:err('Invalid number of arguments (1..2)') 544 | endif 545 | 546 | try 547 | let repo = s:trim(a:repo) 548 | let opts = a:0 == 1 ? s:parse_options(a:1) : s:base_spec 549 | let name = get(opts, 'as', fnamemodify(repo, ':t:s?\.git$??')) 550 | let spec = extend(s:infer_properties(name, repo), opts) 551 | if !has_key(g:plugs, name) 552 | call add(g:plugs_order, name) 553 | endif 554 | let g:plugs[name] = spec 555 | let s:loaded[name] = get(s:loaded, name, 0) 556 | catch 557 | return s:err(v:exception) 558 | endtry 559 | endfunction 560 | 561 | function! s:parse_options(arg) 562 | let opts = copy(s:base_spec) 563 | let type = type(a:arg) 564 | if type == s:TYPE.string 565 | let opts.tag = a:arg 566 | elseif type == s:TYPE.dict 567 | call extend(opts, a:arg) 568 | if has_key(opts, 'dir') 569 | let opts.dir = s:dirpath(expand(opts.dir)) 570 | endif 571 | else 572 | throw 'Invalid argument type (expected: string or dictionary)' 573 | endif 574 | return opts 575 | endfunction 576 | 577 | function! s:infer_properties(name, repo) 578 | let repo = a:repo 579 | if s:is_local_plug(repo) 580 | return { 'dir': s:dirpath(expand(repo)) } 581 | else 582 | if repo =~ ':' 583 | let uri = repo 584 | else 585 | if repo !~ '/' 586 | throw printf('Invalid argument: %s (implicit `vim-scripts'' expansion is deprecated)', repo) 587 | endif 588 | let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git') 589 | let uri = printf(fmt, repo) 590 | endif 591 | return { 'dir': s:dirpath(g:plug_home.'/'.a:name), 'uri': uri } 592 | endif 593 | endfunction 594 | 595 | function! s:install(force, names) 596 | call s:update_impl(0, a:force, a:names) 597 | endfunction 598 | 599 | function! s:update(force, names) 600 | call s:update_impl(1, a:force, a:names) 601 | endfunction 602 | 603 | function! plug#helptags() 604 | if !exists('g:plugs') 605 | return s:err('plug#begin was not called') 606 | endif 607 | for spec in values(g:plugs) 608 | let docd = join([s:rtp(spec), 'doc'], '/') 609 | if isdirectory(docd) 610 | silent! execute 'helptags' s:esc(docd) 611 | endif 612 | endfor 613 | return 1 614 | endfunction 615 | 616 | function! s:syntax() 617 | syntax clear 618 | syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber 619 | syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX 620 | syn match plugNumber /[0-9]\+[0-9.]*/ contained 621 | syn match plugBracket /[[\]]/ contained 622 | syn match plugX /x/ contained 623 | syn match plugDash /^-/ 624 | syn match plugPlus /^+/ 625 | syn match plugStar /^*/ 626 | syn match plugMessage /\(^- \)\@<=.*/ 627 | syn match plugName /\(^- \)\@<=[^ ]*:/ 628 | syn match plugSha /\%(: \)\@<=[0-9a-f]\{4,}$/ 629 | syn match plugTag /(tag: [^)]\+)/ 630 | syn match plugInstall /\(^+ \)\@<=[^:]*/ 631 | syn match plugUpdate /\(^* \)\@<=[^:]*/ 632 | syn match plugCommit /^ \X*[0-9a-f]\{7,9} .*/ contains=plugRelDate,plugEdge,plugTag 633 | syn match plugEdge /^ \X\+$/ 634 | syn match plugEdge /^ \X*/ contained nextgroup=plugSha 635 | syn match plugSha /[0-9a-f]\{7,9}/ contained 636 | syn match plugRelDate /([^)]*)$/ contained 637 | syn match plugNotLoaded /(not loaded)$/ 638 | syn match plugError /^x.*/ 639 | syn region plugDeleted start=/^\~ .*/ end=/^\ze\S/ 640 | syn match plugH2 /^.*:\n-\+$/ 641 | syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean 642 | hi def link plug1 Title 643 | hi def link plug2 Repeat 644 | hi def link plugH2 Type 645 | hi def link plugX Exception 646 | hi def link plugBracket Structure 647 | hi def link plugNumber Number 648 | 649 | hi def link plugDash Special 650 | hi def link plugPlus Constant 651 | hi def link plugStar Boolean 652 | 653 | hi def link plugMessage Function 654 | hi def link plugName Label 655 | hi def link plugInstall Function 656 | hi def link plugUpdate Type 657 | 658 | hi def link plugError Error 659 | hi def link plugDeleted Ignore 660 | hi def link plugRelDate Comment 661 | hi def link plugEdge PreProc 662 | hi def link plugSha Identifier 663 | hi def link plugTag Constant 664 | 665 | hi def link plugNotLoaded Comment 666 | endfunction 667 | 668 | function! s:lpad(str, len) 669 | return a:str . repeat(' ', a:len - len(a:str)) 670 | endfunction 671 | 672 | function! s:lines(msg) 673 | return split(a:msg, "[\r\n]") 674 | endfunction 675 | 676 | function! s:lastline(msg) 677 | return get(s:lines(a:msg), -1, '') 678 | endfunction 679 | 680 | function! s:new_window() 681 | execute get(g:, 'plug_window', 'vertical topleft new') 682 | endfunction 683 | 684 | function! s:plug_window_exists() 685 | let buflist = tabpagebuflist(s:plug_tab) 686 | return !empty(buflist) && index(buflist, s:plug_buf) >= 0 687 | endfunction 688 | 689 | function! s:switch_in() 690 | if !s:plug_window_exists() 691 | return 0 692 | endif 693 | 694 | if winbufnr(0) != s:plug_buf 695 | let s:pos = [tabpagenr(), winnr(), winsaveview()] 696 | execute 'normal!' s:plug_tab.'gt' 697 | let winnr = bufwinnr(s:plug_buf) 698 | execute winnr.'wincmd w' 699 | call add(s:pos, winsaveview()) 700 | else 701 | let s:pos = [winsaveview()] 702 | endif 703 | 704 | setlocal modifiable 705 | return 1 706 | endfunction 707 | 708 | function! s:switch_out(...) 709 | call winrestview(s:pos[-1]) 710 | setlocal nomodifiable 711 | if a:0 > 0 712 | execute a:1 713 | endif 714 | 715 | if len(s:pos) > 1 716 | execute 'normal!' s:pos[0].'gt' 717 | execute s:pos[1] 'wincmd w' 718 | call winrestview(s:pos[2]) 719 | endif 720 | endfunction 721 | 722 | function! s:finish_bindings() 723 | nnoremap R :call retry() 724 | nnoremap D :PlugDiff 725 | nnoremap S :PlugStatus 726 | nnoremap U :call status_update() 727 | xnoremap U :call status_update() 728 | nnoremap ]] :silent! call section('') 729 | nnoremap [[ :silent! call section('b') 730 | endfunction 731 | 732 | function! s:prepare(...) 733 | if empty(getcwd()) 734 | throw 'Invalid current working directory. Cannot proceed.' 735 | endif 736 | 737 | for evar in ['$GIT_DIR', '$GIT_WORK_TREE'] 738 | if exists(evar) 739 | throw evar.' detected. Cannot proceed.' 740 | endif 741 | endfor 742 | 743 | call s:job_abort() 744 | if s:switch_in() 745 | if b:plug_preview == 1 746 | pc 747 | endif 748 | enew 749 | else 750 | call s:new_window() 751 | endif 752 | 753 | nnoremap q :if b:plug_preview==1pcendifbd 754 | if a:0 == 0 755 | call s:finish_bindings() 756 | endif 757 | let b:plug_preview = -1 758 | let s:plug_tab = tabpagenr() 759 | let s:plug_buf = winbufnr(0) 760 | call s:assign_name() 761 | 762 | for k in ['', 'L', 'o', 'X', 'd', 'dd'] 763 | execute 'silent! unmap ' k 764 | endfor 765 | setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline modifiable nospell 766 | setf vim-plug 767 | if exists('g:syntax_on') 768 | call s:syntax() 769 | endif 770 | endfunction 771 | 772 | function! s:assign_name() 773 | " Assign buffer name 774 | let prefix = '[Plugins]' 775 | let name = prefix 776 | let idx = 2 777 | while bufexists(name) 778 | let name = printf('%s (%s)', prefix, idx) 779 | let idx = idx + 1 780 | endwhile 781 | silent! execute 'f' fnameescape(name) 782 | endfunction 783 | 784 | function! s:chsh(swap) 785 | let prev = [&shell, &shellcmdflag, &shellredir] 786 | if s:is_win 787 | set shell=cmd.exe shellcmdflag=/c shellredir=>%s\ 2>&1 788 | elseif a:swap 789 | set shell=sh shellredir=>%s\ 2>&1 790 | endif 791 | return prev 792 | endfunction 793 | 794 | function! s:bang(cmd, ...) 795 | try 796 | let [sh, shellcmdflag, shrd] = s:chsh(a:0) 797 | " FIXME: Escaping is incomplete. We could use shellescape with eval, 798 | " but it won't work on Windows. 799 | let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd 800 | if s:is_win 801 | let batchfile = tempname().'.bat' 802 | call writefile(['@echo off', cmd], batchfile) 803 | let cmd = batchfile 804 | endif 805 | let g:_plug_bang = (s:is_win && has('gui_running') ? 'silent ' : '').'!'.escape(cmd, '#!%') 806 | execute "normal! :execute g:_plug_bang\\" 807 | finally 808 | unlet g:_plug_bang 809 | let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] 810 | if s:is_win 811 | call delete(batchfile) 812 | endif 813 | endtry 814 | return v:shell_error ? 'Exit status: ' . v:shell_error : '' 815 | endfunction 816 | 817 | function! s:regress_bar() 818 | let bar = substitute(getline(2)[1:-2], '.*\zs=', 'x', '') 819 | call s:progress_bar(2, bar, len(bar)) 820 | endfunction 821 | 822 | function! s:is_updated(dir) 823 | return !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', a:dir)) 824 | endfunction 825 | 826 | function! s:do(pull, force, todo) 827 | for [name, spec] in items(a:todo) 828 | if !isdirectory(spec.dir) 829 | continue 830 | endif 831 | let installed = has_key(s:update.new, name) 832 | let updated = installed ? 0 : 833 | \ (a:pull && index(s:update.errors, name) < 0 && s:is_updated(spec.dir)) 834 | if a:force || installed || updated 835 | execute 'cd' s:esc(spec.dir) 836 | call append(3, '- Post-update hook for '. name .' ... ') 837 | let error = '' 838 | let type = type(spec.do) 839 | if type == s:TYPE.string 840 | if spec.do[0] == ':' 841 | if !get(s:loaded, name, 0) 842 | let s:loaded[name] = 1 843 | call s:reorg_rtp() 844 | endif 845 | call s:load_plugin(spec) 846 | try 847 | execute spec.do[1:] 848 | catch 849 | let error = v:exception 850 | endtry 851 | if !s:plug_window_exists() 852 | cd - 853 | throw 'Warning: vim-plug was terminated by the post-update hook of '.name 854 | endif 855 | else 856 | let error = s:bang(spec.do) 857 | endif 858 | elseif type == s:TYPE.funcref 859 | try 860 | let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged') 861 | call spec.do({ 'name': name, 'status': status, 'force': a:force }) 862 | catch 863 | let error = v:exception 864 | endtry 865 | else 866 | let error = 'Invalid hook type' 867 | endif 868 | call s:switch_in() 869 | call setline(4, empty(error) ? (getline(4) . 'OK') 870 | \ : ('x' . getline(4)[1:] . error)) 871 | if !empty(error) 872 | call add(s:update.errors, name) 873 | call s:regress_bar() 874 | endif 875 | cd - 876 | endif 877 | endfor 878 | endfunction 879 | 880 | function! s:hash_match(a, b) 881 | return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 882 | endfunction 883 | 884 | function! s:checkout(spec) 885 | let sha = a:spec.commit 886 | let output = s:system('git rev-parse HEAD', a:spec.dir) 887 | if !v:shell_error && !s:hash_match(sha, s:lines(output)[0]) 888 | let output = s:system( 889 | \ 'git fetch --depth 999999 && git checkout '.s:esc(sha).' --', a:spec.dir) 890 | endif 891 | return output 892 | endfunction 893 | 894 | function! s:finish(pull) 895 | let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) 896 | if new_frozen 897 | let s = new_frozen > 1 ? 's' : '' 898 | call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) 899 | endif 900 | call append(3, '- Finishing ... ') | 4 901 | redraw 902 | call plug#helptags() 903 | call plug#end() 904 | call setline(4, getline(4) . 'Done!') 905 | redraw 906 | let msgs = [] 907 | if !empty(s:update.errors) 908 | call add(msgs, "Press 'R' to retry.") 909 | endif 910 | if a:pull && len(s:update.new) < len(filter(getline(5, '$'), 911 | \ "v:val =~ '^- ' && v:val !~# 'Already up.to.date'")) 912 | call add(msgs, "Press 'D' to see the updated changes.") 913 | endif 914 | echo join(msgs, ' ') 915 | call s:finish_bindings() 916 | endfunction 917 | 918 | function! s:retry() 919 | if empty(s:update.errors) 920 | return 921 | endif 922 | echo 923 | call s:update_impl(s:update.pull, s:update.force, 924 | \ extend(copy(s:update.errors), [s:update.threads])) 925 | endfunction 926 | 927 | function! s:is_managed(name) 928 | return has_key(g:plugs[a:name], 'uri') 929 | endfunction 930 | 931 | function! s:names(...) 932 | return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)')) 933 | endfunction 934 | 935 | function! s:check_ruby() 936 | silent! ruby require 'thread'; VIM::command("let g:plug_ruby = '#{RUBY_VERSION}'") 937 | if !exists('g:plug_ruby') 938 | redraw! 939 | return s:warn('echom', 'Warning: Ruby interface is broken') 940 | endif 941 | let ruby_version = split(g:plug_ruby, '\.') 942 | unlet g:plug_ruby 943 | return s:version_requirement(ruby_version, [1, 8, 7]) 944 | endfunction 945 | 946 | function! s:update_impl(pull, force, args) abort 947 | let sync = index(a:args, '--sync') >= 0 || has('vim_starting') 948 | let args = filter(copy(a:args), 'v:val != "--sync"') 949 | let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? 950 | \ remove(args, -1) : get(g:, 'plug_threads', 16) 951 | 952 | let managed = filter(copy(g:plugs), 's:is_managed(v:key)') 953 | let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : 954 | \ filter(managed, 'index(args, v:key) >= 0') 955 | 956 | if empty(todo) 957 | return s:warn('echo', 'No plugin to '. (a:pull ? 'update' : 'install')) 958 | endif 959 | 960 | if !s:is_win && s:git_version_requirement(2, 3) 961 | let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' 962 | let $GIT_TERMINAL_PROMPT = 0 963 | for plug in values(todo) 964 | let plug.uri = substitute(plug.uri, 965 | \ '^https://git::@github\.com', 'https://github.com', '') 966 | endfor 967 | endif 968 | 969 | if !isdirectory(g:plug_home) 970 | try 971 | call mkdir(g:plug_home, 'p') 972 | catch 973 | return s:err(printf('Invalid plug directory: %s. '. 974 | \ 'Try to call plug#begin with a valid directory', g:plug_home)) 975 | endtry 976 | endif 977 | 978 | if has('nvim') && !exists('*jobwait') && threads > 1 979 | call s:warn('echom', '[vim-plug] Update Neovim for parallel installer') 980 | endif 981 | 982 | let use_job = s:nvim || s:vim8 983 | let python = (has('python') || has('python3')) && !use_job 984 | 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() 985 | 986 | let s:update = { 987 | \ 'start': reltime(), 988 | \ 'all': todo, 989 | \ 'todo': copy(todo), 990 | \ 'errors': [], 991 | \ 'pull': a:pull, 992 | \ 'force': a:force, 993 | \ 'new': {}, 994 | \ 'threads': (python || ruby || use_job) ? min([len(todo), threads]) : 1, 995 | \ 'bar': '', 996 | \ 'fin': 0 997 | \ } 998 | 999 | call s:prepare(1) 1000 | call append(0, ['', '']) 1001 | normal! 2G 1002 | silent! redraw 1003 | 1004 | let s:clone_opt = get(g:, 'plug_shallow', 1) ? 1005 | \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : '' 1006 | 1007 | if has('win32unix') 1008 | let s:clone_opt .= ' -c core.eol=lf -c core.autocrlf=input' 1009 | endif 1010 | 1011 | " Python version requirement (>= 2.7) 1012 | if python && !has('python3') && !ruby && !use_job && s:update.threads > 1 1013 | redir => pyv 1014 | silent python import platform; print platform.python_version() 1015 | redir END 1016 | let python = s:version_requirement( 1017 | \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) 1018 | endif 1019 | 1020 | if (python || ruby) && s:update.threads > 1 1021 | try 1022 | let imd = &imd 1023 | if s:mac_gui 1024 | set noimd 1025 | endif 1026 | if ruby 1027 | call s:update_ruby() 1028 | else 1029 | call s:update_python() 1030 | endif 1031 | catch 1032 | let lines = getline(4, '$') 1033 | let printed = {} 1034 | silent! 4,$d _ 1035 | for line in lines 1036 | let name = s:extract_name(line, '.', '') 1037 | if empty(name) || !has_key(printed, name) 1038 | call append('$', line) 1039 | if !empty(name) 1040 | let printed[name] = 1 1041 | if line[0] == 'x' && index(s:update.errors, name) < 0 1042 | call add(s:update.errors, name) 1043 | end 1044 | endif 1045 | endif 1046 | endfor 1047 | finally 1048 | let &imd = imd 1049 | call s:update_finish() 1050 | endtry 1051 | else 1052 | call s:update_vim() 1053 | while use_job && sync 1054 | sleep 100m 1055 | if s:update.fin 1056 | break 1057 | endif 1058 | endwhile 1059 | endif 1060 | endfunction 1061 | 1062 | function! s:log4(name, msg) 1063 | call setline(4, printf('- %s (%s)', a:msg, a:name)) 1064 | redraw 1065 | endfunction 1066 | 1067 | function! s:update_finish() 1068 | if exists('s:git_terminal_prompt') 1069 | let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt 1070 | endif 1071 | if s:switch_in() 1072 | call append(3, '- Updating ...') | 4 1073 | 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))')) 1074 | let [pos, _] = s:logpos(name) 1075 | if !pos 1076 | continue 1077 | endif 1078 | if has_key(spec, 'commit') 1079 | call s:log4(name, 'Checking out '.spec.commit) 1080 | let out = s:checkout(spec) 1081 | elseif has_key(spec, 'tag') 1082 | let tag = spec.tag 1083 | if tag =~ '\*' 1084 | let tags = s:lines(s:system('git tag --list '.s:shellesc(tag).' --sort -version:refname 2>&1', spec.dir)) 1085 | if !v:shell_error && !empty(tags) 1086 | let tag = tags[0] 1087 | call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) 1088 | call append(3, '') 1089 | endif 1090 | endif 1091 | call s:log4(name, 'Checking out '.tag) 1092 | let out = s:system('git checkout -q '.s:esc(tag).' -- 2>&1', spec.dir) 1093 | else 1094 | let branch = s:esc(get(spec, 'branch', 'master')) 1095 | call s:log4(name, 'Merging origin/'.branch) 1096 | let out = s:system('git checkout -q '.branch.' -- 2>&1' 1097 | \. (has_key(s:update.new, name) ? '' : ('&& git merge --ff-only origin/'.branch.' 2>&1')), spec.dir) 1098 | endif 1099 | if !v:shell_error && filereadable(spec.dir.'/.gitmodules') && 1100 | \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) 1101 | call s:log4(name, 'Updating submodules. This may take a while.') 1102 | let out .= s:bang('git submodule update --init --recursive 2>&1', spec.dir) 1103 | endif 1104 | let msg = s:format_message(v:shell_error ? 'x': '-', name, out) 1105 | if v:shell_error 1106 | call add(s:update.errors, name) 1107 | call s:regress_bar() 1108 | silent execute pos 'd _' 1109 | call append(4, msg) | 4 1110 | elseif !empty(out) 1111 | call setline(pos, msg[0]) 1112 | endif 1113 | redraw 1114 | endfor 1115 | silent 4 d _ 1116 | try 1117 | 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")')) 1118 | catch 1119 | call s:warn('echom', v:exception) 1120 | call s:warn('echo', '') 1121 | return 1122 | endtry 1123 | call s:finish(s:update.pull) 1124 | call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') 1125 | call s:switch_out('normal! gg') 1126 | endif 1127 | endfunction 1128 | 1129 | function! s:job_abort() 1130 | if (!s:nvim && !s:vim8) || !exists('s:jobs') 1131 | return 1132 | endif 1133 | 1134 | for [name, j] in items(s:jobs) 1135 | if s:nvim 1136 | silent! call jobstop(j.jobid) 1137 | elseif s:vim8 1138 | silent! call job_stop(j.jobid) 1139 | endif 1140 | if j.new 1141 | call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir)) 1142 | endif 1143 | endfor 1144 | let s:jobs = {} 1145 | endfunction 1146 | 1147 | function! s:last_non_empty_line(lines) 1148 | let len = len(a:lines) 1149 | for idx in range(len) 1150 | let line = a:lines[len-idx-1] 1151 | if !empty(line) 1152 | return line 1153 | endif 1154 | endfor 1155 | return '' 1156 | endfunction 1157 | 1158 | function! s:job_out_cb(self, data) abort 1159 | let self = a:self 1160 | let data = remove(self.lines, -1) . a:data 1161 | let lines = map(split(data, "\n", 1), 'split(v:val, "\r", 1)[-1]') 1162 | call extend(self.lines, lines) 1163 | " To reduce the number of buffer updates 1164 | let self.tick = get(self, 'tick', -1) + 1 1165 | if !self.running || self.tick % len(s:jobs) == 0 1166 | let bullet = self.running ? (self.new ? '+' : '*') : (self.error ? 'x' : '-') 1167 | let result = self.error ? join(self.lines, "\n") : s:last_non_empty_line(self.lines) 1168 | call s:log(bullet, self.name, result) 1169 | endif 1170 | endfunction 1171 | 1172 | function! s:job_exit_cb(self, data) abort 1173 | let a:self.running = 0 1174 | let a:self.error = a:data != 0 1175 | call s:reap(a:self.name) 1176 | call s:tick() 1177 | endfunction 1178 | 1179 | function! s:job_cb(fn, job, ch, data) 1180 | if !s:plug_window_exists() " plug window closed 1181 | return s:job_abort() 1182 | endif 1183 | call call(a:fn, [a:job, a:data]) 1184 | endfunction 1185 | 1186 | function! s:nvim_cb(job_id, data, event) dict abort 1187 | return a:event == 'stdout' ? 1188 | \ s:job_cb('s:job_out_cb', self, 0, join(a:data, "\n")) : 1189 | \ s:job_cb('s:job_exit_cb', self, 0, a:data) 1190 | endfunction 1191 | 1192 | function! s:spawn(name, cmd, opts) 1193 | let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [''], 1194 | \ 'batchfile': (s:is_win && (s:nvim || s:vim8)) ? tempname().'.bat' : '', 1195 | \ 'new': get(a:opts, 'new', 0) } 1196 | let s:jobs[a:name] = job 1197 | let cmd = has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd 1198 | if !empty(job.batchfile) 1199 | call writefile(['@echo off', cmd], job.batchfile) 1200 | let cmd = job.batchfile 1201 | endif 1202 | let argv = add(s:is_win ? ['cmd', '/c'] : ['sh', '-c'], cmd) 1203 | 1204 | if s:nvim 1205 | call extend(job, { 1206 | \ 'on_stdout': function('s:nvim_cb'), 1207 | \ 'on_exit': function('s:nvim_cb'), 1208 | \ }) 1209 | let jid = jobstart(argv, job) 1210 | if jid > 0 1211 | let job.jobid = jid 1212 | else 1213 | let job.running = 0 1214 | let job.error = 1 1215 | let job.lines = [jid < 0 ? argv[0].' is not executable' : 1216 | \ 'Invalid arguments (or job table is full)'] 1217 | endif 1218 | elseif s:vim8 1219 | let jid = job_start(s:is_win ? join(argv, ' ') : argv, { 1220 | \ 'out_cb': function('s:job_cb', ['s:job_out_cb', job]), 1221 | \ 'exit_cb': function('s:job_cb', ['s:job_exit_cb', job]), 1222 | \ 'out_mode': 'raw' 1223 | \}) 1224 | if job_status(jid) == 'run' 1225 | let job.jobid = jid 1226 | else 1227 | let job.running = 0 1228 | let job.error = 1 1229 | let job.lines = ['Failed to start job'] 1230 | endif 1231 | else 1232 | let job.lines = s:lines(call('s:system', [cmd])) 1233 | let job.error = v:shell_error != 0 1234 | let job.running = 0 1235 | endif 1236 | endfunction 1237 | 1238 | function! s:reap(name) 1239 | let job = s:jobs[a:name] 1240 | if job.error 1241 | call add(s:update.errors, a:name) 1242 | elseif get(job, 'new', 0) 1243 | let s:update.new[a:name] = 1 1244 | endif 1245 | let s:update.bar .= job.error ? 'x' : '=' 1246 | 1247 | let bullet = job.error ? 'x' : '-' 1248 | let result = job.error ? join(job.lines, "\n") : s:last_non_empty_line(job.lines) 1249 | call s:log(bullet, a:name, empty(result) ? 'OK' : result) 1250 | call s:bar() 1251 | 1252 | if has_key(job, 'batchfile') && !empty(job.batchfile) 1253 | call delete(job.batchfile) 1254 | endif 1255 | call remove(s:jobs, a:name) 1256 | endfunction 1257 | 1258 | function! s:bar() 1259 | if s:switch_in() 1260 | let total = len(s:update.all) 1261 | call setline(1, (s:update.pull ? 'Updating' : 'Installing'). 1262 | \ ' plugins ('.len(s:update.bar).'/'.total.')') 1263 | call s:progress_bar(2, s:update.bar, total) 1264 | call s:switch_out() 1265 | endif 1266 | endfunction 1267 | 1268 | function! s:logpos(name) 1269 | for i in range(4, line('$')) 1270 | if getline(i) =~# '^[-+x*] '.a:name.':' 1271 | for j in range(i + 1, line('$')) 1272 | if getline(j) !~ '^ ' 1273 | return [i, j - 1] 1274 | endif 1275 | endfor 1276 | return [i, i] 1277 | endif 1278 | endfor 1279 | return [0, 0] 1280 | endfunction 1281 | 1282 | function! s:log(bullet, name, lines) 1283 | if s:switch_in() 1284 | let [b, e] = s:logpos(a:name) 1285 | if b > 0 1286 | silent execute printf('%d,%d d _', b, e) 1287 | if b > winheight('.') 1288 | let b = 4 1289 | endif 1290 | else 1291 | let b = 4 1292 | endif 1293 | " FIXME For some reason, nomodifiable is set after :d in vim8 1294 | setlocal modifiable 1295 | call append(b - 1, s:format_message(a:bullet, a:name, a:lines)) 1296 | call s:switch_out() 1297 | endif 1298 | endfunction 1299 | 1300 | function! s:update_vim() 1301 | let s:jobs = {} 1302 | 1303 | call s:bar() 1304 | call s:tick() 1305 | endfunction 1306 | 1307 | function! s:tick() 1308 | let pull = s:update.pull 1309 | let prog = s:progress_opt(s:nvim || s:vim8) 1310 | while 1 " Without TCO, Vim stack is bound to explode 1311 | if empty(s:update.todo) 1312 | if empty(s:jobs) && !s:update.fin 1313 | call s:update_finish() 1314 | let s:update.fin = 1 1315 | endif 1316 | return 1317 | endif 1318 | 1319 | let name = keys(s:update.todo)[0] 1320 | let spec = remove(s:update.todo, name) 1321 | let new = !isdirectory(spec.dir) 1322 | 1323 | call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') 1324 | redraw 1325 | 1326 | let has_tag = has_key(spec, 'tag') 1327 | if !new 1328 | let [error, _] = s:git_validate(spec, 0) 1329 | if empty(error) 1330 | if pull 1331 | let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : '' 1332 | call s:spawn(name, printf('git fetch %s %s 2>&1', fetch_opt, prog), { 'dir': spec.dir }) 1333 | else 1334 | let s:jobs[name] = { 'running': 0, 'lines': ['Already installed'], 'error': 0 } 1335 | endif 1336 | else 1337 | let s:jobs[name] = { 'running': 0, 'lines': s:lines(error), 'error': 1 } 1338 | endif 1339 | else 1340 | call s:spawn(name, 1341 | \ printf('git clone %s %s %s %s 2>&1', 1342 | \ has_tag ? '' : s:clone_opt, 1343 | \ prog, 1344 | \ s:shellesc(spec.uri), 1345 | \ s:shellesc(s:trim(spec.dir))), { 'new': 1 }) 1346 | endif 1347 | 1348 | if !s:jobs[name].running 1349 | call s:reap(name) 1350 | endif 1351 | if len(s:jobs) >= s:update.threads 1352 | break 1353 | endif 1354 | endwhile 1355 | endfunction 1356 | 1357 | function! s:update_python() 1358 | let py_exe = has('python') ? 'python' : 'python3' 1359 | execute py_exe "<< EOF" 1360 | import datetime 1361 | import functools 1362 | import os 1363 | try: 1364 | import queue 1365 | except ImportError: 1366 | import Queue as queue 1367 | import random 1368 | import re 1369 | import shutil 1370 | import signal 1371 | import subprocess 1372 | import tempfile 1373 | import threading as thr 1374 | import time 1375 | import traceback 1376 | import vim 1377 | 1378 | G_NVIM = vim.eval("has('nvim')") == '1' 1379 | G_PULL = vim.eval('s:update.pull') == '1' 1380 | G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 1381 | G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) 1382 | G_CLONE_OPT = vim.eval('s:clone_opt') 1383 | G_PROGRESS = vim.eval('s:progress_opt(1)') 1384 | G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) 1385 | G_STOP = thr.Event() 1386 | G_IS_WIN = vim.eval('s:is_win') == '1' 1387 | 1388 | class PlugError(Exception): 1389 | def __init__(self, msg): 1390 | self.msg = msg 1391 | class CmdTimedOut(PlugError): 1392 | pass 1393 | class CmdFailed(PlugError): 1394 | pass 1395 | class InvalidURI(PlugError): 1396 | pass 1397 | class Action(object): 1398 | INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] 1399 | 1400 | class Buffer(object): 1401 | def __init__(self, lock, num_plugs, is_pull): 1402 | self.bar = '' 1403 | self.event = 'Updating' if is_pull else 'Installing' 1404 | self.lock = lock 1405 | self.maxy = int(vim.eval('winheight(".")')) 1406 | self.num_plugs = num_plugs 1407 | 1408 | def __where(self, name): 1409 | """ Find first line with name in current buffer. Return line num. """ 1410 | found, lnum = False, 0 1411 | matcher = re.compile('^[-+x*] {0}:'.format(name)) 1412 | for line in vim.current.buffer: 1413 | if matcher.search(line) is not None: 1414 | found = True 1415 | break 1416 | lnum += 1 1417 | 1418 | if not found: 1419 | lnum = -1 1420 | return lnum 1421 | 1422 | def header(self): 1423 | curbuf = vim.current.buffer 1424 | curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) 1425 | 1426 | num_spaces = self.num_plugs - len(self.bar) 1427 | curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') 1428 | 1429 | with self.lock: 1430 | vim.command('normal! 2G') 1431 | vim.command('redraw') 1432 | 1433 | def write(self, action, name, lines): 1434 | first, rest = lines[0], lines[1:] 1435 | msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] 1436 | msg.extend([' ' + line for line in rest]) 1437 | 1438 | try: 1439 | if action == Action.ERROR: 1440 | self.bar += 'x' 1441 | vim.command("call add(s:update.errors, '{0}')".format(name)) 1442 | elif action == Action.DONE: 1443 | self.bar += '=' 1444 | 1445 | curbuf = vim.current.buffer 1446 | lnum = self.__where(name) 1447 | if lnum != -1: # Found matching line num 1448 | del curbuf[lnum] 1449 | if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): 1450 | lnum = 3 1451 | else: 1452 | lnum = 3 1453 | curbuf.append(msg, lnum) 1454 | 1455 | self.header() 1456 | except vim.error: 1457 | pass 1458 | 1459 | class Command(object): 1460 | CD = 'cd /d' if G_IS_WIN else 'cd' 1461 | 1462 | def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): 1463 | self.cmd = cmd 1464 | if cmd_dir: 1465 | self.cmd = '{0} {1} && {2}'.format(Command.CD, cmd_dir, self.cmd) 1466 | self.timeout = timeout 1467 | self.callback = cb if cb else (lambda msg: None) 1468 | self.clean = clean if clean else (lambda: None) 1469 | self.proc = None 1470 | 1471 | @property 1472 | def alive(self): 1473 | """ Returns true only if command still running. """ 1474 | return self.proc and self.proc.poll() is None 1475 | 1476 | def execute(self, ntries=3): 1477 | """ Execute the command with ntries if CmdTimedOut. 1478 | Returns the output of the command if no Exception. 1479 | """ 1480 | attempt, finished, limit = 0, False, self.timeout 1481 | 1482 | while not finished: 1483 | try: 1484 | attempt += 1 1485 | result = self.try_command() 1486 | finished = True 1487 | return result 1488 | except CmdTimedOut: 1489 | if attempt != ntries: 1490 | self.notify_retry() 1491 | self.timeout += limit 1492 | else: 1493 | raise 1494 | 1495 | def notify_retry(self): 1496 | """ Retry required for command, notify user. """ 1497 | for count in range(3, 0, -1): 1498 | if G_STOP.is_set(): 1499 | raise KeyboardInterrupt 1500 | msg = 'Timeout. Will retry in {0} second{1} ...'.format( 1501 | count, 's' if count != 1 else '') 1502 | self.callback([msg]) 1503 | time.sleep(1) 1504 | self.callback(['Retrying ...']) 1505 | 1506 | def try_command(self): 1507 | """ Execute a cmd & poll for callback. Returns list of output. 1508 | Raises CmdFailed -> return code for Popen isn't 0 1509 | Raises CmdTimedOut -> command exceeded timeout without new output 1510 | """ 1511 | first_line = True 1512 | 1513 | try: 1514 | tfile = tempfile.NamedTemporaryFile(mode='w+b') 1515 | preexec_fn = not G_IS_WIN and os.setsid or None 1516 | self.proc = subprocess.Popen(self.cmd, stdout=tfile, 1517 | stderr=subprocess.STDOUT, 1518 | stdin=subprocess.PIPE, shell=True, 1519 | preexec_fn=preexec_fn) 1520 | thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) 1521 | thrd.start() 1522 | 1523 | thread_not_started = True 1524 | while thread_not_started: 1525 | try: 1526 | thrd.join(0.1) 1527 | thread_not_started = False 1528 | except RuntimeError: 1529 | pass 1530 | 1531 | while self.alive: 1532 | if G_STOP.is_set(): 1533 | raise KeyboardInterrupt 1534 | 1535 | if first_line or random.random() < G_LOG_PROB: 1536 | first_line = False 1537 | line = '' if G_IS_WIN else nonblock_read(tfile.name) 1538 | if line: 1539 | self.callback([line]) 1540 | 1541 | time_diff = time.time() - os.path.getmtime(tfile.name) 1542 | if time_diff > self.timeout: 1543 | raise CmdTimedOut(['Timeout!']) 1544 | 1545 | thrd.join(0.5) 1546 | 1547 | tfile.seek(0) 1548 | result = [line.decode('utf-8', 'replace').rstrip() for line in tfile] 1549 | 1550 | if self.proc.returncode != 0: 1551 | raise CmdFailed([''] + result) 1552 | 1553 | return result 1554 | except: 1555 | self.terminate() 1556 | raise 1557 | 1558 | def terminate(self): 1559 | """ Terminate process and cleanup. """ 1560 | if self.alive: 1561 | if G_IS_WIN: 1562 | os.kill(self.proc.pid, signal.SIGINT) 1563 | else: 1564 | os.killpg(self.proc.pid, signal.SIGTERM) 1565 | self.clean() 1566 | 1567 | class Plugin(object): 1568 | def __init__(self, name, args, buf_q, lock): 1569 | self.name = name 1570 | self.args = args 1571 | self.buf_q = buf_q 1572 | self.lock = lock 1573 | self.tag = args.get('tag', 0) 1574 | 1575 | def manage(self): 1576 | try: 1577 | if os.path.exists(self.args['dir']): 1578 | self.update() 1579 | else: 1580 | self.install() 1581 | with self.lock: 1582 | thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) 1583 | except PlugError as exc: 1584 | self.write(Action.ERROR, self.name, exc.msg) 1585 | except KeyboardInterrupt: 1586 | G_STOP.set() 1587 | self.write(Action.ERROR, self.name, ['Interrupted!']) 1588 | except: 1589 | # Any exception except those above print stack trace 1590 | msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) 1591 | self.write(Action.ERROR, self.name, msg.split('\n')) 1592 | raise 1593 | 1594 | def install(self): 1595 | target = self.args['dir'] 1596 | if target[-1] == '\\': 1597 | target = target[0:-1] 1598 | 1599 | def clean(target): 1600 | def _clean(): 1601 | try: 1602 | shutil.rmtree(target) 1603 | except OSError: 1604 | pass 1605 | return _clean 1606 | 1607 | self.write(Action.INSTALL, self.name, ['Installing ...']) 1608 | callback = functools.partial(self.write, Action.INSTALL, self.name) 1609 | cmd = 'git clone {0} {1} {2} {3} 2>&1'.format( 1610 | '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], 1611 | esc(target)) 1612 | com = Command(cmd, None, G_TIMEOUT, callback, clean(target)) 1613 | result = com.execute(G_RETRIES) 1614 | self.write(Action.DONE, self.name, result[-1:]) 1615 | 1616 | def repo_uri(self): 1617 | cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url' 1618 | command = Command(cmd, self.args['dir'], G_TIMEOUT,) 1619 | result = command.execute(G_RETRIES) 1620 | return result[-1] 1621 | 1622 | def update(self): 1623 | actual_uri = self.repo_uri() 1624 | expect_uri = self.args['uri'] 1625 | regex = re.compile(r'^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$') 1626 | ma = regex.match(actual_uri) 1627 | mb = regex.match(expect_uri) 1628 | if ma is None or mb is None or ma.groups() != mb.groups(): 1629 | msg = ['', 1630 | 'Invalid URI: {0}'.format(actual_uri), 1631 | 'Expected {0}'.format(expect_uri), 1632 | 'PlugClean required.'] 1633 | raise InvalidURI(msg) 1634 | 1635 | if G_PULL: 1636 | self.write(Action.UPDATE, self.name, ['Updating ...']) 1637 | callback = functools.partial(self.write, Action.UPDATE, self.name) 1638 | fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' 1639 | cmd = 'git fetch {0} {1} 2>&1'.format(fetch_opt, G_PROGRESS) 1640 | com = Command(cmd, self.args['dir'], G_TIMEOUT, callback) 1641 | result = com.execute(G_RETRIES) 1642 | self.write(Action.DONE, self.name, result[-1:]) 1643 | else: 1644 | self.write(Action.DONE, self.name, ['Already installed']) 1645 | 1646 | def write(self, action, name, msg): 1647 | self.buf_q.put((action, name, msg)) 1648 | 1649 | class PlugThread(thr.Thread): 1650 | def __init__(self, tname, args): 1651 | super(PlugThread, self).__init__() 1652 | self.tname = tname 1653 | self.args = args 1654 | 1655 | def run(self): 1656 | thr.current_thread().name = self.tname 1657 | buf_q, work_q, lock = self.args 1658 | 1659 | try: 1660 | while not G_STOP.is_set(): 1661 | name, args = work_q.get_nowait() 1662 | plug = Plugin(name, args, buf_q, lock) 1663 | plug.manage() 1664 | work_q.task_done() 1665 | except queue.Empty: 1666 | pass 1667 | 1668 | class RefreshThread(thr.Thread): 1669 | def __init__(self, lock): 1670 | super(RefreshThread, self).__init__() 1671 | self.lock = lock 1672 | self.running = True 1673 | 1674 | def run(self): 1675 | while self.running: 1676 | with self.lock: 1677 | thread_vim_command('noautocmd normal! a') 1678 | time.sleep(0.33) 1679 | 1680 | def stop(self): 1681 | self.running = False 1682 | 1683 | if G_NVIM: 1684 | def thread_vim_command(cmd): 1685 | vim.session.threadsafe_call(lambda: vim.command(cmd)) 1686 | else: 1687 | def thread_vim_command(cmd): 1688 | vim.command(cmd) 1689 | 1690 | def esc(name): 1691 | return '"' + name.replace('"', '\"') + '"' 1692 | 1693 | def nonblock_read(fname): 1694 | """ Read a file with nonblock flag. Return the last line. """ 1695 | fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) 1696 | buf = os.read(fread, 100000).decode('utf-8', 'replace') 1697 | os.close(fread) 1698 | 1699 | line = buf.rstrip('\r\n') 1700 | left = max(line.rfind('\r'), line.rfind('\n')) 1701 | if left != -1: 1702 | left += 1 1703 | line = line[left:] 1704 | 1705 | return line 1706 | 1707 | def main(): 1708 | thr.current_thread().name = 'main' 1709 | nthreads = int(vim.eval('s:update.threads')) 1710 | plugs = vim.eval('s:update.todo') 1711 | mac_gui = vim.eval('s:mac_gui') == '1' 1712 | 1713 | lock = thr.Lock() 1714 | buf = Buffer(lock, len(plugs), G_PULL) 1715 | buf_q, work_q = queue.Queue(), queue.Queue() 1716 | for work in plugs.items(): 1717 | work_q.put(work) 1718 | 1719 | start_cnt = thr.active_count() 1720 | for num in range(nthreads): 1721 | tname = 'PlugT-{0:02}'.format(num) 1722 | thread = PlugThread(tname, (buf_q, work_q, lock)) 1723 | thread.start() 1724 | if mac_gui: 1725 | rthread = RefreshThread(lock) 1726 | rthread.start() 1727 | 1728 | while not buf_q.empty() or thr.active_count() != start_cnt: 1729 | try: 1730 | action, name, msg = buf_q.get(True, 0.25) 1731 | buf.write(action, name, ['OK'] if not msg else msg) 1732 | buf_q.task_done() 1733 | except queue.Empty: 1734 | pass 1735 | except KeyboardInterrupt: 1736 | G_STOP.set() 1737 | 1738 | if mac_gui: 1739 | rthread.stop() 1740 | rthread.join() 1741 | 1742 | main() 1743 | EOF 1744 | endfunction 1745 | 1746 | function! s:update_ruby() 1747 | ruby << EOF 1748 | module PlugStream 1749 | SEP = ["\r", "\n", nil] 1750 | def get_line 1751 | buffer = '' 1752 | loop do 1753 | char = readchar rescue return 1754 | if SEP.include? char.chr 1755 | buffer << $/ 1756 | break 1757 | else 1758 | buffer << char 1759 | end 1760 | end 1761 | buffer 1762 | end 1763 | end unless defined?(PlugStream) 1764 | 1765 | def esc arg 1766 | %["#{arg.gsub('"', '\"')}"] 1767 | end 1768 | 1769 | def killall pid 1770 | pids = [pid] 1771 | if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM 1772 | pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } 1773 | else 1774 | unless `which pgrep 2> /dev/null`.empty? 1775 | children = pids 1776 | until children.empty? 1777 | children = children.map { |pid| 1778 | `pgrep -P #{pid}`.lines.map { |l| l.chomp } 1779 | }.flatten 1780 | pids += children 1781 | end 1782 | end 1783 | pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } 1784 | end 1785 | end 1786 | 1787 | def compare_git_uri a, b 1788 | regex = %r{^(?:\w+://)?(?:[^@/]*@)?([^:/]*(?::[0-9]*)?)[:/](.*?)(?:\.git)?/?$} 1789 | regex.match(a).to_a.drop(1) == regex.match(b).to_a.drop(1) 1790 | end 1791 | 1792 | require 'thread' 1793 | require 'fileutils' 1794 | require 'timeout' 1795 | running = true 1796 | iswin = VIM::evaluate('s:is_win').to_i == 1 1797 | pull = VIM::evaluate('s:update.pull').to_i == 1 1798 | base = VIM::evaluate('g:plug_home') 1799 | all = VIM::evaluate('s:update.todo') 1800 | limit = VIM::evaluate('get(g:, "plug_timeout", 60)') 1801 | tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1 1802 | nthr = VIM::evaluate('s:update.threads').to_i 1803 | maxy = VIM::evaluate('winheight(".")').to_i 1804 | vim7 = VIM::evaluate('v:version').to_i <= 703 && RUBY_PLATFORM =~ /darwin/ 1805 | cd = iswin ? 'cd /d' : 'cd' 1806 | tot = VIM::evaluate('len(s:update.todo)') || 0 1807 | bar = '' 1808 | skip = 'Already installed' 1809 | mtx = Mutex.new 1810 | take1 = proc { mtx.synchronize { running && all.shift } } 1811 | logh = proc { 1812 | cnt = bar.length 1813 | $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" 1814 | $curbuf[2] = '[' + bar.ljust(tot) + ']' 1815 | VIM::command('normal! 2G') 1816 | VIM::command('redraw') 1817 | } 1818 | where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } 1819 | log = proc { |name, result, type| 1820 | mtx.synchronize do 1821 | ing = ![true, false].include?(type) 1822 | bar += type ? '=' : 'x' unless ing 1823 | b = case type 1824 | when :install then '+' when :update then '*' 1825 | when true, nil then '-' else 1826 | VIM::command("call add(s:update.errors, '#{name}')") 1827 | 'x' 1828 | end 1829 | result = 1830 | if type || type.nil? 1831 | ["#{b} #{name}: #{result.lines.to_a.last || 'OK'}"] 1832 | elsif result =~ /^Interrupted|^Timeout/ 1833 | ["#{b} #{name}: #{result}"] 1834 | else 1835 | ["#{b} #{name}"] + result.lines.map { |l| " " << l } 1836 | end 1837 | if lnum = where.call(name) 1838 | $curbuf.delete lnum 1839 | lnum = 4 if ing && lnum > maxy 1840 | end 1841 | result.each_with_index do |line, offset| 1842 | $curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp) 1843 | end 1844 | logh.call 1845 | end 1846 | } 1847 | bt = proc { |cmd, name, type, cleanup| 1848 | tried = timeout = 0 1849 | begin 1850 | tried += 1 1851 | timeout += limit 1852 | fd = nil 1853 | data = '' 1854 | if iswin 1855 | Timeout::timeout(timeout) do 1856 | tmp = VIM::evaluate('tempname()') 1857 | system("(#{cmd}) > #{tmp}") 1858 | data = File.read(tmp).chomp 1859 | File.unlink tmp rescue nil 1860 | end 1861 | else 1862 | fd = IO.popen(cmd).extend(PlugStream) 1863 | first_line = true 1864 | log_prob = 1.0 / nthr 1865 | while line = Timeout::timeout(timeout) { fd.get_line } 1866 | data << line 1867 | log.call name, line.chomp, type if name && (first_line || rand < log_prob) 1868 | first_line = false 1869 | end 1870 | fd.close 1871 | end 1872 | [$? == 0, data.chomp] 1873 | rescue Timeout::Error, Interrupt => e 1874 | if fd && !fd.closed? 1875 | killall fd.pid 1876 | fd.close 1877 | end 1878 | cleanup.call if cleanup 1879 | if e.is_a?(Timeout::Error) && tried < tries 1880 | 3.downto(1) do |countdown| 1881 | s = countdown > 1 ? 's' : '' 1882 | log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type 1883 | sleep 1 1884 | end 1885 | log.call name, 'Retrying ...', type 1886 | retry 1887 | end 1888 | [false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"] 1889 | end 1890 | } 1891 | main = Thread.current 1892 | threads = [] 1893 | watcher = Thread.new { 1894 | if vim7 1895 | while VIM::evaluate('getchar(1)') 1896 | sleep 0.1 1897 | end 1898 | else 1899 | require 'io/console' # >= Ruby 1.9 1900 | nil until IO.console.getch == 3.chr 1901 | end 1902 | mtx.synchronize do 1903 | running = false 1904 | threads.each { |t| t.raise Interrupt } unless vim7 1905 | end 1906 | threads.each { |t| t.join rescue nil } 1907 | main.kill 1908 | } 1909 | refresh = Thread.new { 1910 | while true 1911 | mtx.synchronize do 1912 | break unless running 1913 | VIM::command('noautocmd normal! a') 1914 | end 1915 | sleep 0.2 1916 | end 1917 | } if VIM::evaluate('s:mac_gui') == 1 1918 | 1919 | clone_opt = VIM::evaluate('s:clone_opt') 1920 | progress = VIM::evaluate('s:progress_opt(1)') 1921 | nthr.times do 1922 | mtx.synchronize do 1923 | threads << Thread.new { 1924 | while pair = take1.call 1925 | name = pair.first 1926 | dir, uri, tag = pair.last.values_at *%w[dir uri tag] 1927 | exists = File.directory? dir 1928 | ok, result = 1929 | if exists 1930 | chdir = "#{cd} #{iswin ? dir : esc(dir)}" 1931 | ret, data = bt.call "#{chdir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config -f .git/config remote.origin.url", nil, nil, nil 1932 | current_uri = data.lines.to_a.last 1933 | if !ret 1934 | if data =~ /^Interrupted|^Timeout/ 1935 | [false, data] 1936 | else 1937 | [false, [data.chomp, "PlugClean required."].join($/)] 1938 | end 1939 | elsif !compare_git_uri(current_uri, uri) 1940 | [false, ["Invalid URI: #{current_uri}", 1941 | "Expected: #{uri}", 1942 | "PlugClean required."].join($/)] 1943 | else 1944 | if pull 1945 | log.call name, 'Updating ...', :update 1946 | fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' 1947 | bt.call "#{chdir} && git fetch #{fetch_opt} #{progress} 2>&1", name, :update, nil 1948 | else 1949 | [true, skip] 1950 | end 1951 | end 1952 | else 1953 | d = esc dir.sub(%r{[\\/]+$}, '') 1954 | log.call name, 'Installing ...', :install 1955 | bt.call "git clone #{clone_opt unless tag} #{progress} #{uri} #{d} 2>&1", name, :install, proc { 1956 | FileUtils.rm_rf dir 1957 | } 1958 | end 1959 | mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok 1960 | log.call name, result, ok 1961 | end 1962 | } if running 1963 | end 1964 | end 1965 | threads.each { |t| t.join rescue nil } 1966 | logh.call 1967 | refresh.kill if refresh 1968 | watcher.kill 1969 | EOF 1970 | endfunction 1971 | 1972 | function! s:shellesc_cmd(arg) 1973 | let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g') 1974 | let escaped = substitute(escaped, '%', '%%', 'g') 1975 | let escaped = substitute(escaped, '"', '\\^&', 'g') 1976 | let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g') 1977 | return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"' 1978 | endfunction 1979 | 1980 | function! s:shellesc(arg) 1981 | if &shell =~# 'cmd.exe$' 1982 | return s:shellesc_cmd(a:arg) 1983 | endif 1984 | return shellescape(a:arg) 1985 | endfunction 1986 | 1987 | function! s:glob_dir(path) 1988 | return map(filter(s:glob(a:path, '**'), 'isdirectory(v:val)'), 's:dirpath(v:val)') 1989 | endfunction 1990 | 1991 | function! s:progress_bar(line, bar, total) 1992 | call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']') 1993 | endfunction 1994 | 1995 | function! s:compare_git_uri(a, b) 1996 | " See `git help clone' 1997 | " https:// [user@] github.com[:port] / junegunn/vim-plug [.git] 1998 | " [git@] github.com[:port] : junegunn/vim-plug [.git] 1999 | " file:// / junegunn/vim-plug [/] 2000 | " / junegunn/vim-plug [/] 2001 | let pat = '^\%(\w\+://\)\='.'\%([^@/]*@\)\='.'\([^:/]*\%(:[0-9]*\)\=\)'.'[:/]'.'\(.\{-}\)'.'\%(\.git\)\=/\?$' 2002 | let ma = matchlist(a:a, pat) 2003 | let mb = matchlist(a:b, pat) 2004 | return ma[1:2] ==# mb[1:2] 2005 | endfunction 2006 | 2007 | function! s:format_message(bullet, name, message) 2008 | if a:bullet != 'x' 2009 | return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))] 2010 | else 2011 | let lines = map(s:lines(a:message), '" ".v:val') 2012 | return extend([printf('x %s:', a:name)], lines) 2013 | endif 2014 | endfunction 2015 | 2016 | function! s:with_cd(cmd, dir) 2017 | return printf('cd%s %s && %s', s:is_win ? ' /d' : '', s:shellesc(a:dir), a:cmd) 2018 | endfunction 2019 | 2020 | function! s:system(cmd, ...) 2021 | try 2022 | let [sh, shellcmdflag, shrd] = s:chsh(1) 2023 | let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd 2024 | if s:is_win 2025 | let batchfile = tempname().'.bat' 2026 | call writefile(['@echo off', cmd], batchfile) 2027 | let cmd = batchfile 2028 | endif 2029 | return system(s:is_win ? '('.cmd.')' : cmd) 2030 | finally 2031 | let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] 2032 | if s:is_win 2033 | call delete(batchfile) 2034 | endif 2035 | endtry 2036 | endfunction 2037 | 2038 | function! s:system_chomp(...) 2039 | let ret = call('s:system', a:000) 2040 | return v:shell_error ? '' : substitute(ret, '\n$', '', '') 2041 | endfunction 2042 | 2043 | function! s:git_validate(spec, check_branch) 2044 | let err = '' 2045 | if isdirectory(a:spec.dir) 2046 | 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)) 2047 | let remote = result[-1] 2048 | if v:shell_error 2049 | let err = join([remote, 'PlugClean required.'], "\n") 2050 | elseif !s:compare_git_uri(remote, a:spec.uri) 2051 | let err = join(['Invalid URI: '.remote, 2052 | \ 'Expected: '.a:spec.uri, 2053 | \ 'PlugClean required.'], "\n") 2054 | elseif a:check_branch && has_key(a:spec, 'commit') 2055 | let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir)) 2056 | let sha = result[-1] 2057 | if v:shell_error 2058 | let err = join(add(result, 'PlugClean required.'), "\n") 2059 | elseif !s:hash_match(sha, a:spec.commit) 2060 | let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', 2061 | \ a:spec.commit[:6], sha[:6]), 2062 | \ 'PlugUpdate required.'], "\n") 2063 | endif 2064 | elseif a:check_branch 2065 | let branch = result[0] 2066 | " Check tag 2067 | if has_key(a:spec, 'tag') 2068 | let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) 2069 | if a:spec.tag !=# tag && a:spec.tag !~ '\*' 2070 | let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', 2071 | \ (empty(tag) ? 'N/A' : tag), a:spec.tag) 2072 | endif 2073 | " Check branch 2074 | elseif a:spec.branch !=# branch 2075 | let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', 2076 | \ branch, a:spec.branch) 2077 | endif 2078 | if empty(err) 2079 | let [ahead, behind] = split(s:lastline(s:system(printf( 2080 | \ 'git rev-list --count --left-right HEAD...origin/%s', 2081 | \ a:spec.branch), a:spec.dir)), '\t') 2082 | if !v:shell_error && ahead 2083 | if behind 2084 | " Only mention PlugClean if diverged, otherwise it's likely to be 2085 | " pushable (and probably not that messed up). 2086 | let err = printf( 2087 | \ "Diverged from origin/%s (%d commit(s) ahead and %d commit(s) behind!\n" 2088 | \ .'Backup local changes and run PlugClean and PlugUpdate to reinstall it.', a:spec.branch, ahead, behind) 2089 | else 2090 | let err = printf("Ahead of origin/%s by %d commit(s).\n" 2091 | \ .'Cannot update until local changes are pushed.', 2092 | \ a:spec.branch, ahead) 2093 | endif 2094 | endif 2095 | endif 2096 | endif 2097 | else 2098 | let err = 'Not found' 2099 | endif 2100 | return [err, err =~# 'PlugClean'] 2101 | endfunction 2102 | 2103 | function! s:rm_rf(dir) 2104 | if isdirectory(a:dir) 2105 | call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(a:dir)) 2106 | endif 2107 | endfunction 2108 | 2109 | function! s:clean(force) 2110 | call s:prepare() 2111 | call append(0, 'Searching for invalid plugins in '.g:plug_home) 2112 | call append(1, '') 2113 | 2114 | " List of valid directories 2115 | let dirs = [] 2116 | let errs = {} 2117 | let [cnt, total] = [0, len(g:plugs)] 2118 | for [name, spec] in items(g:plugs) 2119 | if !s:is_managed(name) 2120 | call add(dirs, spec.dir) 2121 | else 2122 | let [err, clean] = s:git_validate(spec, 1) 2123 | if clean 2124 | let errs[spec.dir] = s:lines(err)[0] 2125 | else 2126 | call add(dirs, spec.dir) 2127 | endif 2128 | endif 2129 | let cnt += 1 2130 | call s:progress_bar(2, repeat('=', cnt), total) 2131 | normal! 2G 2132 | redraw 2133 | endfor 2134 | 2135 | let allowed = {} 2136 | for dir in dirs 2137 | let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1 2138 | let allowed[dir] = 1 2139 | for child in s:glob_dir(dir) 2140 | let allowed[child] = 1 2141 | endfor 2142 | endfor 2143 | 2144 | let todo = [] 2145 | let found = sort(s:glob_dir(g:plug_home)) 2146 | while !empty(found) 2147 | let f = remove(found, 0) 2148 | if !has_key(allowed, f) && isdirectory(f) 2149 | call add(todo, f) 2150 | call append(line('$'), '- ' . f) 2151 | if has_key(errs, f) 2152 | call append(line('$'), ' ' . errs[f]) 2153 | endif 2154 | let found = filter(found, 'stridx(v:val, f) != 0') 2155 | end 2156 | endwhile 2157 | 2158 | 4 2159 | redraw 2160 | if empty(todo) 2161 | call append(line('$'), 'Already clean.') 2162 | else 2163 | let s:clean_count = 0 2164 | call append(3, ['Directories to delete:', '']) 2165 | redraw! 2166 | if a:force || s:ask_no_interrupt('Delete all directories?') 2167 | call s:delete([6, line('$')], 1) 2168 | else 2169 | call setline(4, 'Cancelled.') 2170 | nnoremap d :set opfunc=delete_opg@ 2171 | nmap dd d_ 2172 | xnoremap d :call delete_op(visualmode(), 1) 2173 | echo 'Delete the lines (d{motion}) to delete the corresponding directories' 2174 | endif 2175 | endif 2176 | 4 2177 | setlocal nomodifiable 2178 | endfunction 2179 | 2180 | function! s:delete_op(type, ...) 2181 | call s:delete(a:0 ? [line("'<"), line("'>")] : [line("'["), line("']")], 0) 2182 | endfunction 2183 | 2184 | function! s:delete(range, force) 2185 | let [l1, l2] = a:range 2186 | let force = a:force 2187 | while l1 <= l2 2188 | let line = getline(l1) 2189 | if line =~ '^- ' && isdirectory(line[2:]) 2190 | execute l1 2191 | redraw! 2192 | let answer = force ? 1 : s:ask('Delete '.line[2:].'?', 1) 2193 | let force = force || answer > 1 2194 | if answer 2195 | call s:rm_rf(line[2:]) 2196 | setlocal modifiable 2197 | call setline(l1, '~'.line[1:]) 2198 | let s:clean_count += 1 2199 | call setline(4, printf('Removed %d directories.', s:clean_count)) 2200 | setlocal nomodifiable 2201 | endif 2202 | endif 2203 | let l1 += 1 2204 | endwhile 2205 | endfunction 2206 | 2207 | function! s:upgrade() 2208 | echo 'Downloading the latest version of vim-plug' 2209 | redraw 2210 | let tmp = tempname() 2211 | let new = tmp . '/plug.vim' 2212 | 2213 | try 2214 | let out = s:system(printf('git clone --depth 1 %s %s', s:plug_src, tmp)) 2215 | if v:shell_error 2216 | return s:err('Error upgrading vim-plug: '. out) 2217 | endif 2218 | 2219 | if readfile(s:me) ==# readfile(new) 2220 | echo 'vim-plug is already up-to-date' 2221 | return 0 2222 | else 2223 | call rename(s:me, s:me . '.old') 2224 | call rename(new, s:me) 2225 | unlet g:loaded_plug 2226 | echo 'vim-plug has been upgraded' 2227 | return 1 2228 | endif 2229 | finally 2230 | silent! call s:rm_rf(tmp) 2231 | endtry 2232 | endfunction 2233 | 2234 | function! s:upgrade_specs() 2235 | for spec in values(g:plugs) 2236 | let spec.frozen = get(spec, 'frozen', 0) 2237 | endfor 2238 | endfunction 2239 | 2240 | function! s:status() 2241 | call s:prepare() 2242 | call append(0, 'Checking plugins') 2243 | call append(1, '') 2244 | 2245 | let ecnt = 0 2246 | let unloaded = 0 2247 | let [cnt, total] = [0, len(g:plugs)] 2248 | for [name, spec] in items(g:plugs) 2249 | let is_dir = isdirectory(spec.dir) 2250 | if has_key(spec, 'uri') 2251 | if is_dir 2252 | let [err, _] = s:git_validate(spec, 1) 2253 | let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] 2254 | else 2255 | let [valid, msg] = [0, 'Not found. Try PlugInstall.'] 2256 | endif 2257 | else 2258 | if is_dir 2259 | let [valid, msg] = [1, 'OK'] 2260 | else 2261 | let [valid, msg] = [0, 'Not found.'] 2262 | endif 2263 | endif 2264 | let cnt += 1 2265 | let ecnt += !valid 2266 | " `s:loaded` entry can be missing if PlugUpgraded 2267 | if is_dir && get(s:loaded, name, -1) == 0 2268 | let unloaded = 1 2269 | let msg .= ' (not loaded)' 2270 | endif 2271 | call s:progress_bar(2, repeat('=', cnt), total) 2272 | call append(3, s:format_message(valid ? '-' : 'x', name, msg)) 2273 | normal! 2G 2274 | redraw 2275 | endfor 2276 | call setline(1, 'Finished. '.ecnt.' error(s).') 2277 | normal! gg 2278 | setlocal nomodifiable 2279 | if unloaded 2280 | echo "Press 'L' on each line to load plugin, or 'U' to update" 2281 | nnoremap L :call status_load(line('.')) 2282 | xnoremap L :call status_load(line('.')) 2283 | end 2284 | endfunction 2285 | 2286 | function! s:extract_name(str, prefix, suffix) 2287 | return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$') 2288 | endfunction 2289 | 2290 | function! s:status_load(lnum) 2291 | let line = getline(a:lnum) 2292 | let name = s:extract_name(line, '-', '(not loaded)') 2293 | if !empty(name) 2294 | call plug#load(name) 2295 | setlocal modifiable 2296 | call setline(a:lnum, substitute(line, ' (not loaded)$', '', '')) 2297 | setlocal nomodifiable 2298 | endif 2299 | endfunction 2300 | 2301 | function! s:status_update() range 2302 | let lines = getline(a:firstline, a:lastline) 2303 | let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)') 2304 | if !empty(names) 2305 | echo 2306 | execute 'PlugUpdate' join(names) 2307 | endif 2308 | endfunction 2309 | 2310 | function! s:is_preview_window_open() 2311 | silent! wincmd P 2312 | if &previewwindow 2313 | wincmd p 2314 | return 1 2315 | endif 2316 | endfunction 2317 | 2318 | function! s:find_name(lnum) 2319 | for lnum in reverse(range(1, a:lnum)) 2320 | let line = getline(lnum) 2321 | if empty(line) 2322 | return '' 2323 | endif 2324 | let name = s:extract_name(line, '-', '') 2325 | if !empty(name) 2326 | return name 2327 | endif 2328 | endfor 2329 | return '' 2330 | endfunction 2331 | 2332 | function! s:preview_commit() 2333 | if b:plug_preview < 0 2334 | let b:plug_preview = !s:is_preview_window_open() 2335 | endif 2336 | 2337 | let sha = matchstr(getline('.'), '^ \X*\zs[0-9a-f]\{7,9}') 2338 | if empty(sha) 2339 | return 2340 | endif 2341 | 2342 | let name = s:find_name(line('.')) 2343 | if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir) 2344 | return 2345 | endif 2346 | 2347 | if exists('g:plug_pwindow') && !s:is_preview_window_open() 2348 | execute g:plug_pwindow 2349 | execute 'e' sha 2350 | else 2351 | execute 'pedit' sha 2352 | wincmd P 2353 | endif 2354 | setlocal previewwindow filetype=git buftype=nofile nobuflisted modifiable 2355 | try 2356 | let [sh, shellcmdflag, shrd] = s:chsh(1) 2357 | let cmd = 'cd '.s:shellesc(g:plugs[name].dir).' && git show --no-color --pretty=medium '.sha 2358 | if s:is_win 2359 | let batchfile = tempname().'.bat' 2360 | call writefile(['@echo off', cmd], batchfile) 2361 | let cmd = batchfile 2362 | endif 2363 | execute 'silent %!' cmd 2364 | finally 2365 | let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] 2366 | if s:is_win 2367 | call delete(batchfile) 2368 | endif 2369 | endtry 2370 | setlocal nomodifiable 2371 | nnoremap q :q 2372 | wincmd p 2373 | endfunction 2374 | 2375 | function! s:section(flags) 2376 | call search('\(^[x-] \)\@<=[^:]\+:', a:flags) 2377 | endfunction 2378 | 2379 | function! s:format_git_log(line) 2380 | let indent = ' ' 2381 | let tokens = split(a:line, nr2char(1)) 2382 | if len(tokens) != 5 2383 | return indent.substitute(a:line, '\s*$', '', '') 2384 | endif 2385 | let [graph, sha, refs, subject, date] = tokens 2386 | let tag = matchstr(refs, 'tag: [^,)]\+') 2387 | let tag = empty(tag) ? ' ' : ' ('.tag.') ' 2388 | return printf('%s%s%s%s%s (%s)', indent, graph, sha, tag, subject, date) 2389 | endfunction 2390 | 2391 | function! s:append_ul(lnum, text) 2392 | call append(a:lnum, ['', a:text, repeat('-', len(a:text))]) 2393 | endfunction 2394 | 2395 | function! s:diff() 2396 | call s:prepare() 2397 | call append(0, ['Collecting changes ...', '']) 2398 | let cnts = [0, 0] 2399 | let bar = '' 2400 | let total = filter(copy(g:plugs), 's:is_managed(v:key) && isdirectory(v:val.dir)') 2401 | call s:progress_bar(2, bar, len(total)) 2402 | for origin in [1, 0] 2403 | let plugs = reverse(sort(items(filter(copy(total), (origin ? '' : '!').'(has_key(v:val, "commit") || has_key(v:val, "tag"))')))) 2404 | if empty(plugs) 2405 | continue 2406 | endif 2407 | call s:append_ul(2, origin ? 'Pending updates:' : 'Last update:') 2408 | for [k, v] in plugs 2409 | let range = origin ? '..origin/'.v.branch : 'HEAD@{1}..' 2410 | let diff = s:system_chomp('git log --graph --color=never '.join(map(['--pretty=format:%x01%h%x01%d%x01%s%x01%cr', range], 's:shellesc(v:val)')), v.dir) 2411 | if !empty(diff) 2412 | let ref = has_key(v, 'tag') ? (' (tag: '.v.tag.')') : has_key(v, 'commit') ? (' '.v.commit) : '' 2413 | call append(5, extend(['', '- '.k.':'.ref], map(s:lines(diff), 's:format_git_log(v:val)'))) 2414 | let cnts[origin] += 1 2415 | endif 2416 | let bar .= '=' 2417 | call s:progress_bar(2, bar, len(total)) 2418 | normal! 2G 2419 | redraw 2420 | endfor 2421 | if !cnts[origin] 2422 | call append(5, ['', 'N/A']) 2423 | endif 2424 | endfor 2425 | call setline(1, printf('%d plugin(s) updated.', cnts[0]) 2426 | \ . (cnts[1] ? printf(' %d plugin(s) have pending updates.', cnts[1]) : '')) 2427 | 2428 | if cnts[0] || cnts[1] 2429 | nnoremap :silent! call preview_commit() 2430 | nnoremap o :silent! call preview_commit() 2431 | endif 2432 | if cnts[0] 2433 | nnoremap X :call revert() 2434 | echo "Press 'X' on each block to revert the update" 2435 | endif 2436 | normal! gg 2437 | setlocal nomodifiable 2438 | endfunction 2439 | 2440 | function! s:revert() 2441 | if search('^Pending updates', 'bnW') 2442 | return 2443 | endif 2444 | 2445 | let name = s:find_name(line('.')) 2446 | if empty(name) || !has_key(g:plugs, name) || 2447 | \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' 2448 | return 2449 | endif 2450 | 2451 | call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch).' --', g:plugs[name].dir) 2452 | setlocal modifiable 2453 | normal! "_dap 2454 | setlocal nomodifiable 2455 | echo 'Reverted' 2456 | endfunction 2457 | 2458 | function! s:snapshot(force, ...) abort 2459 | call s:prepare() 2460 | setf vim 2461 | call append(0, ['" Generated by vim-plug', 2462 | \ '" '.strftime("%c"), 2463 | \ '" :source this file in vim to restore the snapshot', 2464 | \ '" or execute: vim -S snapshot.vim', 2465 | \ '', '', 'PlugUpdate!']) 2466 | 1 2467 | let anchor = line('$') - 3 2468 | let names = sort(keys(filter(copy(g:plugs), 2469 | \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)'))) 2470 | for name in reverse(names) 2471 | let sha = s:system_chomp('git rev-parse --short HEAD', g:plugs[name].dir) 2472 | if !empty(sha) 2473 | call append(anchor, printf("silent! let g:plugs['%s'].commit = '%s'", name, sha)) 2474 | redraw 2475 | endif 2476 | endfor 2477 | 2478 | if a:0 > 0 2479 | let fn = expand(a:1) 2480 | if filereadable(fn) && !(a:force || s:ask(a:1.' already exists. Overwrite?')) 2481 | return 2482 | endif 2483 | call writefile(getline(1, '$'), fn) 2484 | echo 'Saved as '.a:1 2485 | silent execute 'e' s:esc(fn) 2486 | setf vim 2487 | endif 2488 | endfunction 2489 | 2490 | function! s:split_rtp() 2491 | return split(&rtp, '\\\@