commit d77d961b350da580c3fb71847b3eec7563273494
parent c8fbb0d2ee05d05f5971b132c3b38cb0141db235
Author: Alexej Kowalew <616b2f@gmail.com>
Date: Sat, 12 Apr 2025 17:24:42 +0200
feat(defaults): shelltemp=false #33012
Co-authored-by: zeertzjq <zeertzjq@outlook.com>
Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
Diffstat:
11 files changed, 137 insertions(+), 61 deletions(-)
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
@@ -81,6 +81,7 @@ OPTIONS
• 'chistory' and 'lhistory' set size of the |quickfix-stack|.
• 'diffopt' `inline:` configures diff highlighting for changes within a line.
• 'pummaxwidth' sets maximum width for the completion popup menu.
+• 'shelltemp' defaults to "false".
PLUGINS
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
@@ -5411,7 +5411,7 @@ A jump table for the options with a short description can be found at |Q_op|.
< Also see 'completeslash'.
*'shelltemp'* *'stmp'* *'noshelltemp'* *'nostmp'*
-'shelltemp' 'stmp' boolean (default on)
+'shelltemp' 'stmp' boolean (default off)
global
When on, use temp files for shell commands. When off use a pipe.
When using a pipe is not possible temp files are used anyway.
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
@@ -5716,7 +5716,7 @@ vim.go.ssl = vim.go.shellslash
--- `system()` does not respect this option, it always uses pipes.
---
--- @type boolean
-vim.o.shelltemp = true
+vim.o.shelltemp = false
vim.o.stmp = vim.o.shelltemp
vim.go.shelltemp = vim.o.shelltemp
vim.go.stmp = vim.go.shelltemp
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
@@ -1146,7 +1146,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
}
// Create the shell command in allocated memory.
- char *cmd_buf = make_filter_cmd(cmd, itmp, otmp);
+ char *cmd_buf = make_filter_cmd(cmd, itmp, otmp, do_in);
ui_cursor_goto(Rows - 1, 0);
if (do_out) {
@@ -1344,8 +1344,9 @@ static char *find_pipe(const char *cmd)
/// @param cmd Command to execute.
/// @param itmp NULL or the input file.
/// @param otmp NULL or the output file.
+/// @param do_in true if stdin is needed.
/// @returns an allocated string with the shell command.
-char *make_filter_cmd(char *cmd, char *itmp, char *otmp)
+char *make_filter_cmd(char *cmd, char *itmp, char *otmp, bool do_in)
{
bool is_fish_shell =
#if defined(UNIX)
@@ -1367,6 +1368,11 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp)
len += is_pwsh ? strlen(itmp) + sizeof("& { Get-Content " " | & " " }") - 1 + 6 // +6: #20530
: strlen(itmp) + sizeof(" { " " < " " } ") - 1;
}
+
+ if (do_in && is_pwsh) {
+ len += sizeof(" $input | ");
+ }
+
if (otmp != NULL) {
len += strlen(otmp) + strlen(p_srr) + 2; // two extra spaces (" "),
}
@@ -1380,8 +1386,11 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp)
xstrlcat(buf, " | & ", len - 1); // FIXME: add `&` ourself or leave to user?
xstrlcat(buf, cmd, len - 1);
xstrlcat(buf, " }", len - 1);
+ } else if (do_in) {
+ xstrlcpy(buf, " $input | ", len - 1);
+ xstrlcat(buf, cmd, len);
} else {
- xstrlcpy(buf, cmd, len - 1);
+ xstrlcpy(buf, cmd, len);
}
} else {
#if defined(UNIX)
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
@@ -7621,7 +7621,7 @@ local options = {
},
{
abbreviation = 'stmp',
- defaults = true,
+ defaults = false,
desc = [=[
When on, use temp files for shell commands. When off use a pipe.
When using a pipe is not possible temp files are used anyway.
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
@@ -778,7 +778,7 @@ char *get_cmd_output(char *cmd, char *infile, int flags, size_t *ret_len)
}
// Add the redirection stuff
- char *command = make_filter_cmd(cmd, infile, tempname);
+ char *command = make_filter_cmd(cmd, infile, tempname, false);
// Call the shell to execute the command (errors are ignored).
// Don't check timestamps here.
@@ -1253,11 +1253,19 @@ static size_t write_output(char *output, size_t remaining, bool eof)
char *start = output;
size_t off = 0;
while (off < remaining) {
- if (output[off] == NL) {
+ // CRLF
+ if (output[off] == CAR && output[off + 1] == NL) {
+ output[off] = NUL;
+ ml_append(curwin->w_cursor.lnum++, output, (int)off + 1, false);
+ size_t skip = off + 2;
+ output += skip;
+ remaining -= skip;
+ off = 0;
+ continue;
+ } else if (output[off] == CAR || output[off] == NL) {
// Insert the line
output[off] = NUL;
- ml_append(curwin->w_cursor.lnum++, output, (int)off + 1,
- false);
+ ml_append(curwin->w_cursor.lnum++, output, (int)off + 1, false);
size_t skip = off + 1;
output += skip;
remaining -= skip;
@@ -1276,7 +1284,7 @@ static size_t write_output(char *output, size_t remaining, bool eof)
if (remaining) {
// append unfinished line
ml_append(curwin->w_cursor.lnum++, output, 0, false);
- // remember that the NL was missing
+ // remember that the line ending was missing
curbuf->b_no_eol_lnum = curwin->w_cursor.lnum;
output += remaining;
} else {
diff --git a/test/functional/ex_cmds/make_spec.lua b/test/functional/ex_cmds/make_spec.lua
@@ -23,22 +23,47 @@ describe(':make', function()
n.set_shell_powershell()
end)
- it('captures stderr & non zero exit code #14349', function()
+ it('captures stderr & non zero exit code using "commands" #14349', function()
+ api.nvim_set_option_value('makeprg', testprg('shell-test') .. ' foo', {})
+ local out = eval('execute("make")')
+ -- Error message is captured in the file and printed in the footer
+ matches('[\r\n]+.*[\r\n]+%(1 of 1%)%: Unknown first argument%: foo', out)
+ end)
+
+ it('captures stderr & zero exit code using "commands" #14349', function()
+ api.nvim_set_option_value('makeprg', testprg('shell-test'), {})
+ local out = eval('execute("make")')
+ -- Ensure there are no "shell returned X" messages between
+ -- command and last line (indicating zero exit)
+ matches('[\n]+%(1 of 1%)%: ready [$]', out)
+ end)
+
+ it('captures stderr & non zero exit code using "cmdlets"', function()
+ api.nvim_set_option_value(
+ 'shellpipe',
+ '2>&1 | Tee-Object -FilePath %s; exit $LastExitCode',
+ {}
+ )
api.nvim_set_option_value('makeprg', testprg('shell-test') .. ' foo', {})
local out = eval('execute("make")')
-- Error message is captured in the file and printed in the footer
matches(
- '[\r\n]+.*[\r\n]+Unknown first argument%: foo[\r\n]+%(1 of 1%)%: Unknown first argument%: foo',
+ '[\r\n]+.*[\r\n]+.*Unknown first argument%: foo%^%[%[0m[\r\n]+shell returned 3[\r\n]+%(1 of 1%)%: Unknown first argument%: foo',
out
)
end)
- it('captures stderr & zero exit code #14349', function()
+ it('captures stderr & zero exit code using "cmdlets"', function()
+ api.nvim_set_option_value(
+ 'shellpipe',
+ '2>&1 | Tee-Object -FilePath %s; exit $LastExitCode',
+ {}
+ )
api.nvim_set_option_value('makeprg', testprg('shell-test'), {})
local out = eval('execute("make")')
-- Ensure there are no "shell returned X" messages between
-- command and last line (indicating zero exit)
- matches('LastExitCode%s+ready [$]%s+[(]', out)
+ matches('.*ready [$]%s+%^%[%[0m', out)
matches('\n.*%: ready [$]', out)
end)
end)
diff --git a/test/functional/legacy/011_autocommands_spec.lua b/test/functional/legacy/011_autocommands_spec.lua
@@ -35,6 +35,11 @@ local function prepare_gz_file(name, text)
eq(nil, vim.uv.fs_stat(name))
end
+local function prepare_file(name, text)
+ os.remove(name)
+ write_file(name, text)
+end
+
describe('file reading, writing and bufnew and filter autocommands', function()
local text1 = dedent([[
start of testfile
@@ -63,23 +68,27 @@ describe('file reading, writing and bufnew and filter autocommands', function()
end)
teardown(function()
os.remove('Xtestfile.gz')
+ os.remove('Xtestfile')
+ os.remove('XtestfileByFileReadPost')
os.remove('Xtest.c')
os.remove('test.out')
end)
+ it('FileReadPost', function()
+ feed_command('set bin')
+ prepare_file('Xtestfile', text1)
+ os.remove('XtestfileByFileReadPost')
+ --execute('au FileChangedShell * echo "caught FileChangedShell"')
+ feed_command("au FileReadPost Xtestfile '[,']w XtestfileByFileReadPost")
+ -- Read the testfile.
+ feed_command('$r Xtestfile')
+ expect('\n' .. text1)
+ eq(text1, read_file('XtestfileByFileReadPost'))
+ end)
+
if not has_gzip() then
pending('skipped (missing `gzip` utility)', function() end)
else
- it('FileReadPost (using gzip)', function()
- prepare_gz_file('Xtestfile', text1)
- --execute('au FileChangedShell * echo "caught FileChangedShell"')
- feed_command('set bin')
- feed_command("au FileReadPost *.gz '[,']!gzip -d")
- -- Read and decompress the testfile.
- feed_command('$r Xtestfile.gz')
- expect('\n' .. text1)
- end)
-
it('BufReadPre, BufReadPost (using gzip)', function()
prepare_gz_file('Xtestfile', text1)
local gzip_data = read_file('Xtestfile.gz')
@@ -112,18 +121,18 @@ describe('file reading, writing and bufnew and filter autocommands', function()
-- Discard all prompts and messages.
feed('<C-L>')
expect([[
-
- start of testfiLe
- Line 2 Abcdefghijklmnopqrstuvwxyz
- Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- Line 4 Abcdefghijklmnopqrstuvwxyz
- Line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- Line 6 Abcdefghijklmnopqrstuvwxyz
- Line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- Line 8 Abcdefghijklmnopqrstuvwxyz
- Line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- Line 10 Abcdefghijklmnopqrstuvwxyz
- end of testfiLe]])
+
+ start of testfiLe
+ Line 2 Abcdefghijklmnopqrstuvwxyz
+ Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ Line 4 Abcdefghijklmnopqrstuvwxyz
+ Line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ Line 6 Abcdefghijklmnopqrstuvwxyz
+ Line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ Line 8 Abcdefghijklmnopqrstuvwxyz
+ Line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ Line 10 Abcdefghijklmnopqrstuvwxyz
+ end of testfiLe]])
end)
end
diff --git a/test/functional/testnvim.lua b/test/functional/testnvim.lua
@@ -642,7 +642,7 @@ function M.source(code)
end
function M.has_powershell()
- return M.eval('executable("' .. (is_os('win') and 'powershell' or 'pwsh') .. '")') == 1
+ return M.eval('executable("pwsh")') == 1
end
--- Sets Nvim shell to powershell.
@@ -655,7 +655,7 @@ function M.set_shell_powershell(fake)
if not fake then
assert(found)
end
- local shell = found and (is_os('win') and 'powershell' or 'pwsh') or M.testprg('pwsh-test')
+ local shell = found and 'pwsh' or M.testprg('pwsh-test')
local cmd = 'Remove-Item -Force '
.. table.concat(
is_os('win') and { 'alias:cat', 'alias:echo', 'alias:sleep', 'alias:sort', 'alias:tee' }
@@ -671,7 +671,7 @@ function M.set_shell_powershell(fake)
let &shellcmdflag .= '$PSDefaultParameterValues[''Out-File:Encoding'']=''utf8'';'
let &shellcmdflag .= ']] .. cmd .. [['
let &shellredir = '2>&1 | %%{ "$_" } | Out-File %s; exit $LastExitCode'
- let &shellpipe = '2>&1 | %%{ "$_" } | tee %s; exit $LastExitCode'
+ let &shellpipe = '> %s 2>&1'
]])
return found
end
diff --git a/test/functional/vimscript/system_spec.lua b/test/functional/vimscript/system_spec.lua
@@ -558,9 +558,12 @@ end)
describe('shell :!', function()
before_each(clear)
- it(':{range}! with powershell filter/redirect #16271 #19250', function()
+ it(':{range}! with powershell using "commands" filter/redirect #16271 #19250', function()
+ if not n.has_powershell() then
+ return
+ end
local screen = Screen.new(500, 8)
- local found = n.set_shell_powershell(true)
+ n.set_shell_powershell()
insert([[
3
1
@@ -569,23 +572,44 @@ describe('shell :!', function()
if is_os('win') then
feed(':4verbose %!sort /R<cr>')
screen:expect {
- any = [[Executing command: .?& { Get%-Content .* | & sort /R } 2>&1 | %%{ "$_" } | Out%-File .*; exit $LastExitCode"]],
+ any = [[Executing command: " $input | sort /R".*]],
}
else
feed(':4verbose %!sort -r<cr>')
screen:expect {
- any = [[Executing command: .?& { Get%-Content .* | & sort %-r } 2>&1 | %%{ "$_" } | Out%-File .*; exit $LastExitCode"]],
+ any = [[Executing command: " $input | sort %-r".*]],
}
end
feed('<CR>')
- if found then
- -- Not using fake powershell, so we can test the result.
- expect([[
- 4
- 3
- 2
- 1]])
+ expect([[
+ 4
+ 3
+ 2
+ 1]])
+ end)
+
+ it(':{range}! with powershell using "cmdlets" filter/redirect #16271 #19250', function()
+ if not n.has_powershell() then
+ pending('powershell not found', function() end)
+ return
end
+ local screen = Screen.new(500, 8)
+ n.set_shell_powershell()
+ insert([[
+ 3
+ 1
+ 4
+ 2]])
+ feed(':4verbose %!Sort-Object -Descending<cr>')
+ screen:expect {
+ any = [[Executing command: " $input | Sort%-Object %-Descending".*]],
+ }
+ feed('<CR>')
+ expect([[
+ 4
+ 3
+ 2
+ 1]])
end)
it(':{range}! without redirecting to buffer', function()
@@ -596,20 +620,19 @@ describe('shell :!', function()
4
2]])
feed(':4verbose %w !sort<cr>')
- if is_os('win') then
- screen:expect {
- any = [[Executing command: .?sort %< .*]],
- }
- else
- screen:expect {
- any = [[Executing command: .?%(sort%) %< .*]],
- }
- end
+ screen:expect {
+ any = [[Executing command: "sort".*]],
+ }
feed('<CR>')
+
+ if not n.has_powershell() then
+ return
+ end
+
n.set_shell_powershell(true)
feed(':4verbose %w !sort<cr>')
screen:expect {
- any = [[Executing command: .?& { Get%-Content .* | & sort }]],
+ any = [[Executing command: " $input | sort".*]],
}
feed('<CR>')
n.expect_exit(command, 'qall!')
diff --git a/test/old/testdir/setup.vim b/test/old/testdir/setup.vim
@@ -19,6 +19,7 @@ if exists('s:did_load')
set nohlsearch noincsearch
set nrformats=bin,octal,hex
set shortmess=filnxtToOS
+ set shelltemp
set sidescroll=0
set tags=./tags,tags
set undodir^=.