From cf2d8995001bf466975e79409cb5e9cf632526ed Mon Sep 17 00:00:00 2001 From: Jan Geboers Date: Wed, 18 Feb 2026 16:40:58 +0100 Subject: [PATCH 1/3] Use job API for system() calls in GUI Vim to avoid overhead In GUI Vim (gvim, MacVim), each system() call incurs significant overhead due to command prompt window creation, making operations like PlugDiff ~5x slower than in terminal Vim or Neovim. Add s:system_job() which uses job_start with file-based output to run commands without that overhead, and use it in s:system() when running in GUI Vim with job support. Since v:shell_error is read-only in Vim 9, introduce s:shell_error which is set by both code paths and used at all call sites. Fixes #1312 --- plug.vim | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/plug.vim b/plug.vim index f479fba9..6a453799 100644 --- a/plug.vim +++ b/plug.vim @@ -68,6 +68,7 @@ let s:mac_gui = has('gui_macvim') && has('gui_running') let s:is_win = has('win32') let s:nvim = has('nvim-0.2') || (has('nvim') && exists('*jobwait') && !s:is_win) let s:vim8 = has('patch-8.0.0039') && exists('*job_start') +let s:shell_error = 0 if s:is_win && &shellslash set noshellslash let s:me = resolve(expand(':p')) @@ -170,7 +171,7 @@ function! s:git_origin_branch(spec) " The command may not return the name of a branch in detached HEAD state let result = s:lines(s:system('git symbolic-ref --short HEAD', a:spec.dir)) - return v:shell_error ? '' : result[-1] + return s:shell_error ? '' : result[-1] endfunction if s:is_win @@ -1097,7 +1098,7 @@ function! s:checkout(spec) let credential_helper = s:disable_credential_helper() ? '-c credential.helper= ' : '' let output = s:system( \ 'git '.credential_helper.'fetch --depth 999999 && git checkout '.plug#shellescape(sha).' --', a:spec.dir) - let error = v:shell_error + let error = s:shell_error endif return [output, error] endfunction @@ -1303,7 +1304,7 @@ function! s:update_finish() let tag = spec.tag if tag =~ '\*' let tags = s:lines(s:system('git tag --list '.plug#shellescape(tag).' --sort -version:refname 2>&1', spec.dir)) - if !v:shell_error && !empty(tags) + if !s:shell_error && !empty(tags) let tag = tags[0] call s:log4(name, printf('Latest tag for %s -> %s', spec.tag, tag)) call append(3, '') @@ -1311,7 +1312,7 @@ function! s:update_finish() endif call s:log4(name, 'Checking out '.tag) let out = s:system('git checkout -q '.plug#shellescape(tag).' -- 2>&1', spec.dir) - let error = v:shell_error + let error = s:shell_error endif if !error && filereadable(spec.dir.'/.gitmodules') && \ (s:update.force || has_key(s:update.new, name) || s:is_updated(spec.dir)) @@ -1319,7 +1320,7 @@ function! s:update_finish() let out .= s:bang('git submodule update --init --recursive'.s:submodule_opt.' 2>&1', spec.dir) let error = v:shell_error endif - let msg = s:format_message(v:shell_error ? 'x': '-', name, out) + let msg = s:format_message(error ? 'x': '-', name, out) if error call add(s:update.errors, name) call s:regress_bar() @@ -1480,7 +1481,7 @@ function! s:spawn(name, spec, queue, opts) endif else let job.lines = s:lines(call('s:system', has_key(a:opts, 'dir') ? [argv, a:opts.dir] : [argv])) - let job.error = v:shell_error != 0 + let job.error = s:shell_error != 0 let job.running = 0 endif endfunction @@ -2334,6 +2335,21 @@ function! s:with_cd(cmd, dir, ...) return printf('%s %s %s %s', cd, plug#shellescape(a:dir, {'script': script, 'shell': &shell}), sep, a:cmd) endfunction +function! s:system_job(cmd) abort + let tmp = tempname() + let job = job_start(['/bin/sh', '-c', a:cmd], { + \ 'out_io': 'file', + \ 'out_name': tmp, + \ 'err_io': 'out', + \}) + while job_status(job) ==# 'run' + endwhile + let s:shell_error = job_info(job).exitval + let result = filereadable(tmp) ? join(readfile(tmp, 'b'), "\n") : '' + silent! call delete(tmp) + return result +endfunction + function! s:system(cmd, ...) let batchfile = '' try @@ -2343,7 +2359,9 @@ function! s:system(cmd, ...) " but it cannot set the working directory for the command. " Assume that the command does not rely on the shell. if has('nvim') && a:0 == 0 - return system(a:cmd) + let ret = system(a:cmd) + let s:shell_error = v:shell_error + return ret endif let cmd = join(map(copy(a:cmd), 'plug#shellescape(v:val, {"shell": &shell, "script": 0})')) if s:is_powershell(&shell) @@ -2358,7 +2376,12 @@ function! s:system(cmd, ...) if s:is_win && type(a:cmd) != s:TYPE.list let [batchfile, cmd] = s:batchfile(cmd) endif - return system(cmd) + if s:vim8 && has('gui_running') && !s:is_win + return s:system_job(cmd) + endif + let ret = system(cmd) + let s:shell_error = v:shell_error + return ret finally let [&shell, &shellcmdflag, &shellredir] = [sh, shellcmdflag, shrd] if s:is_win && filereadable(batchfile) @@ -2369,7 +2392,7 @@ endfunction function! s:system_chomp(...) let ret = call('s:system', a:000) - return v:shell_error ? '' : substitute(ret, '\n$', '', '') + return s:shell_error ? '' : substitute(ret, '\n$', '', '') endfunction function! s:git_validate(spec, check_branch) @@ -2412,7 +2435,7 @@ function! s:git_validate(spec, check_branch) \ 'git', 'rev-list', '--count', '--left-right', \ printf('HEAD...origin/%s', origin_branch) \ ], a:spec.dir)), '\t') - if v:shell_error || len(ahead_behind) != 2 + if s:shell_error || len(ahead_behind) != 2 let err = "Failed to compare with the origin. The default branch might have changed.\nPlugClean required." else let [ahead, behind] = ahead_behind @@ -2562,7 +2585,7 @@ function! s:upgrade() try let out = s:system(['git', 'clone', '--depth', '1', s:plug_src, tmp]) - if v:shell_error + if s:shell_error return s:err('Error upgrading vim-plug: '. out) endif From 87dfcb2d2126e6778be6a9cb09f9eed0ac878cc4 Mon Sep 17 00:00:00 2001 From: Jan Geboers Date: Wed, 18 Feb 2026 17:01:54 +0100 Subject: [PATCH 2/3] Avoid busy-wait in s:system_job() polling loop Add sleep 10m to the job_status polling loop so it yields the CPU instead of spinning at 100% while waiting for the job to finish. --- plug.vim | 1 + 1 file changed, 1 insertion(+) diff --git a/plug.vim b/plug.vim index 6a453799..27f1b65e 100644 --- a/plug.vim +++ b/plug.vim @@ -2343,6 +2343,7 @@ function! s:system_job(cmd) abort \ 'err_io': 'out', \}) while job_status(job) ==# 'run' + sleep 10m endwhile let s:shell_error = job_info(job).exitval let result = filereadable(tmp) ? join(readfile(tmp, 'b'), "\n") : '' From 28dfd3695bff5ed7877853484970dc937c0bdbc4 Mon Sep 17 00:00:00 2001 From: Jan Geboers Date: Wed, 18 Feb 2026 17:20:32 +0100 Subject: [PATCH 3/3] Reduce sleep in s:system_job() polling loop to 1ms --- plug.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plug.vim b/plug.vim index 27f1b65e..de5ae1fc 100644 --- a/plug.vim +++ b/plug.vim @@ -2343,7 +2343,7 @@ function! s:system_job(cmd) abort \ 'err_io': 'out', \}) while job_status(job) ==# 'run' - sleep 10m + sleep 1m endwhile let s:shell_error = job_info(job).exitval let result = filereadable(tmp) ? join(readfile(tmp, 'b'), "\n") : ''