Compare commits

...

13 commits

Author SHA1 Message Date
Junegunn Choi 88e3147181
Renew issue template
Some checks failed
Test vim-plug / build (vim) (push) Has been cancelled
2026-05-22 22:40:23 +09:00
tiamat18 e216852799
Fix breakage to 'PlugUpgrade' under Windows PowerShell. (#1331)
Commit 09b7c0e has caused this command to emit an error during its
cleanup when run on Windows with 'shell' set to PowerShell.

The cause of the failure is that the cleanup logic passes the command
string 'rmdir /s /q' to `s:system()` (within `s:rm_rf()`). Before the
commit at fault, this command would be written to a batch file and
executed by 'cmd.exe'. After the commit, it is passed directly to
PowerShell for execution, where 'rmdir' is an alias for 'Remove-Item'
and does not support '/s' and '/q' flags.
2026-05-22 16:21:46 +09:00
tiamat18 d7db1b637c
Fix Support for PowerShell under Windows with VIM 8+ (#1326)
The following 'vim-plug' commands would fail and/or display errors
when executed by VIM 8+ on a Windows platform with the 'shell' option
set to 'powershell' or 'pwsh':
  - PlugInstall
  - PlugUpdate
  - PlugClean
  - PlugStatus
  - PlugDiff

There were two causes for these errors:
  - A bug in VIM itself (resolved by patch 9.2.6) with how compound
    PowerShell commands are handled by the 'system()' vimscript
    function.
  - A bug in the 's:vim8' branch of the private 'vim-plug' function
    's:spawn()' where the actual command to be executed was escaped
    for 'cmd.exe' but the 'cd' prefix added by 's:with_cd()" was escaped
    for PowerShell.
2026-05-17 10:51:31 +09:00
jgb 34467fc07d
Use job API for system() calls in GUI Vim to avoid overhead (#1325)
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
2026-02-19 01:23:49 +09:00
Junegunn Choi d72ac8734f
Update README 2026-02-09 09:43:54 +09:00
Junegunn Choi 60dd7a5eaf
Update README 2026-02-02 09:41:17 +09:00
Evelyn Schneider 3f17a5cc3d
Fix PlugStatus reporting divergence when a tag is specified (#1321) 2025-11-06 13:41:49 +09:00
Junegunn Choi 904dac1530
Update README.md 2025-09-03 09:57:35 +09:00
Subhaditya Nath baa66bcf34
Allow command modifiers for on-demand-loading commands (#1110)
Before this commit, commands like this -
	:tab Git diff HEAD^ HEAD
were interpreted like this -
	:Git diff HEAD^ HEAD
This commit fixes that issue.
2025-03-29 14:17:01 +09:00
Junegunn Choi cdea0244a5
Add g:plug_disable_credential_helper (default: 1)
Some checks failed
Test vim-plug / build (vim) (push) Has been cancelled
This can be set to '0' not to disable git credential helper.

    let g:plug_disable_credential_helper = 0

See #1026
Close #1047
2025-03-13 00:18:41 +09:00
Junegunn Choi 9ef7739c81
Update README
Some checks failed
Test vim-plug / build (vim) (push) Has been cancelled
2025-02-27 14:01:38 +09:00
Eric Chen 7cadca537e
Update README.md (#1305)
Some checks failed
Test vim-plug / build (vim) (push) Has been cancelled
2025-02-09 09:19:56 +09:00
Junegunn Choi a7d4a73dd6
Update README
Some checks failed
Test vim-plug / build (vim) (push) Has been cancelled
2025-02-02 23:28:24 +09:00
4 changed files with 146 additions and 95 deletions

View file

@ -1,37 +0,0 @@
<!--
### Before Submitting
- You checked the [faq](https://github.com/junegunn/vim-plug/wiki/faq) for common problems.
- Check your [requirements](https://github.com/junegunn/vim-plug/wiki/requirements) are satisfied.
- You are not going to suggest vim-plug manage itself like Vundle, see #240, #364, #367 ...
-->
Explain the problem here ...
------------------------------
<!-- Paste your Plug block (from `call plug#begin()` to `call plug#end()`) -->
```
```
<!-- Paste the contents of `:version` below -->
```
```
<!-- Check all that apply. -->
- Type:
- [ ] Bug
- [ ] Enhancement
- [ ] Feature Request
- [ ] Question
- OS:
- [ ] All/Other
- [ ] Linux
- [ ] macOS
- [ ] Windows
- Vim:
- [ ] Terminal Vim
- [ ] GVim
- [ ] Neovim

59
.github/ISSUE_TEMPLATE/issue.yml vendored Normal file
View file

@ -0,0 +1,59 @@
name: Issue
description: File an issue
body:
- type: markdown
attributes:
value: |
### Before Submitting
- You checked the [faq](https://github.com/junegunn/vim-plug/wiki/faq) for common problems.
- Check your [requirements](https://github.com/junegunn/vim-plug/wiki/requirements) are satisfied.
- type: textarea
id: description
attributes:
label: Description
placeholder: Explain the problem here
- type: textarea
id: plug-block
attributes:
label: Plug block
description: Paste your Plug block (from `call plug#begin()` to `call plug#end()`)
render: vim
- type: textarea
id: version
attributes:
label: ":version"
description: Paste the contents of `:version` below
render: text
- type: checkboxes
id: type
attributes:
label: Type
options:
- label: Bug
- label: Enhancement
- label: Feature Request
- label: Question
- type: checkboxes
id: os
attributes:
label: OS
options:
- label: All/Other
- label: Linux
- label: macOS
- label: Windows
- type: checkboxes
id: vim
attributes:
label: Vim
options:
- label: Terminal Vim
- label: GVim
- label: Neovim

View file

@ -1,21 +1,3 @@
<div align="center">
<sup>Special thanks to:</sup>
<br>
<br>
<a href="https://warp.dev/?utm_source=github&utm_medium=referral&utm_campaign=vimplug_20240209">
<div>
<img src="https://raw.githubusercontent.com/junegunn/i/master/warp.png" width="300" alt="Warp">
</div>
<b>Warp is a modern, Rust-based terminal with AI built in so you and your team can build great software, faster.</b>
<div>
<sup>Visit warp.dev to learn more.</sup>
</div>
</a>
<br>
<hr>
</div>
<br>
<h1 title="vim-plug">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./plug-dark.png">

127
plug.vim
View file

@ -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('<sfile>: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
@ -372,8 +373,10 @@ function! plug#end()
for [cmd, names] in items(lod.cmd)
execute printf(
\ 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
\ cmd, string(cmd), string(names))
\ has('patch-7.4.1898')
\ ? 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, <q-mods> ,%s)'
\ : 'command! -nargs=* -range -bang -complete=file %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)'
\ , cmd, string(cmd), string(names))
endfor
for [map, names] in items(lod.map)
@ -651,11 +654,19 @@ function! s:lod_ft(pat, names)
call s:doautocmd('filetypeindent', 'FileType')
endfunction
function! s:lod_cmd(cmd, bang, l1, l2, args, names)
call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
call s:dobufread(a:names)
execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
endfunction
if has('patch-7.4.1898')
function! s:lod_cmd(cmd, bang, l1, l2, args, mods, names)
call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
call s:dobufread(a:names)
execute printf('%s %s%s%s %s', a:mods, (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
endfunction
else
function! s:lod_cmd(cmd, bang, l1, l2, args, names)
call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
call s:dobufread(a:names)
execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
endfunction
endif
function! s:lod_map(map, names, with_prefix, prefix)
call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
@ -986,7 +997,7 @@ function! s:bang(cmd, ...)
let [sh, shellcmdflag, shrd] = s:chsh(a:0)
" FIXME: Escaping is incomplete. We could use shellescape with eval,
" but it won't work on Windows.
let cmd = a:0 ? s:with_cd(a:cmd, a:1) : a:cmd
let cmd = a:0 ? s:with_cd(a:cmd, a:1, {'shell': s:is_win ? 'cmd.exe' : &shell}) : a:cmd
if s:is_win
let [batchfile, cmd] = s:batchfile(cmd)
endif
@ -1075,15 +1086,19 @@ function! s:hash_match(a, b)
return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0
endfunction
function! s:disable_credential_helper()
return s:git_version_requirement(2) && get(g:, 'plug_disable_credential_helper', 1)
endfunction
function! s:checkout(spec)
let sha = a:spec.commit
let output = s:git_revision(a:spec.dir)
let error = 0
if !empty(output) && !s:hash_match(sha, s:lines(output)[0])
let credential_helper = s:git_version_requirement(2) ? '-c credential.helper= ' : ''
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
@ -1289,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, '')
@ -1297,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))
@ -1305,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()
@ -1447,7 +1462,7 @@ function! s:spawn(name, spec, queue, opts)
elseif s:vim8
let cmd = join(map(copy(argv), 'plug#shellescape(v:val, {"script": 0})'))
if has_key(a:opts, 'dir')
let cmd = s:with_cd(cmd, a:opts.dir, 0)
let cmd = s:with_cd(cmd, a:opts.dir, {'shell': s:is_win ? 'cmd.exe' : 'sh', 'script': 0})
endif
let argv = s:is_win ? ['cmd', '/s', '/c', '"'.cmd.'"'] : ['sh', '-c', cmd]
let jid = job_start(s:is_win ? join(argv, ' ') : argv, {
@ -1466,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
@ -1589,7 +1604,7 @@ while 1 " Without TCO, Vim stack is bound to explode
let [error, _] = s:git_validate(spec, 0)
if empty(error)
if pull
let cmd = s:git_version_requirement(2) ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
let cmd = s:disable_credential_helper() ? ['git', '-c', 'credential.helper=', 'fetch'] : ['git', 'fetch']
if has_tag && !empty(globpath(spec.dir, '.git/shallow'))
call extend(cmd, ['--depth', '99999999'])
endif
@ -2313,11 +2328,34 @@ function! s:format_message(bullet, name, message)
endfunction
function! s:with_cd(cmd, dir, ...)
let script = a:0 > 0 ? a:1 : 1
let pwsh = s:is_powershell(&shell)
let opts = a:0 > 0 && type(a:1) == s:TYPE.dict ? a:1 : {}
let opts.shell = get(opts, 'shell', &shell)
let opts.script = get(opts, 'script', 1)
let pwsh = s:is_powershell(opts.shell)
let cd = s:is_win && !pwsh ? 'cd /d' : 'cd'
let sep = pwsh ? ';' : '&&'
return printf('%s %s %s %s', cd, plug#shellescape(a:dir, {'script': script, 'shell': &shell}), sep, a:cmd)
let pwsh_block_required = pwsh && !has('patch-9.2.6')
let start = pwsh_block_required ? '& { ' : ''
let end = pwsh_block_required ? ' }' : ''
return printf('%s%s %s %s %s%s', start, cd, plug#shellescape(a:dir, opts), sep, a:cmd, end)
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'
sleep 1m
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, ...)
@ -2329,7 +2367,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)
@ -2339,12 +2379,17 @@ function! s:system(cmd, ...)
let cmd = a:cmd
endif
if a:0 > 0
let cmd = s:with_cd(cmd, a:1, type(a:cmd) != s:TYPE.list)
let cmd = s:with_cd(cmd, a:1, {'script': type(a:cmd) != s:TYPE.list})
endif
if s:is_win && type(a:cmd) != s:TYPE.list
if s:is_win && type(a:cmd) != s:TYPE.list && !s:is_powershell(&shell)
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)
@ -2355,7 +2400,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)
@ -2369,7 +2414,9 @@ function! s:git_validate(spec, check_branch)
let err = join(['Invalid URI: '.remote,
\ 'Expected: '.a:spec.uri,
\ 'PlugClean required.'], "\n")
elseif a:check_branch && has_key(a:spec, 'commit')
elseif !a:check_branch
return ['', 0]
elseif has_key(a:spec, 'commit')
let sha = s:git_revision(a:spec.dir)
if empty(sha)
let err = join(add(result, 'PlugClean required.'), "\n")
@ -2378,18 +2425,16 @@ function! s:git_validate(spec, check_branch)
\ a:spec.commit[:6], sha[:6]),
\ 'PlugUpdate required.'], "\n")
endif
elseif has_key(a:spec, 'tag')
let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
if a:spec.tag !=# tag && a:spec.tag !~ '\*'
let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
\ (empty(tag) ? 'N/A' : tag), a:spec.tag)
endif
elseif a:check_branch
let current_branch = result[0]
" Check tag
let origin_branch = s:git_origin_branch(a:spec)
if has_key(a:spec, 'tag')
let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
if a:spec.tag !=# tag && a:spec.tag !~ '\*'
let err = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
\ (empty(tag) ? 'N/A' : tag), a:spec.tag)
endif
" Check branch
elseif origin_branch !=# current_branch
if origin_branch !=# current_branch
let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
\ current_branch, origin_branch)
endif
@ -2398,7 +2443,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
@ -2424,9 +2469,11 @@ endfunction
function! s:rm_rf(dir)
if isdirectory(a:dir)
return s:system(s:is_win
\ ? 'rmdir /S /Q '.plug#shellescape(a:dir)
\ : ['rm', '-rf', a:dir])
return s:system(!s:is_win
\ ? ['rm', '-rf', a:dir]
\ : s:is_powershell(&shell)
\ ? ['Remove-Item', '-Recurse', '-Force', a:dir]
\ : 'rmdir /S /Q '.plug#shellescape(a:dir))
endif
endfunction
@ -2548,7 +2595,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