├── 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 |
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 |
--------------------------------------------------------------------------------