feat(terminal): TermClose: set exit code in v:event.status #15406

Closes #4713
This commit is contained in:
Gregory Anders
2021-08-20 11:45:28 -06:00
committed by GitHub
parent 599af74514
commit 50b30de200
8 changed files with 43 additions and 13 deletions

View File

@ -914,6 +914,8 @@ TermLeave After leaving |Terminal-mode|.
After TermClose. After TermClose.
*TermClose* *TermClose*
TermClose When a |terminal| job ends. TermClose When a |terminal| job ends.
Sets these |v:event| keys:
status
*TermResponse* *TermResponse*
TermResponse After the response to t_RV is received from TermResponse After the response to t_RV is received from
the terminal. The value of |v:termresponse| the terminal. The value of |v:termresponse|

View File

@ -1641,6 +1641,7 @@ v:event Dictionary of event data for the current |autocommand|. Valid
|v:false| if not. |v:false| if not.
changed_window Is |v:true| if the the event fired changed_window Is |v:true| if the the event fired
while changing window (or tab) on |DirChanged|. while changing window (or tab) on |DirChanged|.
status Job status or exit code, -1 means "unknown". |TermClose|
*v:exception* *exception-variable* *v:exception* *exception-variable*
v:exception The value of the exception most recently caught and not v:exception The value of the exception most recently caught and not

View File

@ -134,6 +134,10 @@ Example: >
programs can set this by emitting an escape sequence. programs can set this by emitting an escape sequence.
- |'channel'| Terminal PTY |job-id|. Can be used with |chansend()| to send - |'channel'| Terminal PTY |job-id|. Can be used with |chansend()| to send
input to the terminal. input to the terminal.
- The |TermClose| event gives the terminal job exit code in the |v:event|
"status" field. For example, this autocmd closes terminal buffers if the job
exited without error: >
autocmd TermClose * if !v:event.status | exe 'bdelete! '..expand('<abuf>') | endif
Use |jobwait()| to check if the terminal job has finished: > Use |jobwait()| to check if the terminal job has finished: >
let running = jobwait([&channel], 0)[0] == -1 let running = jobwait([&channel], 0)[0] == -1

View File

@ -532,7 +532,7 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
} }
if (buf->terminal) { if (buf->terminal) {
terminal_close(buf->terminal, NULL); terminal_close(buf->terminal, -1);
} }
// Always remove the buffer when there is no file name. // Always remove the buffer when there is no file name.

View File

@ -698,9 +698,7 @@ static void channel_process_exit_cb(Process *proc, int status, void *data)
{ {
Channel *chan = data; Channel *chan = data;
if (chan->term) { if (chan->term) {
char msg[sizeof("\r\n[Process exited ]") + NUMBUFLEN]; terminal_close(chan->term, status);
snprintf(msg, sizeof msg, "\r\n[Process exited %d]", proc->status);
terminal_close(chan->term, msg);
} }
// If process did not exit, we only closed the handle of a detached process. // If process did not exit, we only closed the handle of a detached process.

View File

@ -260,7 +260,7 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
return rv; return rv;
} }
void terminal_close(Terminal *term, char *msg) void terminal_close(Terminal *term, int status)
{ {
if (term->closed) { if (term->closed) {
return; return;
@ -278,8 +278,8 @@ void terminal_close(Terminal *term, char *msg)
buf_T *buf = handle_get_buffer(term->buf_handle); buf_T *buf = handle_get_buffer(term->buf_handle);
term->closed = true; term->closed = true;
if (!msg || exiting) { if (status == -1 || exiting) {
// If no msg was given, this was called by close_buffer(buffer.c). Or if // If status is -1, this was called by close_buffer(buffer.c). Or if
// exiting, we must inform the buffer the terminal no longer exists so that // exiting, we must inform the buffer the terminal no longer exists so that
// close_buffer() doesn't call this again. // close_buffer() doesn't call this again.
term->buf_handle = 0; term->buf_handle = 0;
@ -291,11 +291,16 @@ void terminal_close(Terminal *term, char *msg)
term->opts.close_cb(term->opts.data); term->opts.close_cb(term->opts.data);
} }
} else { } else {
char msg[sizeof("\r\n[Process exited ]") + NUMBUFLEN];
snprintf(msg, sizeof msg, "\r\n[Process exited %d]", status);
terminal_receive(term, msg, strlen(msg)); terminal_receive(term, msg, strlen(msg));
} }
if (buf) { if (buf && !is_autocmd_blocked()) {
dict_T *dict = get_vim_var_dict(VV_EVENT);
tv_dict_add_nr(dict, S_LEN("status"), status);
apply_autocmds(EVENT_TERMCLOSE, NULL, NULL, false, buf); apply_autocmds(EVENT_TERMCLOSE, NULL, NULL, false, buf);
tv_dict_clear(dict);
} }
} }

View File

@ -13,7 +13,7 @@ describe('autocmd TermClose', function()
before_each(function() before_each(function()
clear() clear()
nvim('set_option', 'shell', nvim_dir .. '/shell-test') nvim('set_option', 'shell', nvim_dir .. '/shell-test')
nvim('set_option', 'shellcmdflag', 'EXE') command('set shellcmdflag=EXE shellredir= shellpipe= shellquote= shellxquote=')
end) end)
it('triggers when fast-exiting terminal job stops', function() it('triggers when fast-exiting terminal job stops', function()
@ -90,6 +90,17 @@ describe('autocmd TermClose', function()
retry(nil, nil, function() eq('3', eval('g:abuf')) end) retry(nil, nil, function() eq('3', eval('g:abuf')) end)
feed('<c-c>:qa!<cr>') feed('<c-c>:qa!<cr>')
end) end)
it('exposes v:event.status', function()
command('set shellcmdflag=EXIT')
command('autocmd TermClose * let g:status = v:event.status')
command('terminal 0')
retry(nil, nil, function() eq(0, eval('g:status')) end)
command('terminal 42')
retry(nil, nil, function() eq(42, eval('g:status')) end)
end)
end) end)
it('autocmd TermEnter, TermLeave', function() it('autocmd TermEnter, TermLeave', function()

View File

@ -19,7 +19,7 @@ static void flush_wait(void)
static void help(void) static void help(void)
{ {
puts("A simple implementation of a shell for testing termopen()."); puts("Fake shell");
puts(""); puts("");
puts("Usage:"); puts("Usage:");
puts(" shell-test --help"); puts(" shell-test --help");
@ -42,6 +42,8 @@ static void help(void)
puts(" 96: foo bar"); puts(" 96: foo bar");
puts(" shell-test INTERACT"); puts(" shell-test INTERACT");
puts(" Prints \"interact $ \" to stderr, and waits for \"exit\" input."); puts(" Prints \"interact $ \" to stderr, and waits for \"exit\" input.");
puts(" shell-test EXIT {code}");
puts(" Exits immediately with exit code \"{code}\".");
} }
int main(int argc, char **argv) int main(int argc, char **argv)
@ -103,7 +105,6 @@ int main(int argc, char **argv)
char input[256]; char input[256];
char cmd[100]; char cmd[100];
int arg; int arg;
int input_argc;
while (1) { while (1) {
fprintf(stderr, "interact $ "); fprintf(stderr, "interact $ ");
@ -112,8 +113,7 @@ int main(int argc, char **argv)
break; // EOF break; // EOF
} }
input_argc = sscanf(input, "%99s %d", cmd, &arg); if(1 == sscanf(input, "%99s %d", cmd, &arg)) {
if(1 == input_argc) {
arg = 0; arg = 0;
} }
if (strcmp(cmd, "exit") == 0) { if (strcmp(cmd, "exit") == 0) {
@ -122,6 +122,15 @@ int main(int argc, char **argv)
fprintf(stderr, "command not found: %s\n", cmd); fprintf(stderr, "command not found: %s\n", cmd);
} }
} }
} else if (strcmp(argv[1], "EXIT") == 0) {
int code = 1;
if (argc >= 3) {
if (sscanf(argv[2], "%d", &code) != 1) {
fprintf(stderr, "Invalid exit code: %s\n", argv[2]);
return 2;
}
}
return code;
} else { } else {
fprintf(stderr, "Unknown first argument: %s\n", argv[1]); fprintf(stderr, "Unknown first argument: %s\n", argv[1]);
return 3; return 3;