mirror of
https://github.com/neovim/neovim
synced 2025-07-15 16:51:49 +00:00
fix(tui): ensure all pending escape sequences are processed before exiting #32151
Problem:
Neovim disables a number of terminal modes when it exits, some of which
cause the terminal to send asynchronous events to Neovim. It's possible
that Neovim exits before the terminal has received and processed all of
the sequences to disable these modes, causing the terminal to emit one
of these asynchronous sequences after Neovim has already exited. If this
happens, then the sequence is received by the user's shell (or some
other program that is not Neovim).
Solution:
When Neovim exits, it now emits a Device Attributes request (DA1)
after disabling all of the different modes. When the terminal responds
to this request we know that it has already received all of our other
sequences disabling the other modes. At that point, it should not be
emitting any further asynchronous sequences. This means the process of
exiting Neovim is now asynchronous as well since it depends on receiving
the DA1 response from the terminal.
(cherry picked from commit 82f08f33c1
)
This commit is contained in:
committed by
github-actions[bot]
parent
b9c9b15ad7
commit
646a8f663e
@ -642,24 +642,17 @@ static void handle_unknown_csi(TermInput *input, const TermKeyKey *key)
|
||||
switch (initial) {
|
||||
case '?':
|
||||
// Kitty keyboard protocol query response.
|
||||
if (input->waiting_for_kkp_response) {
|
||||
input->waiting_for_kkp_response = false;
|
||||
input->key_encoding = kKeyEncodingKitty;
|
||||
tui_set_key_encoding(input->tui_data);
|
||||
}
|
||||
|
||||
input->key_encoding = kKeyEncodingKitty;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
switch (initial) {
|
||||
case '?':
|
||||
// Primary Device Attributes response
|
||||
if (input->waiting_for_kkp_response) {
|
||||
input->waiting_for_kkp_response = false;
|
||||
|
||||
// Enable the fallback key encoding (if any)
|
||||
tui_set_key_encoding(input->tui_data);
|
||||
// Primary Device Attributes (DA1) response
|
||||
if (input->callbacks.primary_device_attr) {
|
||||
input->callbacks.primary_device_attr(input->tui_data);
|
||||
input->callbacks.primary_device_attr = NULL;
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -17,6 +17,10 @@ typedef enum {
|
||||
kKeyEncodingXterm, ///< Xterm's modifyOtherKeys encoding (XTMODKEYS)
|
||||
} KeyEncoding;
|
||||
|
||||
typedef struct {
|
||||
void (*primary_device_attr)(TUIData *tui);
|
||||
} TermInputCallbacks;
|
||||
|
||||
#define KEY_BUFFER_SIZE 0x1000
|
||||
typedef struct {
|
||||
int in_fd;
|
||||
@ -24,8 +28,7 @@ typedef struct {
|
||||
int8_t paste;
|
||||
bool ttimeout;
|
||||
|
||||
bool waiting_for_kkp_response; ///< True if we are expecting to receive a response to a query for
|
||||
///< Kitty keyboard protocol support
|
||||
TermInputCallbacks callbacks;
|
||||
|
||||
KeyEncoding key_encoding; ///< The key encoding used by the terminal emulator
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "nvim/cursor_shape.h"
|
||||
#include "nvim/event/defs.h"
|
||||
#include "nvim/event/loop.h"
|
||||
#include "nvim/event/multiqueue.h"
|
||||
#include "nvim/event/signal.h"
|
||||
#include "nvim/event/stream.h"
|
||||
#include "nvim/globals.h"
|
||||
@ -46,6 +47,10 @@
|
||||
# include "nvim/os/os_win_console.h"
|
||||
#endif
|
||||
|
||||
// Maximum amount of time (in ms) to wait to receive a Device Attributes
|
||||
// response before exiting.
|
||||
#define EXIT_TIMEOUT_MS 1000
|
||||
|
||||
#define OUTBUF_SIZE 0xffff
|
||||
|
||||
#define TOO_MANY_EVENTS 1000000
|
||||
@ -100,6 +105,7 @@ struct TUIData {
|
||||
bool bce;
|
||||
bool mouse_enabled;
|
||||
bool mouse_move_enabled;
|
||||
bool mouse_enabled_save;
|
||||
bool title_enabled;
|
||||
bool sync_output;
|
||||
bool busy, is_invisible, want_invisible;
|
||||
@ -287,7 +293,8 @@ void tui_enable_extended_underline(TUIData *tui)
|
||||
static void tui_query_kitty_keyboard(TUIData *tui)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
tui->input.waiting_for_kkp_response = true;
|
||||
// Set the key encoding whenever the Device Attributes (DA1) response is received.
|
||||
tui->input.callbacks.primary_device_attr = tui_set_key_encoding;
|
||||
out(tui, S_LEN("\x1b[?u\x1b[c"));
|
||||
}
|
||||
|
||||
@ -505,8 +512,8 @@ static void terminfo_start(TUIData *tui)
|
||||
flush_buf(tui);
|
||||
}
|
||||
|
||||
/// Disable the alternate screen and prepare for the TUI to close.
|
||||
static void terminfo_stop(TUIData *tui)
|
||||
/// Disable various terminal modes and other features.
|
||||
static void terminfo_disable(TUIData *tui)
|
||||
{
|
||||
// Disable theme update notifications. We do this first to avoid getting any
|
||||
// more notifications after we reset the cursor and any color palette changes.
|
||||
@ -531,16 +538,6 @@ static void terminfo_stop(TUIData *tui)
|
||||
|
||||
// May restore old title before exiting alternate screen.
|
||||
tui_set_title(tui, NULL_STRING);
|
||||
if (ui_client_exit_status == 0) {
|
||||
ui_client_exit_status = tui->seen_error_exit;
|
||||
}
|
||||
// if nvim exited with nonzero status, without indicated this was an
|
||||
// intentional exit (like `:1cquit`), it likely was an internal failure.
|
||||
// Don't clobber the stderr error message in this case.
|
||||
if (ui_client_exit_status == tui->seen_error_exit) {
|
||||
// Exit alternate screen.
|
||||
unibi_out(tui, unibi_exit_ca_mode);
|
||||
}
|
||||
if (tui->cursor_has_color) {
|
||||
unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color);
|
||||
}
|
||||
@ -549,6 +546,29 @@ static void terminfo_stop(TUIData *tui)
|
||||
// Disable focus reporting
|
||||
unibi_out_ext(tui, tui->unibi_ext.disable_focus_reporting);
|
||||
|
||||
// Send a DA1 request. When the terminal responds we know that it has
|
||||
// processed all of our requests and won't be emitting anymore sequences.
|
||||
out(tui, S_LEN("\x1b[c"));
|
||||
|
||||
// Immediately flush the buffer and wait for the DA1 response.
|
||||
flush_buf(tui);
|
||||
}
|
||||
|
||||
/// Disable the alternate screen and prepare for the TUI to close.
|
||||
static void terminfo_stop(TUIData *tui)
|
||||
{
|
||||
if (ui_client_exit_status == 0) {
|
||||
ui_client_exit_status = tui->seen_error_exit;
|
||||
}
|
||||
|
||||
// if nvim exited with nonzero status, without indicated this was an
|
||||
// intentional exit (like `:1cquit`), it likely was an internal failure.
|
||||
// Don't clobber the stderr error message in this case.
|
||||
if (ui_client_exit_status == tui->seen_error_exit) {
|
||||
// Exit alternate screen.
|
||||
unibi_out(tui, unibi_exit_ca_mode);
|
||||
}
|
||||
|
||||
flush_buf(tui);
|
||||
uv_tty_reset_mode();
|
||||
uv_close((uv_handle_t *)&tui->output_handle, NULL);
|
||||
@ -583,8 +603,14 @@ static void tui_terminal_after_startup(TUIData *tui)
|
||||
flush_buf(tui);
|
||||
}
|
||||
|
||||
/// Stop the terminal but allow it to restart later (like after suspend)
|
||||
static void tui_terminal_stop(TUIData *tui)
|
||||
void tui_error_exit(TUIData *tui, Integer status)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
tui->seen_error_exit = (int)status;
|
||||
}
|
||||
|
||||
void tui_stop(TUIData *tui)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
if (uv_is_closing((uv_handle_t *)&tui->output_handle)) {
|
||||
// Race between SIGCONT (tui.c) and SIGHUP (os/signal.c)? #8075
|
||||
@ -592,28 +618,42 @@ static void tui_terminal_stop(TUIData *tui)
|
||||
tui->stopped = true;
|
||||
return;
|
||||
}
|
||||
|
||||
tui->input.callbacks.primary_device_attr = tui_stop_cb;
|
||||
terminfo_disable(tui);
|
||||
|
||||
// Wait until DA1 response is received
|
||||
LOOP_PROCESS_EVENTS_UNTIL(tui->loop, tui->loop->events, EXIT_TIMEOUT_MS, tui->stopped);
|
||||
|
||||
tui_terminal_stop(tui);
|
||||
stream_set_blocking(tui->input.in_fd, true); // normalize stream (#2598)
|
||||
tinput_destroy(&tui->input);
|
||||
signal_watcher_stop(&tui->winch_handle);
|
||||
signal_watcher_close(&tui->winch_handle, NULL);
|
||||
uv_close((uv_handle_t *)&tui->startup_delay_timer, NULL);
|
||||
}
|
||||
|
||||
/// Callback function called when the response to the Device Attributes (DA1)
|
||||
/// request is sent during shutdown.
|
||||
static void tui_stop_cb(TUIData *tui)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
tui->stopped = true;
|
||||
}
|
||||
|
||||
/// Stop the terminal but allow it to restart later (like after suspend)
|
||||
///
|
||||
/// This is called after we receive the response to the DA1 request sent from
|
||||
/// terminfo_disable.
|
||||
static void tui_terminal_stop(TUIData *tui)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
tinput_stop(&tui->input);
|
||||
// Position the cursor on the last screen line, below all the text
|
||||
cursor_goto(tui, tui->height - 1, 0);
|
||||
terminfo_stop(tui);
|
||||
}
|
||||
|
||||
void tui_error_exit(TUIData *tui, Integer status)
|
||||
{
|
||||
tui->seen_error_exit = (int)status;
|
||||
}
|
||||
|
||||
void tui_stop(TUIData *tui)
|
||||
{
|
||||
tui_terminal_stop(tui);
|
||||
stream_set_blocking(tui->input.in_fd, true); // normalize stream (#2598)
|
||||
tinput_destroy(&tui->input);
|
||||
tui->stopped = true;
|
||||
signal_watcher_stop(&tui->winch_handle);
|
||||
signal_watcher_close(&tui->winch_handle, NULL);
|
||||
uv_close((uv_handle_t *)&tui->startup_delay_timer, NULL);
|
||||
}
|
||||
|
||||
/// Returns true if UI `ui` is stopped.
|
||||
bool tui_is_stopped(TUIData *tui)
|
||||
{
|
||||
@ -1572,7 +1612,16 @@ void tui_suspend(TUIData *tui)
|
||||
// on a non-UNIX system, this is a no-op
|
||||
#ifdef UNIX
|
||||
ui_client_detach();
|
||||
bool enable_mouse = tui->mouse_enabled;
|
||||
tui->mouse_enabled_save = tui->mouse_enabled;
|
||||
tui->input.callbacks.primary_device_attr = tui_suspend_cb;
|
||||
terminfo_disable(tui);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef UNIX
|
||||
static void tui_suspend_cb(TUIData *tui)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
tui_terminal_stop(tui);
|
||||
stream_set_blocking(tui->input.in_fd, true); // normalize stream (#2598)
|
||||
|
||||
@ -1580,13 +1629,13 @@ void tui_suspend(TUIData *tui)
|
||||
|
||||
tui_terminal_start(tui);
|
||||
tui_terminal_after_startup(tui);
|
||||
if (enable_mouse) {
|
||||
if (tui->mouse_enabled_save) {
|
||||
tui_mouse_on(tui);
|
||||
}
|
||||
stream_set_blocking(tui->input.in_fd, false); // libuv expects this
|
||||
ui_client_attach(tui->width, tui->height, tui->term, tui->rgb);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
void tui_set_title(TUIData *tui, String title)
|
||||
{
|
||||
@ -2464,7 +2513,9 @@ static void augment_terminfo(TUIData *tui, const char *term, int vte_version, in
|
||||
|
||||
if (!kitty && (vte_version == 0 || vte_version >= 5400)) {
|
||||
// Fallback to Xterm's modifyOtherKeys if terminal does not support the
|
||||
// Kitty keyboard protocol
|
||||
// Kitty keyboard protocol. We don't actually enable the key encoding here
|
||||
// though: it won't be enabled until the terminal responds to our query for
|
||||
// kitty keyboard support.
|
||||
tui->input.key_encoding = kKeyEncodingXterm;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user