├── LICENSE ├── README.md └── zen.vim /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Danish Prakash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

vim-zen

2 |

Barebones Vim Plugin Manager

3 | 4 |

5 | Asciicast 6 |

7 | 8 | ### Features 9 | - Does 3 things and does them well - Install, Remove, Update 10 | - Parallel install & update using Python multithreading. 11 | - Easy setup and simple usage. 12 | 13 | ### Installation 14 | Put the [zen.vim](https://raw.githubusercontent.com/danishprakash/vim-zen/master/zen.vim) file into the `autoload` directory. 15 | 16 | #### Unix 17 | ##### Neovim 18 | ```bash 19 | curl -o ~/.local/share/nvim/site/autoload/zen.vim --create-dirs https://raw.githubusercontent.com/danishprakash/vim-zen/master/zen.vim 20 | ``` 21 | 22 | ##### Vim 23 | ```bash 24 | curl -o ~/.vim/autoload/zen.vim --create-dirs https://raw.githubusercontent.com/danishprakash/vim-zen/master/zen.vim 25 | ``` 26 | 27 | ### Usage 28 | - Add a `vim-zen` section in your vimrc. 29 | - Add `call zen#init()` method at the beginnning of the section. 30 | - Add plugins using the `Plugin` command. 31 | - Reload `.vimrc`. 32 | - Run `ZenInstall` from within vim. 33 | 34 | 35 | ### Example vim-zen section 36 | ```vim 37 | " begin section 38 | call zen#init() 39 | Plugin 'junegunn/goyo.vim' 40 | Plugin 'https://github.com/danishprakash/vimport' 41 | " end section 42 | ``` 43 | See [this](https://github.com/danishprakash/dotfiles/blob/master/nvim/init.vim) for reference. 44 | 45 | ### Commands 46 | 47 | 1. `ZenInstall`: Install plugins. 48 | 2. `ZenUpdate`: Update plugins. 49 | 3. `ZenDelete`: Remove unused plugins. 50 | 51 | ### Why? 52 | I wanted something really simple, all other plugin managers out there did the things that I wanted along with other stuff. I wanted a plugin manager that helped me `install`, `remove`, and `update` the plugins I use. 53 | 54 | ### Links 55 | - [Changelog Nightly (21/6)](http://nightly.changelog.com/2018/06/21/) 56 | 57 | ### License 58 | MIT 59 | -------------------------------------------------------------------------------- /zen.vim: -------------------------------------------------------------------------------- 1 | " ========================================================== 2 | " Name: vim-zen: Vim plugin manager 3 | " Maintainer: Danish Prakash 4 | " HomePage: https://github.com/danishprakash/vim-zen 5 | " License: MIT 6 | " ========================================================== 7 | 8 | 9 | function! zen#init() abort 10 | let s:zen_win = 0 11 | let g:plugins = {} 12 | let g:plugin_names = [] 13 | let s:installation_path = '' 14 | let s:plugin_display_order = {} 15 | 16 | " set installation path for plugins 17 | if has('nvim') 18 | if !isdirectory($HOME . '/.local/share/nvim/plugged') 19 | call mkdir($HOME . '/.local/share/nvim/plugged') 20 | endif 21 | let s:installation_path = $HOME . '/.local/share/nvim/plugged' 22 | else 23 | if !isdirectory($HOME . '/.vim/plugged') 24 | call mkdir($HOME . '/.vim/plugged') 25 | endif 26 | let s:installation_path = $HOME . '/.vim/plugged' 27 | endif 28 | 29 | call s:define_commands() 30 | autocmd VimEnter * call s:git_installed() 31 | endfunction 32 | 33 | 34 | " syntax highlighting 35 | function! s:syntax() abort 36 | syntax clear 37 | syntax match ZenSep /^=.*=/ 38 | syntax match ZenTitle /^vim-zen/ 39 | syntax match ZenPlus /\[+].*:/ 40 | syntax match ZenMinus /\[-].*:/ 41 | syntax match ZenSpace /\[ ].*:/ 42 | syntax match ZenDelete /\[x.\{-}:/ 43 | 44 | highlight link ZenPlus Function 45 | highlight link ZenMinus Type 46 | highlight link ZenSpace Comment 47 | highlight link ZenSep Comment 48 | highlight link ZenTitle Special 49 | highlight link ZenDelete Exception 50 | endfunction 51 | 52 | 53 | function! s:git_installed() abort 54 | if !executable('git') 55 | echohl ErrorMsg 56 | echom "[vim-zen] git is required." 57 | echohl None 58 | endif 59 | endfunction 60 | 61 | 62 | " list all installed plugins 63 | function! s:list_plugins() abort 64 | let l:count = 4 65 | for l:plugin in keys(g:plugins) 66 | call append(line('$'), '[ ] ' . g:plugins[l:plugin]['name'] . ': ') 67 | let s:plugin_display_order[g:plugins[l:plugin]['name']] = l:count 68 | let l:count = l:count + 1 69 | endfor 70 | call s:syntax() 71 | redraw 72 | endfunction 73 | 74 | 75 | function! s:populate_window(message, flag) abort 76 | let l:heading = 'vim-zen - ' . '[ ' . a:message . ' ]' 77 | 78 | " when populating window for the first time 79 | if !(a:flag) 80 | call s:start_window() 81 | call append(0, l:heading) 82 | call append(1, repeat('=', len(l:heading))) 83 | else 84 | call setline(1, l:heading) 85 | call setline(2, repeat('=', len(l:heading))) 86 | endif 87 | call s:syntax() 88 | redraw 89 | endfunction 90 | 91 | 92 | " source plugin files 93 | function! s:load_plugin(plugin) abort 94 | let l:plugin_path = g:plugins[a:plugin]['path'] 95 | if has_key(g:plugins[a:plugin], 'rtp') 96 | let l:plugin_path = l:plugin_path . '/' . g:plugins[a:plugin]['rtp'] 97 | endif 98 | let l:patterns = ['plugin/**/*.vim', 'after/plugin/**/*.vim'] 99 | for pattern in l:patterns 100 | for vimfile in split(globpath(l:plugin_path, pattern), '\n') 101 | execute 'source' vimfile 102 | endfor 103 | endfor 104 | endfunction 105 | 106 | 107 | " user defined commands for functions 108 | function! s:define_commands() abort 109 | command! -nargs=+ -bar Plugin call zen#add() 110 | command! -nargs=* -bar -bang -complete=customlist,s:names ZenInstall call zen#install() 111 | command! -nargs=* -bar -bang -complete=customlist,s:names ZenRemove call zen#remove() 112 | command! -nargs=* -bar -bang -complete=customlist,s:names ZenUpdate call zen#update() 113 | endfunction 114 | 115 | 116 | " load `g:plugins` with plugins in .vimrc 117 | function! zen#add(remote, ...) 118 | let l:local = 0 119 | let l:plugin_name = split(a:remote, '/')[-1] 120 | let l:plugin_dir = s:installation_path . '/' . l:plugin_name 121 | 122 | if a:remote =~ '^https:\/\/.\+' 123 | let l:remote_name = a:remote 124 | elseif a:remote =~ '^http:\/\/.\+' 125 | let l:remote_name = a:remote 126 | let l:remote_name = substitute(l:remote, '^http:\/\/.\+', 'https://', '') 127 | elseif isdirectory(a:remote) 128 | let l:local = 1 129 | let l:remote_name = a:remote 130 | let l:plugin_dir = a:remote 131 | elseif a:remote =~ '^.\+/.\+' 132 | let l:remote_name = 'https://github.com/' . a:remote . '.git' 133 | else 134 | echom "Failed to create remote repository path" 135 | endif 136 | 137 | let g:plugins[l:plugin_name] = {'name': l:plugin_name, 'remote': l:remote_name, 'path': l:plugin_dir} 138 | 139 | if a:0 > 0 140 | call extend(g:plugins[l:plugin_name], a:1) 141 | if has_key(a:000[0], 'rtp') 142 | let l:plugin_dir = l:plugin_dir . '/' . a:000[0]['rtp'] 143 | let g:plugins[l:plugin_name]['path'] = l:plugin_dir 144 | endif 145 | endif 146 | 147 | if l:local 148 | let g:plugins[l:plugin_name]['local'] = l:local 149 | endif 150 | 151 | execute "set rtp+=" . l:plugin_dir 152 | call add(g:plugin_names, l:plugin_name) 153 | endfunction 154 | 155 | 156 | " assign name to the plugin buffer window 157 | function! s:assign_buffer_name() abort 158 | let name = '[vim-zen]' 159 | silent! execute "f " . l:name 160 | endfunction 161 | 162 | 163 | " start a new buffer window for plugin operations 164 | function! s:start_window() abort 165 | execute s:zen_win . 'wincmd w' 166 | if !exists('b:plug') 167 | vertical new 168 | nnoremap q :q 169 | let b:plug = 1 170 | let s:zen_win = winnr() 171 | else 172 | %d 173 | endif 174 | setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline 175 | call s:assign_buffer_name() 176 | endfunction 177 | 178 | 179 | " install plugins 180 | function! zen#install() abort 181 | let l:plugins_to_install = [] 182 | let l:populate_window_message = 'Installation finished' 183 | call s:populate_window('Installing plugins...', 0) 184 | call s:list_plugins() 185 | 186 | for key in keys(g:plugins) 187 | let l:plugin = g:plugins[key] 188 | let l:install_path = s:installation_path . "/" . l:plugin['name'] 189 | if has_key(g:plugins[key], 'local') 190 | call s:load_plugin(key) 191 | call setline(s:plugin_display_order[key], '[-] ' . key . ': ' . '[ MANAGED MANUALLY ]') 192 | continue 193 | elseif !isdirectory(l:install_path) 194 | let l:cmd = "git clone " . l:plugin['remote'] . " " . l:install_path 195 | call add(l:plugins_to_install, l:cmd) 196 | else 197 | call setline(s:plugin_display_order[g:plugins[key]['name']], '[-] ' . l:plugin['name'] . ': ' . 'Already installed.') 198 | endif 199 | redraw 200 | endfor 201 | 202 | if len(l:plugins_to_install) == 0 203 | : 204 | elseif len(l:plugins_to_install) > 1 205 | call s:parallel_operation_python('install', l:plugins_to_install) 206 | return 207 | else 208 | let l:plugin_name = split(cmd, '/')[-1] 209 | let l:cmd_result = system(l:cmd) 210 | if l:cmd_result =~ 'fatal' 211 | let l:installation_status = 'x' 212 | let l:cmd_result = 'Failed (' . l:cmd_result . ')' 213 | let l:populate_window_message = l:populate_window_message . ' with errors' 214 | else 215 | let l:cmd_result = 'Installed' 216 | let l:installation_status = '+' 217 | endif 218 | call setline(s:plugin_display_order[l:plugin_name], '[' . l:installation_status . '] ' . l:plugin_name . ': ' . l:cmd_result) 219 | call s:load_plugin(l:plugin_name) 220 | endif 221 | redraw 222 | call s:populate_window(l:populate_window_message, 1) 223 | endfunction 224 | 225 | 226 | " display warning prompt and ask for input 227 | function! s:warning_prompt(message) abort 228 | call inputsave() 229 | echohl WarningMsg 230 | let l:choice = input(a:message . ' (y/n): ') 231 | echohl None 232 | call inputrestore() 233 | echo "\r" 234 | return (l:choice =~? '^y') ? 1 : 0 235 | endfunction 236 | 237 | 238 | " remove unused plugins 239 | function! zen#remove() abort 240 | let l:count = 4 241 | let l:unused_plugins = [] 242 | let l:cloned_plugins = split(globpath(s:installation_path, "*"), "\n") 243 | call s:populate_window('Removing unused plugins...', 0) 244 | 245 | if g:plugins == {} 246 | call append(line('$'), 'No plugins installed.') 247 | endif 248 | 249 | for l:dir in l:cloned_plugins 250 | let l:plugin_dir_name = split(l:dir, '/')[-1] 251 | let l:plugin_dir_path = s:installation_path . "/" . l:plugin_dir_name 252 | if !has_key(g:plugins, l:plugin_dir_name) 253 | call add(l:unused_plugins, l:plugin_dir_path) 254 | call setline(l:count, '[ ] ' . l:plugin_dir_path) 255 | let l:count = l:count + 1 256 | redraw 257 | endif 258 | endfor 259 | 260 | if l:unused_plugins == [] 261 | call append(line('$'), 'No plugins to remove.') 262 | call s:populate_window('Done', 1) 263 | return 264 | endif 265 | 266 | let l:count = 4 267 | if s:warning_prompt('Delete the following plugins?') 268 | for l:item in l:unused_plugins 269 | let l:plugin_name = split(l:item, '/')[-1] 270 | let l:cmd_result = system('rm -rf ' . l:item) 271 | call setline(l:count, '[x] ' . l:item . ': Removed!') 272 | let l:count = l:count + 1 273 | redraw 274 | endfor 275 | endif 276 | call s:populate_window('Finished cleaning!', 1) 277 | endfunction 278 | 279 | 280 | " update plugins 281 | function! zen#update() abort 282 | call s:populate_window('Updating plugins..', 0) 283 | call s:list_plugins() 284 | call s:parallel_operation_python('update', []) 285 | endfunction 286 | 287 | 288 | " python code for multithreaded operations 289 | function! s:parallel_operation_python(mode, plugins_to_install) abort 290 | let py_exe = has('python') ? 'python' : 'python3' 291 | execute py_exe "<< EOF" 292 | import vim 293 | import time 294 | import commands 295 | import threading 296 | 297 | try: 298 | import queue 299 | except ImportError: 300 | import Queue as queue 301 | 302 | class ZenThread(threading.Thread): 303 | def __init__(self, cmd, queue, name=''): 304 | threading.Thread.__init__(self) 305 | self.cmd = cmd 306 | self.queue = queue 307 | self.name = name 308 | 309 | def run(self): 310 | (status, output) = commands.getstatusoutput(self.cmd) 311 | self.queue.put((self.cmd, output, status, self.name)) 312 | 313 | 314 | def install(): 315 | count = 4 316 | thread_list = list() 317 | result_queue = queue.Queue() 318 | plugins = vim.eval('g:plugins') 319 | path = vim.eval('s:installation_path') 320 | plugins_to_install = vim.eval('a:plugins_to_install') 321 | plugin_display_order = vim.eval('s:plugin_display_order') 322 | 323 | if plugins_to_install == []: 324 | return 325 | 326 | start_time = time.time() 327 | for cmd in plugins_to_install: 328 | plugin_name = cmd.split('/')[-1] 329 | plugin_path = path + '/' + plugin_name 330 | to_install = vim.eval('!isdirectory("{}")'.format(plugin_path)) 331 | if to_install: 332 | thread = ZenThread(cmd, result_queue) 333 | thread_list.append(thread) 334 | thread.start() 335 | vim.eval('setline({0}, "[+] {1}: Installing...")'.format(plugin_display_order[plugin_name], plugin_name)) 336 | else: 337 | vim.eval('setline({0}, "[-] {1}: Already installed.")'.format(plugin_display_order[plugin_name], plugin_name)) 338 | vim.command('redraw') 339 | count += 1 340 | 341 | while threading.active_count() > 1 or not result_queue.empty(): 342 | while not result_queue.empty(): 343 | # TODO: use name with queue 344 | (cmd, output, status, name) = result_queue.get() 345 | plugin_name = cmd.split('/')[-1] 346 | if status == 0: 347 | vim.eval('s:load_plugin("{}")'.format(plugin_name)) 348 | vim.eval('setline({0}, "[+] {1}: Installed")'.format(plugin_display_order[plugin_name], plugin_name)) 349 | else: 350 | vim.eval('setline({0}, "[x] {1}: [ERROR] {2}")'.format(plugin_display_order[plugin_name], plugin_name, output)) 351 | vim.command('redraw') 352 | 353 | for thread in thread_list: 354 | thread.join() 355 | 356 | vim.eval('s:populate_window("Installation finished!\t|\t Time: {0}", 1)'.format(time.time()-start_time)) 357 | 358 | 359 | def update(): 360 | commands = list() 361 | git_cmd = 'git -C' 362 | result_queue = queue.Queue() 363 | plugins = vim.eval('g:plugins') 364 | populate_window_message = 'Finished installation' 365 | plugin_display_order = vim.eval('s:plugin_display_order') 366 | start_time = time.time() 367 | 368 | for key, value in plugins.items(): 369 | if value.get('local') == str(1): 370 | vim.eval('setline({0}, "[-] {1}: {2}")'.format(plugin_display_order[key], key, '[ MANAGED MANUALLY ]')) 371 | continue 372 | cmd = str(git_cmd + ' \"' + value['path'] + '\" pull') 373 | thread = ZenThread(cmd, result_queue, name=value.get('name')) 374 | thread.start() 375 | 376 | while threading.active_count() > 1 or not result_queue.empty(): 377 | while not result_queue.empty(): 378 | (cmd, output, status, name) = result_queue.get() 379 | plugin_name = name 380 | if status == 0: 381 | # TODO: add check if already updated or not update status_message accordingly (+, -) 382 | vim.eval('s:load_plugin("{}")'.format(plugin_name)) 383 | vim.eval('setline({0}, "[+] {1}: {2}")'.format(plugin_display_order[plugin_name], plugin_name, output)) 384 | else: 385 | populate_window_message = 'Finished installation with errors' 386 | vim.eval('setline({0}, "[x] {1}: [ERROR] {2}")'.format(plugin_display_order[plugin_name], plugin_name, output)) 387 | vim.command('redraw') 388 | 389 | vim.eval('s:populate_window("{0} | Time: {1}", 1)'.format(populate_window_message, time.time() - start_time)) 390 | 391 | mode = vim.eval('a:mode') 392 | update() if mode == 'update' else install() 393 | EOF 394 | 395 | endfunction 396 | --------------------------------------------------------------------------------