├── README.md ├── autoload └── async │ └── job.vim ├── images ├── outdated.png └── updated.png └── plugin └── vim-outdated-plugins.vim /README.md: -------------------------------------------------------------------------------- 1 | ![Vim support](https://img.shields.io/badge/vim-support-brightgreen.svg?style=flat-square) 2 | ![Vim support](https://img.shields.io/badge/neovim-support-brightgreen.svg?style=flat-square) 3 | ![GitHub release](https://img.shields.io/github/release/semanser/vim-outdated-plugins.svg?style=flat-square) 4 | 5 | # vim-outdated-plugins 6 | Async plugin for showing number of your outdated plugins. 7 | 8 | ## What it does? 9 | This plugin automatically checks if any of your plugins are outdated and display a message about that. 10 | 11 | To use this plugin make sure you have **git** installed. 12 | 13 | ## Installation 14 | ```vim 15 | Plug 'semanser/vim-outdated-plugins' 16 | ``` 17 | 18 | ## Configuration 19 | ```vim 20 | " Do not show any message if all plugins are up to date. 0 by default 21 | let g:outdated_plugins_silent_mode = 1 22 | ``` 23 | 24 | ## Screenshots 25 | Simple message text message under the status bar. 26 | 27 | ![alt text](https://raw.githubusercontent.com/semanser/vim-outdated-plugins/master/images/outdated.png) 28 | ![alt text](https://raw.githubusercontent.com/semanser/vim-outdated-plugins/master/images/updated.png) 29 | 30 | ### OS 31 | - [x] macOS 32 | - [x] Linux 33 | - [ ] Windows 34 | 35 | ### Plugin Managers: 36 | - [x] vim-plug 37 | - [ ] Vundle 38 | - [ ] Pathogen 39 | - [ ] dein.vim 40 | - [ ] NeoBundle 41 | - [ ] VAM 42 | 43 | ### Notificatation methods 44 | - [x] Basic echo 45 | - [ ] Status line variable 46 | -------------------------------------------------------------------------------- /autoload/async/job.vim: -------------------------------------------------------------------------------- 1 | " Author: Prabir Shrestha 2 | " Website: https://github.com/prabirshrestha/async.vim 3 | " License: The MIT License {{{ 4 | " The MIT License (MIT) 5 | " 6 | " Copyright (c) 2016 Prabir Shrestha 7 | " 8 | " Permission is hereby granted, free of charge, to any person obtaining a copy 9 | " of this software and associated documentation files (the "Software"), to deal 10 | " in the Software without restriction, including without limitation the rights 11 | " to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | " copies of the Software, and to permit persons to whom the Software is 13 | " furnished to do so, subject to the following conditions: 14 | " 15 | " The above copyright notice and this permission notice shall be included in all 16 | " copies or substantial portions of the Software. 17 | " 18 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | " IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | " FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | " AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | " LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | " OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | " SOFTWARE. 25 | " }}} 26 | 27 | let s:save_cpo = &cpo 28 | set cpo&vim 29 | 30 | let s:jobidseq = 0 31 | let s:jobs = {} " { job, opts, type: 'vimjob|nvimjob'} 32 | let s:job_type_nvimjob = 'nvimjob' 33 | let s:job_type_vimjob = 'vimjob' 34 | let s:job_error_unsupported_job_type = -2 " unsupported job type 35 | 36 | function! s:job_supported_types() abort 37 | let l:supported_types = [] 38 | if has('nvim') 39 | let l:supported_types += [s:job_type_nvimjob] 40 | endif 41 | if !has('nvim') && has('job') && has('channel') && has('lambda') 42 | let l:supported_types += [s:job_type_vimjob] 43 | endif 44 | return l:supported_types 45 | endfunction 46 | 47 | function! s:job_supports_type(type) abort 48 | return index(s:job_supported_types(), a:type) >= 0 49 | endfunction 50 | 51 | function! s:out_cb(jobid, opts, job, data) abort 52 | if has_key(a:opts, 'on_stdout') 53 | call a:opts.on_stdout(a:jobid, split(a:data, "\n", 1), 'stdout') 54 | endif 55 | endfunction 56 | 57 | function! s:err_cb(jobid, opts, job, data) abort 58 | if has_key(a:opts, 'on_stderr') 59 | call a:opts.on_stderr(a:jobid, split(a:data, "\n", 1), 'stderr') 60 | endif 61 | endfunction 62 | 63 | function! s:exit_cb(jobid, opts, job, status) abort 64 | if has_key(a:opts, 'on_exit') 65 | call a:opts.on_exit(a:jobid, a:status, 'exit') 66 | endif 67 | if has_key(s:jobs, a:jobid) 68 | call remove(s:jobs, a:jobid) 69 | endif 70 | endfunction 71 | 72 | function! s:on_stdout(jobid, data, event) abort 73 | if has_key(s:jobs, a:jobid) 74 | let l:jobinfo = s:jobs[a:jobid] 75 | if has_key(l:jobinfo.opts, 'on_stdout') 76 | call l:jobinfo.opts.on_stdout(a:jobid, a:data, a:event) 77 | endif 78 | endif 79 | endfunction 80 | 81 | function! s:on_stderr(jobid, data, event) abort 82 | if has_key(s:jobs, a:jobid) 83 | let l:jobinfo = s:jobs[a:jobid] 84 | if has_key(l:jobinfo.opts, 'on_stderr') 85 | call l:jobinfo.opts.on_stderr(a:jobid, a:data, a:event) 86 | endif 87 | endif 88 | endfunction 89 | 90 | function! s:on_exit(jobid, status, event) abort 91 | if has_key(s:jobs, a:jobid) 92 | let l:jobinfo = s:jobs[a:jobid] 93 | if has_key(l:jobinfo.opts, 'on_exit') 94 | call l:jobinfo.opts.on_exit(a:jobid, a:status, a:event) 95 | endif 96 | endif 97 | endfunction 98 | 99 | function! s:job_start(cmd, opts) abort 100 | let l:jobtypes = s:job_supported_types() 101 | let l:jobtype = '' 102 | 103 | if has_key(a:opts, 'type') 104 | if type(a:opts.type) == type('') 105 | if !s:job_supports_type(a:opts.type) 106 | return s:job_error_unsupported_job_type 107 | endif 108 | let l:jobtype = a:opts.type 109 | else 110 | let l:jobtypes = a:opts.type 111 | endif 112 | endif 113 | 114 | if empty(l:jobtype) 115 | " find the best jobtype 116 | for l:jobtype2 in l:jobtypes 117 | if s:job_supports_type(l:jobtype2) 118 | let l:jobtype = l:jobtype2 119 | endif 120 | endfor 121 | endif 122 | 123 | if l:jobtype ==? '' 124 | return s:job_error_unsupported_job_type 125 | endif 126 | 127 | if l:jobtype == s:job_type_nvimjob 128 | let l:job = jobstart(a:cmd, { 129 | \ 'on_stdout': function('s:on_stdout'), 130 | \ 'on_stderr': function('s:on_stderr'), 131 | \ 'on_exit': function('s:on_exit'), 132 | \}) 133 | if l:job <= 0 134 | return l:job 135 | endif 136 | let l:jobid = l:job " nvimjobid and internal jobid is same 137 | let s:jobs[l:jobid] = { 138 | \ 'type': s:job_type_nvimjob, 139 | \ 'opts': a:opts, 140 | \ } 141 | let s:jobs[l:jobid].job = l:job 142 | elseif l:jobtype == s:job_type_vimjob 143 | let s:jobidseq = s:jobidseq + 1 144 | let l:jobid = s:jobidseq 145 | let l:jobopt = { 146 | \ 'out_cb': function('s:out_cb', [l:jobid, a:opts]), 147 | \ 'err_cb': function('s:err_cb', [l:jobid, a:opts]), 148 | \ 'exit_cb': function('s:exit_cb', [l:jobid, a:opts]), 149 | \ 'mode': 'raw', 150 | \ } 151 | if has('patch-8.1.889') 152 | let l:jobopt['noblock'] = 1 153 | endif 154 | let l:job = job_start(a:cmd, l:jobopt) 155 | if job_status(l:job) !=? 'run' 156 | return -1 157 | endif 158 | let s:jobs[l:jobid] = { 159 | \ 'type': s:job_type_vimjob, 160 | \ 'opts': a:opts, 161 | \ 'job': l:job, 162 | \ 'channel': job_getchannel(l:job), 163 | \ 'buffer': '' 164 | \ } 165 | else 166 | return s:job_error_unsupported_job_type 167 | endif 168 | 169 | return l:jobid 170 | endfunction 171 | 172 | function! s:job_stop(jobid) abort 173 | if has_key(s:jobs, a:jobid) 174 | let l:jobinfo = s:jobs[a:jobid] 175 | if l:jobinfo.type == s:job_type_nvimjob 176 | call jobstop(a:jobid) 177 | elseif l:jobinfo.type == s:job_type_vimjob 178 | call job_stop(s:jobs[a:jobid].job) 179 | endif 180 | if has_key(s:jobs, a:jobid) 181 | call remove(s:jobs, a:jobid) 182 | endif 183 | endif 184 | endfunction 185 | 186 | function! s:job_send(jobid, data) abort 187 | let l:jobinfo = s:jobs[a:jobid] 188 | if l:jobinfo.type == s:job_type_nvimjob 189 | call jobsend(a:jobid, a:data) 190 | elseif l:jobinfo.type == s:job_type_vimjob 191 | if has('patch-8.1.0818') 192 | call ch_sendraw(l:jobinfo.channel, a:data) 193 | else 194 | let l:jobinfo.buffer .= a:data 195 | call s:flush_vim_sendraw(a:jobid, v:null) 196 | endif 197 | endif 198 | endfunction 199 | 200 | function! s:flush_vim_sendraw(jobid, timer) abort 201 | " https://github.com/vim/vim/issues/2548 202 | " https://github.com/natebosch/vim-lsc/issues/67#issuecomment-357469091 203 | let l:jobinfo = s:jobs[a:jobid] 204 | sleep 1m 205 | if len(l:jobinfo.buffer) <= 4096 206 | call ch_sendraw(l:jobinfo.channel, l:jobinfo.buffer) 207 | let l:jobinfo.buffer = '' 208 | else 209 | let l:to_send = l:jobinfo.buffer[:4095] 210 | let l:jobinfo.buffer = l:jobinfo.buffer[4096:] 211 | call ch_sendraw(l:jobinfo.channel, l:to_send) 212 | call timer_start(1, function('s:flush_vim_sendraw', [a:jobid])) 213 | endif 214 | endfunction 215 | 216 | function! s:job_wait_single(jobid, timeout, start) abort 217 | if !has_key(s:jobs, a:jobid) 218 | return -3 219 | endif 220 | 221 | let l:jobinfo = s:jobs[a:jobid] 222 | if l:jobinfo.type == s:job_type_nvimjob 223 | let l:timeout = a:timeout - reltimefloat(reltime(a:start)) * 1000 224 | return jobwait([a:jobid], float2nr(l:timeout))[0] 225 | elseif l:jobinfo.type == s:job_type_vimjob 226 | let l:timeout = a:timeout / 1000.0 227 | try 228 | while l:timeout < 0 || reltimefloat(reltime(a:start)) < l:timeout 229 | let l:info = job_info(l:jobinfo.job) 230 | if l:info.status ==# 'dead' 231 | return l:info.exitval 232 | elseif l:info.status ==# 'fail' 233 | return -3 234 | endif 235 | sleep 1m 236 | endwhile 237 | catch /^Vim:Interrupt$/ 238 | return -2 239 | endtry 240 | endif 241 | return -1 242 | endfunction 243 | 244 | function! s:job_wait(jobids, timeout) abort 245 | let l:start = reltime() 246 | let l:exitcode = 0 247 | let l:ret = [] 248 | for l:jobid in a:jobids 249 | if l:exitcode != -2 " Not interrupted. 250 | let l:exitcode = s:job_wait_single(l:jobid, a:timeout, l:start) 251 | endif 252 | let l:ret += [l:exitcode] 253 | endfor 254 | return l:ret 255 | endfunction 256 | 257 | function! s:job_pid(jobid) abort 258 | if !has_key(s:jobs, a:jobid) 259 | return 0 260 | endif 261 | 262 | let l:jobinfo = s:jobs[a:jobid] 263 | if l:jobinfo.type == s:job_type_nvimjob 264 | return jobpid(a:jobid) 265 | elseif l:jobinfo.type == s:job_type_vimjob 266 | let l:vimjobinfo = job_info(a:jobid) 267 | if type(l:vimjobinfo) == type({}) && has_key(l:vimjobinfo, 'process') 268 | return l:vimjobinfo['process'] 269 | endif 270 | endif 271 | return 0 272 | endfunction 273 | 274 | " public apis {{{ 275 | function! async#job#start(cmd, opts) abort 276 | return s:job_start(a:cmd, a:opts) 277 | endfunction 278 | 279 | function! async#job#stop(jobid) abort 280 | call s:job_stop(a:jobid) 281 | endfunction 282 | 283 | function! async#job#send(jobid, data) abort 284 | call s:job_send(a:jobid, a:data) 285 | endfunction 286 | 287 | function! async#job#wait(jobids, ...) abort 288 | let l:timeout = get(a:000, 0, -1) 289 | return s:job_wait(a:jobids, l:timeout) 290 | endfunction 291 | 292 | function! async#job#pid(jobid) abort 293 | return s:job_pid(a:jobid) 294 | endfunction 295 | " }}} 296 | -------------------------------------------------------------------------------- /images/outdated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/semanser/vim-outdated-plugins/25769d1b01161498bc963dffd5ed50f0ed986b05/images/outdated.png -------------------------------------------------------------------------------- /images/updated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/semanser/vim-outdated-plugins/25769d1b01161498bc963dffd5ed50f0ed986b05/images/updated.png -------------------------------------------------------------------------------- /plugin/vim-outdated-plugins.vim: -------------------------------------------------------------------------------- 1 | if !exists('g:outdated_plugins_silent_mode') 2 | let g:outdated_plugins_silent_mode = 0 3 | endif 4 | 5 | function! s:JobHandler(job_id, data, event) dict 6 | if (str2nr(join(a:data)) != 0) 7 | let g:pluginsToUpdate += 1 8 | endif 9 | endfunction 10 | 11 | function! s:CalculateUpdates(job_id, data, event) dict 12 | let l:numberOfcheckedPlugins = 0 13 | let l:command = "" 14 | 15 | for key in keys(g:plugs) 16 | let l:command .= '(git -C ' . g:plugs[key].dir . ' rev-list HEAD..origin --count)' 17 | let l:numberOfcheckedPlugins += 1 18 | 19 | if l:numberOfcheckedPlugins != len(keys(g:plugs)) 20 | let l:command .= ' &&' 21 | endif 22 | endfor 23 | 24 | call async#job#start([ 'bash', '-c', l:command], s:calculateCallbacks) 25 | endfunction 26 | 27 | function! s:DisplayResults(job_id, data, event) dict 28 | if g:pluginsToUpdate > 0 29 | echom 'Plugins to update: ' . g:pluginsToUpdate 30 | else 31 | if !g:outdated_plugins_silent_mode 32 | echom 'All plugins up-to-date' 33 | endif 34 | endif 35 | endfunction 36 | 37 | 38 | let s:remoteUpdateCallbacks = { 39 | \ 'on_exit': function('s:CalculateUpdates') 40 | \ } 41 | 42 | let s:calculateCallbacks = { 43 | \ 'on_stdout': function('s:JobHandler'), 44 | \ 'on_exit': function('s:DisplayResults') 45 | \ } 46 | 47 | function! CheckForUpdates() 48 | let g:pluginsToUpdate = 0 49 | let l:command = "" 50 | 51 | " TODO check only activated plugins and not all downloaded 52 | let l:numberOfcheckedPlugins = 0 53 | for key in keys(g:plugs) 54 | let l:command .= 'git -C ' . g:plugs[key].dir . ' remote update > /dev/null' 55 | let l:numberOfcheckedPlugins += 1 56 | 57 | if l:numberOfcheckedPlugins != len(keys(g:plugs)) 58 | let l:command .= ' &' 59 | endif 60 | endfor 61 | 62 | call async#job#start([ 'bash', '-c', l:command], s:remoteUpdateCallbacks) 63 | endfunction 64 | 65 | au VimEnter * call CheckForUpdates() 66 | 67 | --------------------------------------------------------------------------------