diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 2a5cc28eea..bec75cee85 100644 --- 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 index 8be108540e..e91ef758fe 100644 --- 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 index b7df98934d..be1fabad45 100644 --- 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 index 72fd009c32..feca1bb112 100644 --- 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 index e687490704..f9fbecf05d 100644 --- 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 index e711611d67..04bf21c514 100644 --- 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 index d6164cec29..87f9275199 100644 --- 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]+Unknown first argument%: foo[\r\n]+%(1 of 1%)%: Unknown first argument%: foo', - out - ) + matches('[\r\n]+.*[\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 "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('LastExitCode%s+ready [$]%s+[(]', out) + 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%^%[%[0m[\r\n]+shell returned 3[\r\n]+%(1 of 1%)%: Unknown first argument%: foo', + out + ) + end) + + 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('.*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 index 45965d8791..b47143e791 100644 --- 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('') 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 index 0a12cf0920..44426fb991 100644 --- 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 index 2813128aae..d568e71336 100644 --- 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') 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') screen:expect { - any = [[Executing command: .?& { Get%-Content .* | & sort %-r } 2>&1 | %%{ "$_" } | Out%-File .*; exit $LastExitCode"]], + any = [[Executing command: " $input | sort %-r".*]], } end feed('') - 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') + screen:expect { + any = [[Executing command: " $input | Sort%-Object %-Descending".*]], + } + feed('') + 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') - 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('') + + if not n.has_powershell() then + return + end + n.set_shell_powershell(true) feed(':4verbose %w !sort') screen:expect { - any = [[Executing command: .?& { Get%-Content .* | & sort }]], + any = [[Executing command: " $input | sort".*]], } feed('') n.expect_exit(command, 'qall!') diff --git a/test/old/testdir/setup.vim b/test/old/testdir/setup.vim index c077eb021a..a6c7917f63 100644 --- 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^=.