fix(tui): don't overwrite an assertion faliure message on exit

If nvim exited with nonzero status this is for one of the two reasons
- `:cquit` was invoked. This is used by users and plugins to communicate
  a result, like a nonzero status will fail a `git commit` operation
- There was an internal error or deadly signal. in this case an error
  message was likely written to stderr or to the screen.

In the latter case, the error message was often hidden by the TUI
exiting altscreen mode, which erases all visible terminal text.

This change prevents this in the latter case, while still cleaning up
the terminal properly when `:cquit` was deliberatily invoked.
Other cleanup like exiting mouse mode and raw mode is still done.
This commit is contained in:
bfredl 2023-09-21 10:18:37 +02:00
parent f246cf029f
commit 911f3d9623
5 changed files with 24 additions and 3 deletions

View File

@ -167,4 +167,7 @@ void msg_history_show(Array entries)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void msg_history_clear(void)
FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY;
void error_exit(Integer status)
FUNC_API_SINCE(12);
#endif // NVIM_API_UI_EVENTS_IN_H

View File

@ -423,6 +423,7 @@ static void exit_event(void **argv)
if (!exiting) {
if (ui_client_channel_id) {
ui_client_exit_status = status;
os_exit(status);
} else {
assert(status == 0); // Called from rpc_close(), which passes 0 as status.

View File

@ -4591,7 +4591,9 @@ static void ex_cquit(exarg_T *eap)
FUNC_ATTR_NORETURN
{
// this does not always pass on the exit code to the Manx compiler. why?
getout(eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE);
int status = eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE;
ui_call_error_exit(status);
getout(status);
}
/// Do preparations for "qall" and "wqall".

View File

@ -145,6 +145,7 @@ struct TUIData {
} unibi_ext;
char *space_buf;
bool stopped;
int seen_error_exit;
int width;
int height;
bool rgb;
@ -162,6 +163,7 @@ void tui_start(TUIData **tui_p, int *width, int *height, char **term)
tui->is_starting = true;
tui->screenshot = NULL;
tui->stopped = false;
tui->seen_error_exit = 0;
tui->loop = &main_loop;
kv_init(tui->invalid_regions);
signal_watcher_init(tui->loop, &tui->winch_handle, tui);
@ -384,8 +386,13 @@ static void terminfo_stop(TUIData *tui)
unibi_out_ext(tui, tui->unibi_ext.disable_extended_keys);
// May restore old title before exiting alternate screen.
tui_set_title(tui, (String)STRING_INIT);
// Exit alternate screen.
unibi_out(tui, unibi_exit_ca_mode);
// 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_color_changed) {
unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color);
}
@ -443,6 +450,11 @@ static void tui_terminal_stop(TUIData *tui)
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);

View File

@ -23,6 +23,9 @@ EXTERN sattr_T *grid_line_buf_attr INIT(= NULL);
// ID of the ui client channel. If zero, the client is not running.
EXTERN uint64_t ui_client_channel_id INIT(= 0);
// exit status from embedded nvim process
EXTERN int ui_client_exit_status INIT(= 0);
// TODO(bfredl): the current structure for how tui and ui_client.c communicate is a bit awkward.
// This will be restructured as part of The UI Devirtualization Project.