mirror of
https://github.com/neovim/neovim
synced 2025-07-20 21:32:16 +00:00
feat(terminal): TermClose: set exit code in v:event.status #15406
Closes #4713
This commit is contained in:
@ -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|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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;
|
||||||
|
Reference in New Issue
Block a user