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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 43 additions and 13 deletions

View File

@ -914,6 +914,8 @@ TermLeave After leaving |Terminal-mode|.
After TermClose.
*TermClose*
TermClose When a |terminal| job ends.
Sets these |v:event| keys:
status
*TermResponse*
TermResponse After the response to t_RV is received from
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.
changed_window Is |v:true| if the the event fired
while changing window (or tab) on |DirChanged|.
status Job status or exit code, -1 means "unknown". |TermClose|
*v:exception* *exception-variable*
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.
- |'channel'| Terminal PTY |job-id|. Can be used with |chansend()| to send
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: >
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) {
terminal_close(buf->terminal, NULL);
terminal_close(buf->terminal, -1);
}
// 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;
if (chan->term) {
char msg[sizeof("\r\n[Process exited ]") + NUMBUFLEN];
snprintf(msg, sizeof msg, "\r\n[Process exited %d]", proc->status);
terminal_close(chan->term, msg);
terminal_close(chan->term, status);
}
// 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;
}
void terminal_close(Terminal *term, char *msg)
void terminal_close(Terminal *term, int status)
{
if (term->closed) {
return;
@ -278,8 +278,8 @@ void terminal_close(Terminal *term, char *msg)
buf_T *buf = handle_get_buffer(term->buf_handle);
term->closed = true;
if (!msg || exiting) {
// If no msg was given, this was called by close_buffer(buffer.c). Or if
if (status == -1 || exiting) {
// 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
// close_buffer() doesn't call this again.
term->buf_handle = 0;
@ -291,11 +291,16 @@ void terminal_close(Terminal *term, char *msg)
term->opts.close_cb(term->opts.data);
}
} 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));
}
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);
tv_dict_clear(dict);
}
}

View File

@ -13,7 +13,7 @@ describe('autocmd TermClose', function()
before_each(function()
clear()
nvim('set_option', 'shell', nvim_dir .. '/shell-test')
nvim('set_option', 'shellcmdflag', 'EXE')
command('set shellcmdflag=EXE shellredir= shellpipe= shellquote= shellxquote=')
end)
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)
feed('<c-c>:qa!<cr>')
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)
it('autocmd TermEnter, TermLeave', function()

View File

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