From 0c89854da1893c9d9d5da1994bea69df31fc2a6f Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Fri, 20 Aug 2021 14:58:28 -0600 Subject: [PATCH 1/4] feat(terminal): close shell terminals automatically When starting a terminal buffer that uses the default value of &shell (e.g. when using `:terminal` or `termopen([&shell])`), automatically close the buffer if the shell exits without an error. --- src/nvim/eval/funcs.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 177f64ebba..70e687ce73 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -8502,6 +8502,16 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) channel_terminal_open(curbuf, chan); channel_create_event(chan, NULL); + + do_cmdline_cmd("augroup nvim_terminal_close"); + do_cmdline_cmd("autocmd! TermClose " + " if !v:event.status |" + " let info = nvim_get_chan_info(&channel) |" + " if info.argv ==# [&shell] |" + " exec 'bdelete! ' .. expand('') |" + " endif |" + " endif"); + do_cmdline_cmd("augroup END"); } /// "timer_info([timer])" function From 673ee213e9d0900b1fa2638dc5373ee8167ed4fa Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sat, 4 Sep 2021 15:28:08 -0600 Subject: [PATCH 2/4] test: update tests for auto-closing :term buffers The terminal buffer closes automatically when using `:terminal` and the command exits without an error. This messes up some tests that expect the terminal buffer to still be open. We can force the buffer not to close by passing an argument to `:terminal`. This can be anything, since the shell-test stub simply prints whatever argument it's given. --- test/functional/autocmd/termxx_spec.lua | 3 ++- test/functional/terminal/ex_terminal_spec.lua | 25 ++++++++++++------- test/functional/terminal/scrollback_spec.lua | 4 ++- test/functional/terminal/tui_spec.lua | 18 ++++++------- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/test/functional/autocmd/termxx_spec.lua b/test/functional/autocmd/termxx_spec.lua index 359203f945..802020494b 100644 --- a/test/functional/autocmd/termxx_spec.lua +++ b/test/functional/autocmd/termxx_spec.lua @@ -103,12 +103,13 @@ describe('autocmd TermClose', function() it('reports the correct ', function() command('set hidden') + command('set shellcmdflag=EXE') command('autocmd TermClose * let g:abuf = expand("")') command('edit foo') command('edit bar') eq(2, eval('bufnr("%")')) - command('terminal') + command('terminal ls') retry(nil, nil, function() eq(3, eval('bufnr("%")')) end) command('buffer 1') diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index 5204b61f57..6020a12ddb 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -137,23 +137,29 @@ describe(':terminal (with fake shell)', function() -- shell-test.c is a fake shell that prints its arguments and exits. nvim('set_option_value', 'shell', testprg('shell-test'), {}) nvim('set_option_value', 'shellcmdflag', 'EXE', {}) + nvim('set_option_value', 'shellxquote', '', {}) end) -- Invokes `:terminal {cmd}` using a fake shell (shell-test.c) which prints - -- the {cmd} and exits immediately . + -- the {cmd} and exits immediately. + -- When no argument is given and the exit code is zero, the terminal buffer + -- closes automatically. local function terminal_with_fake_shell(cmd) feed_command("terminal "..(cmd and cmd or "")) end it('with no argument, acts like termopen()', function() skip(is_os('win')) - terminal_with_fake_shell() + -- Use the EXIT subcommand to end the process with a non-zero exit code to + -- prevent the buffer from closing automatically + nvim('set_option_value', 'shellcmdflag', 'EXIT', {}) + terminal_with_fake_shell(1) retry(nil, 4 * screen.timeout, function() screen:expect([[ - ^ready $ | - [Process exited 0] | + ^ | + [Process exited 1] | | - :terminal | + :terminal 1 | ]]) end) end) @@ -245,12 +251,13 @@ describe(':terminal (with fake shell)', function() it('works with :find', function() skip(is_os('win')) - terminal_with_fake_shell() + nvim('set_option_value', 'shellcmdflag', 'EXIT', {}) + terminal_with_fake_shell(1) screen:expect([[ - ^ready $ | - [Process exited 0] | + ^ | + [Process exited 1] | | - :terminal | + :terminal 1 | ]]) eq('term://', string.match(eval('bufname("%")'), "^term://")) feed([[]]) diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index d20f5177b8..ba0b663285 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -514,7 +514,9 @@ describe("'scrollback' option", function() -- _Global_ scrollback=-1 defaults :terminal to 10_000. command('setglobal scrollback=-1') - command('terminal') + -- Pass a command to prevent the terminal buffer from automatically + -- closing. The ':' command is just a no-op. + command('terminal :') eq(10000, meths.get_option_value('scrollback', {})) -- _Local_ scrollback=-1 in :terminal forces the _maximum_. diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index e8e65d18fa..1958281592 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -2075,26 +2075,26 @@ describe('TUI FocusGained/FocusLost', function() end) it('in terminal-mode', function() - feed_data(':set shell='..testprg('shell-test')..'\n') + feed_data(':set shell='..testprg('shell-test')..' shellcmdflag=EXE\n') feed_data(':set noshowmode laststatus=0\n') - feed_data(':terminal\n') + feed_data(':terminal zia\n') -- Wait for terminal to be ready. screen:expect{grid=[[ - {1:r}eady $ | + {1:r}eady $ zia | + | [Process exited 0] | | | - | - :terminal | + :terminal zia | {3:-- TERMINAL --} | ]]} feed_data('\027[I') screen:expect{grid=[[ - {1:r}eady $ | - [Process exited 0] | + {1:r}eady $ zia | | + [Process exited 0] | | | gained | @@ -2103,9 +2103,9 @@ describe('TUI FocusGained/FocusLost', function() feed_data('\027[O') screen:expect([[ - {1:r}eady $ | - [Process exited 0] | + {1:r}eady $ zia | | + [Process exited 0] | | | lost | From 6e703f778fe7836663ad93761db676d5e2528d3e Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sat, 4 Sep 2021 15:42:00 -0600 Subject: [PATCH 3/4] fix: handle argv not present in nvim_get_chan_info --- src/nvim/eval/funcs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 70e687ce73..aebd6b25f6 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -8507,7 +8507,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) do_cmdline_cmd("autocmd! TermClose " " if !v:event.status |" " let info = nvim_get_chan_info(&channel) |" - " if info.argv ==# [&shell] |" + " if get(info, 'argv', []) ==# [&shell] |" " exec 'bdelete! ' .. expand('') |" " endif |" " endif"); From 3fb372eba48796b5d0a7758f91e168be8e70e183 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Thu, 10 Aug 2023 09:53:56 -0500 Subject: [PATCH 4/4] Use Lua autocommand and make TermClose autocommand global --- runtime/doc/news.txt | 4 ++++ runtime/doc/various.txt | 4 ++++ runtime/doc/vim_diff.txt | 2 ++ runtime/lua/vim/_editor.lua | 17 +++++++++++++++-- src/nvim/eval/funcs.c | 10 ---------- 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 713569e1ad..179cdfef25 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -181,6 +181,10 @@ The following changes to existing APIs or features add new behavior. supports it, unless |'keywordprg'| was customized before calling |vim.lsp.start()|. +• Terminal buffers started with no arguments (and use 'shell') close + automatically if the job exited without error, eliminating the (often + unwanted) "[Process exited 0]" message. + ============================================================================== REMOVED FEATURES *news-removed* diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 52e8f4d86c..33f57580c7 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -249,6 +249,10 @@ gx Opens the current filepath or URL (decided by Fails if changes have been made to the current buffer, unless 'hidden' is set. + If {cmd} is omitted, and the 'shell' job exits with no + error, the buffer is closed automatically + |default-autocmds|. + To enter |Terminal-mode| automatically: > autocmd TermOpen * startinsert < diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 15bb13d5a9..07b4572a27 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -133,6 +133,8 @@ remove them and ":autocmd {group}" to see how they're defined. nvim_terminal: - BufReadCmd: Treats "term://" buffers as |terminal| buffers. |terminal-start| +- TermClose: A |terminal| buffer started with no arguments (which thus uses + 'shell') and which exits with no error is closed automatically. nvim_cmdwin: - CmdwinEnter: Limits syntax sync to maxlines=1 in the |cmdwin|. diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 58fbc923e1..8c10cc7da3 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -1107,13 +1107,26 @@ end function vim._init_default_autocmds() local nvim_terminal_augroup = vim.api.nvim_create_augroup('nvim_terminal', {}) - vim.api.nvim_create_autocmd({ 'bufreadcmd' }, { + vim.api.nvim_create_autocmd({ 'BufReadCmd' }, { pattern = 'term://*', group = nvim_terminal_augroup, nested = true, command = "if !exists('b:term_title')|call termopen(matchstr(expand(\"\"), '\\c\\mterm://\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), {'cwd': expand(get(matchlist(expand(\"\"), '\\c\\mterm://\\(.\\{-}\\)//'), 1, ''))})", }) - vim.api.nvim_create_autocmd({ 'cmdwinenter' }, { + vim.api.nvim_create_autocmd({ 'TermClose' }, { + group = nvim_terminal_augroup, + desc = 'Automatically close terminal buffers when started with no arguments and exiting without an error', + callback = function(args) + if vim.v.event.status == 0 then + local info = vim.api.nvim_get_chan_info(vim.bo[args.buf].channel) + local argv = info.argv or {} + if #argv == 1 and argv[1] == vim.o.shell then + vim.cmd({ cmd = 'bdelete', args = { args.buf }, bang = true }) + end + end + end, + }) + vim.api.nvim_create_autocmd({ 'CmdwinEnter' }, { pattern = '[:>]', group = vim.api.nvim_create_augroup('nvim_cmdwin', {}), command = 'syntax sync minlines=1 maxlines=1', diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index aebd6b25f6..177f64ebba 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -8502,16 +8502,6 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) channel_terminal_open(curbuf, chan); channel_create_event(chan, NULL); - - do_cmdline_cmd("augroup nvim_terminal_close"); - do_cmdline_cmd("autocmd! TermClose " - " if !v:event.status |" - " let info = nvim_get_chan_info(&channel) |" - " if get(info, 'argv', []) ==# [&shell] |" - " exec 'bdelete! ' .. expand('') |" - " endif |" - " endif"); - do_cmdline_cmd("augroup END"); } /// "timer_info([timer])" function