mirror of
https://github.com/neovim/neovim.git
synced 2026-03-28 03:12:00 +00:00
refactor(terminal): impl "[Process exited]" in Lua #38343
Problem: "[Process exited]" is implemented in C with anonymous namespace and users have no way to hide it. Solution: - Handle "TermClose" event in Lua. - User can delete the "nvim.terminal" augroup to avoid "[Process exited]".
This commit is contained in:
@@ -180,6 +180,7 @@ 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.
|
||||
- TermClose: Displays the "[Process exited]" virtual text.
|
||||
- TermRequest: The terminal emulator responds to OSC background and foreground
|
||||
requests, indicating (1) a black background and white foreground when Nvim
|
||||
option 'background' is "dark" or (2) a white background and black foreground
|
||||
|
||||
@@ -578,6 +578,54 @@ do
|
||||
end,
|
||||
})
|
||||
|
||||
local nvim_terminal_exitmsg_ns = vim.api.nvim_create_namespace('nvim.terminal.exitmsg')
|
||||
|
||||
--- @param buf integer
|
||||
--- @param msg string
|
||||
--- @param pos integer
|
||||
local function set_terminal_exitmsg(buf, msg, pos)
|
||||
vim.api.nvim_buf_set_extmark(buf, nvim_terminal_exitmsg_ns, pos, 0, {
|
||||
virt_text = { { msg, nil } },
|
||||
virt_text_pos = 'overlay',
|
||||
})
|
||||
end
|
||||
|
||||
vim.api.nvim_create_autocmd('TermClose', {
|
||||
group = nvim_terminal_augroup,
|
||||
nested = true,
|
||||
desc = 'Displays the "[Process exited]" virtual text',
|
||||
callback = function(ev)
|
||||
if not vim.api.nvim_buf_is_valid(ev.buf) then
|
||||
return
|
||||
end
|
||||
|
||||
local buf = vim.bo[ev.buf]
|
||||
local pos = ev.data.pos ---@type integer
|
||||
local buf_has_exitmsg = #(
|
||||
vim.api.nvim_buf_get_extmarks(ev.buf, nvim_terminal_exitmsg_ns, 0, -1, {})
|
||||
) > 0
|
||||
|
||||
-- `nvim_open_term` buffers do not have any attached chan
|
||||
local msg = buf.channel == 0 and '[Terminal closed]'
|
||||
or ('[Process exited %d]'):format(vim.v.event.status)
|
||||
|
||||
-- TermClose may be queued before TermOpen if the process
|
||||
-- exits before `terminal_open` is called. Don't display
|
||||
-- the msg now, let TermOpen display it.
|
||||
if buf.buftype ~= 'terminal' or buf_has_exitmsg then
|
||||
vim.api.nvim_create_autocmd('TermOpen', {
|
||||
buffer = ev.buf,
|
||||
once = true,
|
||||
callback = function()
|
||||
set_terminal_exitmsg(ev.buf, msg, pos)
|
||||
end,
|
||||
})
|
||||
return
|
||||
end
|
||||
set_terminal_exitmsg(ev.buf, msg, pos)
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd('TermRequest', {
|
||||
group = nvim_terminal_augroup,
|
||||
desc = 'Handles OSC foreground/background color requests',
|
||||
@@ -694,6 +742,9 @@ do
|
||||
vim.keymap.set({ 'n', 'x', 'o' }, ']]', function()
|
||||
jump_to_prompt(nvim_terminal_prompt_ns, 0, ev.buf, vim.v.count1)
|
||||
end, { buffer = ev.buf, desc = 'Jump [count] shell prompts forward' })
|
||||
|
||||
-- If the terminal buffer is being reused, clear the previous exit msg
|
||||
vim.api.nvim_buf_clear_namespace(ev.buf, nvim_terminal_exitmsg_ns, 0, -1)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
@@ -64,7 +64,6 @@
|
||||
#include "nvim/event/multiqueue.h"
|
||||
#include "nvim/event/time.h"
|
||||
#include "nvim/ex_docmd.h"
|
||||
#include "nvim/extmark.h"
|
||||
#include "nvim/getchar.h"
|
||||
#include "nvim/globals.h"
|
||||
#include "nvim/grid.h"
|
||||
@@ -211,7 +210,6 @@ struct terminal {
|
||||
VTermTerminator termrequest_terminator; ///< Terminator (BEL or ST) used in the termrequest
|
||||
|
||||
size_t refcount; // reference count
|
||||
uint32_t exitmsg_id;
|
||||
};
|
||||
|
||||
static VTermScreenCallbacks vterm_screen_callbacks = {
|
||||
@@ -681,10 +679,6 @@ void terminal_close(Terminal **termpp, int status)
|
||||
// If called from buf_close_terminal() after the process has already exited, we
|
||||
// only need to call the close callback to clean up the terminal object.
|
||||
only_destroy = true;
|
||||
// Buffer may be reused so delete the "[Process exited]" msg
|
||||
if (buf) {
|
||||
extmark_del_id(buf, (uint32_t)-1, term->exitmsg_id);
|
||||
}
|
||||
} else {
|
||||
// flush any pending changes to the buffer
|
||||
if (!exiting) {
|
||||
@@ -695,6 +689,7 @@ void terminal_close(Terminal **termpp, int status)
|
||||
term->closed = true;
|
||||
}
|
||||
|
||||
int pos = buf ? buf->b_ml.ml_line_count - 1 : 0;
|
||||
if (status == -1 || exiting) {
|
||||
// If this was called by buf_close_terminal() (status is -1), or if exiting, we
|
||||
// must inform the buffer the terminal no longer exists so that buf_freeall()
|
||||
@@ -714,37 +709,15 @@ void terminal_close(Terminal **termpp, int status)
|
||||
} else if (!only_destroy) {
|
||||
// Associated channel has been closed and the editor is not exiting.
|
||||
// Do not call the close callback now. Wait for the user to press a key.
|
||||
char msg[sizeof("[Process exited ]") + NUMBUFLEN];
|
||||
if (((Channel *)term->opts.data)->streamtype == kChannelStreamInternal) {
|
||||
snprintf(msg, sizeof msg, "[Terminal closed]");
|
||||
} else {
|
||||
snprintf(msg, sizeof msg, "[Process exited %d]", status);
|
||||
}
|
||||
|
||||
// Show the msg as virtual text instead of adding it to buffer
|
||||
VirtTextChunk *chunk = xmalloc(sizeof(VirtTextChunk));
|
||||
*chunk = (VirtTextChunk) { .text = xstrdup(msg), .hl_id = -1 };
|
||||
DecorVirtText *virt_text = xmalloc(sizeof(DecorVirtText));
|
||||
*virt_text = (DecorVirtText) {
|
||||
.priority = DECOR_PRIORITY_BASE,
|
||||
.pos = kVPosWinCol,
|
||||
.data.virt_text = { .items = chunk, .size = 1 }
|
||||
};
|
||||
DecorInline decor = {
|
||||
.ext = true, .data.ext = { .sh_idx = DECOR_ID_INVALID, .vt = virt_text }
|
||||
};
|
||||
|
||||
int pos = MIN(row_to_linenr(term, term->cursor.row),
|
||||
buf->b_ml.ml_line_count - 1);
|
||||
extmark_set(buf, (uint32_t)-1, &term->exitmsg_id, pos, 0, -1, 0,
|
||||
decor, 0, true, false, true, false, NULL);
|
||||
|
||||
// Redraw statusline to show the exit code.
|
||||
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
|
||||
if (wp->w_buffer == buf) {
|
||||
wp->w_redr_status = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the line number to display "[Process exited]" virt text
|
||||
pos = MIN(row_to_linenr(term, term->cursor.row), pos);
|
||||
}
|
||||
|
||||
if (only_destroy) {
|
||||
@@ -756,7 +729,13 @@ void terminal_close(Terminal **termpp, int status)
|
||||
dict_T *dict = get_v_event(&save_v_event);
|
||||
tv_dict_add_nr(dict, S_LEN("status"), status);
|
||||
tv_dict_set_keys_readonly(dict);
|
||||
apply_autocmds(EVENT_TERMCLOSE, NULL, NULL, false, buf);
|
||||
|
||||
MAXSIZE_TEMP_DICT(data, 1);
|
||||
PUT_C(data, "pos", INTEGER_OBJ(pos));
|
||||
|
||||
apply_autocmds_group(EVENT_TERMCLOSE, NULL, NULL, status >= 0, AUGROUP_ALL,
|
||||
buf, NULL, &DICT_OBJ(data));
|
||||
|
||||
restore_v_event(dict, &save_v_event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ local function test_terminal_with_fake_shell(backslash)
|
||||
command('terminal')
|
||||
screen:expect([[
|
||||
^ready $ |
|
||||
[Process exited 0] |
|
||||
|
|
||||
|*2
|
||||
]])
|
||||
end)
|
||||
@@ -297,7 +297,7 @@ local function test_terminal_with_fake_shell(backslash)
|
||||
command('terminal')
|
||||
screen:expect([[
|
||||
^ready $ |
|
||||
[Process exited 0] |
|
||||
|
|
||||
|*2
|
||||
]])
|
||||
eq('term://', string.match(eval('bufname("%")'), '^term://'))
|
||||
|
||||
Reference in New Issue
Block a user