diff --git a/degesch.c b/degesch.c index 639b60b..75aff82 100644 --- a/degesch.c +++ b/degesch.c @@ -67,6 +67,289 @@ enum #include #include +// --- User interface ---------------------------------------------------------- + +// Currently provided by GNU Readline. A libedit backend is also possible. + +struct input_buffer +{ + HISTORY_STATE *history; ///< Saved history state + char *saved_line; ///< Saved line content + int saved_point; ///< Saved cursor position + int saved_mark; ///< Saved mark +}; + +static struct input_buffer * +input_buffer_new (void) +{ + struct input_buffer *self = xcalloc (1, sizeof *self); + return self; +} + +static void +input_buffer_destroy (struct input_buffer *self) +{ + // Can't really free "history" from here + free (self->saved_line); + free (self); +} + +struct input +{ + bool active; ///< Are we a thing? + + char *saved_line; ///< Saved line content + int saved_point; ///< Saved cursor position + int saved_mark; ///< Saved mark + + char *prompt; ///< The prompt we use + int prompt_shown; ///< Whether the prompt is shown now + + struct input_buffer *current; ///< Current input buffer +}; + +static void +input_init (struct input *self) +{ + memset (self, 0, sizeof *self); +} + +static void +input_free (struct input *self) +{ + free (self->saved_line); + free (self->prompt); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +#define input_ding(self) rl_ding () + +static void +input_on_terminal_resized (struct input *self) +{ + (void) self; + + // This fucks up big time on terminals with automatic wrapping such as + // rxvt-unicode or newer VTE when the current line overflows, however we + // can't do much about that + rl_resize_terminal (); +} + +static void +input_on_readable (struct input *self) +{ + (void) self; + rl_callback_read_char (); +} + +static void +input_set_prompt (struct input *self, char *prompt) +{ + free (self->prompt); + self->prompt = prompt; + + // First reset the prompt to work around a bug in readline + rl_set_prompt (""); + if (self->prompt_shown) + rl_redisplay (); + + rl_set_prompt (self->prompt); + if (self->prompt_shown) + rl_redisplay (); +} + +static void +input_erase (struct input *self) +{ + (void) self; + + rl_set_prompt (""); + rl_replace_line ("", 0); + rl_point = rl_mark = 0; + rl_redisplay (); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static int app_readline_init (void); +static void on_readline_input (char *line); + +static void +input_start (struct input *self) +{ + rl_startup_hook = app_readline_init; + rl_catch_sigwinch = false; + rl_callback_handler_install (self->prompt, on_readline_input); + self->prompt_shown = 1; + self->active = true; +} + +static void +input_stop (struct input *self) +{ + if (self->prompt_shown > 0) + input_erase (self); + + // This is okay as long as we're not called from within readline + rl_callback_handler_remove (); + self->active = false; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static void +input_save (struct input *self) +{ + hard_assert (!self->saved_line); + + self->saved_point = rl_point; + self->saved_mark = rl_mark; + self->saved_line = rl_copy_text (0, rl_end); +} + +static void +input_restore (struct input *self) +{ + hard_assert (self->saved_line); + + rl_set_prompt (self->prompt); + rl_replace_line (self->saved_line, 0); + rl_point = self->saved_point; + rl_mark = self->saved_mark; + free (self->saved_line); + self->saved_line = NULL; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static void +input_hide (struct input *self) +{ + if (!self->active || self->prompt_shown-- < 1) + return; + + input_save (self); + input_erase (self); +} + +static void +input_show (struct input *self) +{ + if (!self->active || ++self->prompt_shown < 1) + return; + + input_restore (self); + rl_redisplay (); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +// The following part shows you why it's not a good idea to use +// GNU Readline for this kind of software. Or for anything else, really. + +static void +input_save_buffer (struct input *self, struct input_buffer *buffer) +{ + (void) self; + + buffer->history = history_get_history_state (); + buffer->saved_line = rl_copy_text (0, rl_end); + buffer->saved_point = rl_point; + buffer->saved_mark = rl_mark; +} + +static void +input_restore_buffer (struct input *self, struct input_buffer *buffer) +{ + // Restore the target buffer's history + if (buffer->history) + { + // history_get_history_state() just allocates a new HISTORY_STATE + // and fills it with its current internal data. We don't need that + // shell anymore after reviving it. + history_set_history_state (buffer->history); + free (buffer->history); + buffer->history = NULL; + } + else + { + // This should get us a clean history while keeping the flags. + // Note that we've either saved the previous history entries, or we've + // cleared them altogether, so there should be nothing to leak. + HISTORY_STATE *state = history_get_history_state (); + state->offset = state->length = state->size = 0; + history_set_history_state (state); + free (state); + } + + // Try to restore the target buffer's readline state + if (buffer->saved_line) + { + rl_replace_line (buffer->saved_line, 0); + rl_point = buffer->saved_point; + rl_mark = buffer->saved_mark; + free (buffer->saved_line); + buffer->saved_line = NULL; + + if (self->prompt_shown) + rl_redisplay (); + } +} + +static void +input_switch_buffer (struct input *self, struct input_buffer *buffer) +{ + // There could possibly be occurences of the current undo list in some + // history entry. We either need to free the undo list, or move it + // somewhere else to load back later, as the buffer we're switching to + // has its own history state. + rl_free_undo_list (); + + // Save this buffer's history so that it's independent for each buffer + if (self->current) + input_save_buffer (self, self->current); + else + // Just throw it away; there should always be an active buffer however +#if RL_READLINE_VERSION >= 0x0603 + rl_clear_history (); +#else // RL_READLINE_VERSION < 0x0603 + // At least something... this may leak undo entries + clear_history (); +#endif // RL_READLINE_VERSION < 0x0603 + + input_restore_buffer (self, buffer); + self->current = buffer; +} + +static void +input_destroy_buffer (struct input *self, struct input_buffer *buffer) +{ + (void) self; + + // rl_clear_history, being the only way I know of to get rid of the complete + // history including attached data, is a pretty recent addition. *sigh* +#if RL_READLINE_VERSION >= 0x0603 + if (buffer->history) + { + // See buffer_activate() for why we need to do this BS + rl_free_undo_list (); + + // This is probably the only way we can free the history fully + HISTORY_STATE *state = history_get_history_state (); + + history_set_history_state (buffer->history); + free (buffer->history); + rl_clear_history (); + + history_set_history_state (state); + free (state); + } +#endif // RL_READLINE_VERSION + + input_buffer_destroy (buffer); +} + // --- Application data -------------------------------------------------------- // All text stored in our data structures is encoded in UTF-8. @@ -310,12 +593,7 @@ struct buffer enum buffer_type type; ///< Type of the buffer char *name; ///< The name of the buffer - // Readline state: - - HISTORY_STATE *history; ///< Saved history state - char *saved_line; ///< Saved line - int saved_point; ///< Saved position in line - int saved_mark; ///< Saved mark + struct input_buffer *input_data; ///< User interface data // Buffer contents: @@ -336,6 +614,7 @@ static struct buffer * buffer_new (void) { struct buffer *self = xcalloc (1, sizeof *self); + self->input_data = input_buffer_new (); return self; } @@ -343,8 +622,8 @@ static void buffer_destroy (struct buffer *self) { free (self->name); - // Can't really free "history" here - free (self->saved_line); + if (self->input_data) + input_buffer_destroy (self->input_data); LIST_FOR_EACH (struct buffer_line, iter, self->lines) buffer_line_destroy (iter); if (self->user) @@ -501,8 +780,7 @@ struct app_context int lines; ///< Current terminal height int columns; ///< Current ternimal width - char *readline_prompt; ///< The prompt we use for readline - bool readline_prompt_shown; ///< Whether the prompt is shown now + struct input input; ///< User interface } *g_ctx; @@ -539,6 +817,8 @@ app_context_init (struct app_context *self) strerror (errno)); free (encoding); + + input_init (&self->input); } static void @@ -560,7 +840,7 @@ app_context_free (struct app_context *self) iconv_close (self->term_from_utf8); iconv_close (self->term_to_utf8); - free (self->readline_prompt); + input_free (&self->input); } static void refresh_prompt (struct app_context *ctx); @@ -899,47 +1179,6 @@ free_terminal (void) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -struct app_readline_state -{ - char *saved_line; - int saved_point; - int saved_mark; -}; - -static void -app_readline_hide (struct app_readline_state *state) -{ - state->saved_point = rl_point; - state->saved_mark = rl_mark; - state->saved_line = rl_copy_text (0, rl_end); - rl_set_prompt (""); - rl_replace_line ("", 0); - rl_redisplay (); -} - -static void -app_readline_restore (struct app_readline_state *state, const char *prompt) -{ - rl_set_prompt (prompt); - rl_replace_line (state->saved_line, 0); - rl_point = state->saved_point; - rl_mark = state->saved_mark; - rl_redisplay (); - free (state->saved_line); -} - -static void -app_readline_erase_to_bol (const char *prompt) -{ - rl_set_prompt (""); - rl_replace_line ("", 0); - rl_point = rl_mark = 0; - rl_redisplay (); - rl_set_prompt (prompt); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - typedef int (*terminal_printer_fn) (int); static int @@ -991,16 +1230,13 @@ log_message_attributed (void *user_data, const char *quote, const char *fmt, { FILE *stream = stderr; - struct app_readline_state state; - if (g_ctx->readline_prompt_shown) - app_readline_hide (&state); + input_hide (&g_ctx->input); print_attributed (g_ctx, stream, (intptr_t) user_data, "%s", quote); vprint_attributed (g_ctx, stream, (intptr_t) user_data, fmt, ap); fputs ("\n", stream); - if (g_ctx->readline_prompt_shown) - app_readline_restore (&state, g_ctx->readline_prompt); + input_show (&g_ctx->input); } static void @@ -1550,9 +1786,7 @@ buffer_line_display (struct app_context *ctx, free (nick); - struct app_readline_state state; - if (ctx->readline_prompt_shown) - app_readline_hide (&state); + input_hide (&ctx->input); // TODO: write the line to a log file; note that the global and server // buffers musn't collide with filenames @@ -1561,8 +1795,7 @@ buffer_line_display (struct app_context *ctx, formatter_flush (&f, stdout); formatter_free (&f); - if (ctx->readline_prompt_shown) - app_readline_restore (&state, ctx->readline_prompt); + input_show (&ctx->input); } static void @@ -1638,25 +1871,8 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer) // TODO: part from the channel if needed - // rl_clear_history, being the only way I know of to get rid of the complete - // history including attached data, is a pretty recent addition. *sigh* -#if RL_READLINE_VERSION >= 0x0603 - if (buffer->history) - { - // See buffer_activate() for why we need to do this BS - rl_free_undo_list (); - - // This is probably the only way we can free the history fully - HISTORY_STATE *state = history_get_history_state (); - - history_set_history_state (buffer->history); - free (buffer->history); - rl_clear_history (); - - history_set_history_state (state); - free (state); - } -#endif // RL_READLINE_VERSION + input_destroy_buffer (&ctx->input, buffer->input_data); + buffer->input_data = NULL; // And make sure to unlink the buffer from "irc_buffer_map" struct server *s = buffer->server; @@ -1701,65 +1917,7 @@ buffer_activate (struct app_context *ctx, struct buffer *buffer) buffer_line_display (ctx, line, false); buffer->unseen_messages_count = 0; - // The following part shows you why it's not a good idea to use - // GNU Readline for this kind of software. Or for anything else, really. - - // There could possibly be occurences of the current undo list in some - // history entry. We either need to free the undo list, or move it - // somewhere else to load back later, as the buffer we're switching to - // has its own history state. - rl_free_undo_list (); - - // Save this buffer's history so that it's independent for each buffer - if (ctx->current_buffer) - { - ctx->current_buffer->history = history_get_history_state (); - ctx->current_buffer->saved_line = rl_copy_text (0, rl_end); - ctx->current_buffer->saved_point = rl_point; - ctx->current_buffer->saved_mark = rl_mark; - } - else - // Just throw it away; there should always be an active buffer however -#if RL_READLINE_VERSION >= 0x0603 - rl_clear_history (); -#else // RL_READLINE_VERSION < 0x0603 - // At least something... this may leak undo entries - clear_history (); -#endif // RL_READLINE_VERSION < 0x0603 - - // Restore the target buffer's history - if (buffer->history) - { - // history_get_history_state() just allocates a new HISTORY_STATE - // and fills it with its current internal data. We don't need that - // shell anymore after reviving it. - history_set_history_state (buffer->history); - free (buffer->history); - buffer->history = NULL; - } - else - { - // This should get us a clean history while keeping the flags. - // Note that we've either saved the previous history entries, or we've - // cleared them altogether, so there should be nothing to leak. - HISTORY_STATE *state = history_get_history_state (); - state->offset = state->length = state->size = 0; - history_set_history_state (state); - free (state); - } - - // Try to restore the target buffer's readline state - if (buffer->saved_line) - { - rl_replace_line (buffer->saved_line, 0); - rl_point = buffer->saved_point; - rl_mark = buffer->saved_mark; - free (buffer->saved_line); - buffer->saved_line = 0; - - if (ctx->readline_prompt_shown) - rl_redisplay (); - } + input_switch_buffer (&ctx->input, buffer->input_data); // Now at last we can switch the pointers ctx->last_buffer = ctx->current_buffer; @@ -2039,15 +2197,8 @@ try_finish_quit (struct app_context *ctx) static void initiate_quit (struct app_context *ctx) { - // First get rid of readline - if (ctx->readline_prompt_shown) - { - app_readline_erase_to_bol (ctx->readline_prompt); - ctx->readline_prompt_shown = false; - } - - // This is okay as long as we're not called from within readline - rl_callback_handler_remove (); + // Destroy the user interface + input_stop (&ctx->input); buffer_send_status (ctx, ctx->global_buffer, "Shutting down"); @@ -2106,16 +2257,13 @@ irc_send (struct server *s, const char *format, ...) if (g_debug_mode) { - struct app_readline_state state; - if (s->ctx->readline_prompt_shown) - app_readline_hide (&state); + input_hide (&s->ctx->input); char *term = irc_to_term (s->ctx, str.str); fprintf (stderr, "[IRC] <== \"%s\"\n", term); free (term); - if (s->ctx->readline_prompt_shown) - app_readline_restore (&state, s->ctx->readline_prompt); + input_show (&s->ctx->input); } str_append (&str, "\r\n"); @@ -2383,145 +2531,19 @@ refresh_prompt (struct app_context *ctx) make_prompt (ctx, &prompt); str_append_c (&prompt, ' '); - // After building the new prompt, replace the old one - free (ctx->readline_prompt); - if (!have_attributes) - ctx->readline_prompt = xstrdup (prompt.str); + input_set_prompt (&ctx->input, xstrdup (prompt.str)); else { // XXX: to be completely correct, we should use tputs, but we cannot - ctx->readline_prompt = xstrdup_printf ("%c%s%c%s%c%s%c", + input_set_prompt (&ctx->input, xstrdup_printf ("%c%s%c%s%c%s%c", RL_PROMPT_START_IGNORE, ctx->attrs[ATTR_PROMPT], RL_PROMPT_END_IGNORE, prompt.str, RL_PROMPT_START_IGNORE, ctx->attrs[ATTR_RESET], - RL_PROMPT_END_IGNORE); + RL_PROMPT_END_IGNORE)); } str_free (&prompt); - - // First reset the prompt to work around a bug in readline - rl_set_prompt (""); - if (ctx->readline_prompt_shown) - rl_redisplay (); - - rl_set_prompt (ctx->readline_prompt); - if (ctx->readline_prompt_shown) - rl_redisplay (); -} - -static int -on_readline_goto_buffer (int count, int key) -{ - (void) count; - - int n = UNMETA (key) - '0'; - if (n < 0 || n > 9) - return 0; - - // There's no buffer zero - if (n == 0) - n = 10; - - struct app_context *ctx = g_ctx; - if (ctx->last_buffer && buffer_get_index (ctx, ctx->current_buffer) == n) - // Fast switching between two buffers - buffer_activate (ctx, ctx->last_buffer); - else if (!buffer_goto (ctx, n == 0 ? 10 : n)) - rl_ding (); - return 0; -} - -static int -on_readline_previous_buffer (int count, int key) -{ - (void) key; - - struct app_context *ctx = g_ctx; - if (ctx->current_buffer) - buffer_activate (ctx, buffer_previous (ctx, count)); - return 0; -} - -static int -on_readline_next_buffer (int count, int key) -{ - (void) key; - - struct app_context *ctx = g_ctx; - if (ctx->current_buffer) - buffer_activate (ctx, buffer_next (ctx, count)); - return 0; -} - -static int -on_readline_return (int count, int key) -{ - (void) count; - (void) key; - - struct app_context *ctx = g_ctx; - - // Let readline pass the line to our input handler - rl_done = 1; - - // Save readline state - int saved_point = rl_point; - int saved_mark = rl_mark; - char *saved_line = rl_copy_text (0, rl_end); - - // Erase the entire line from screen - rl_set_prompt (""); - rl_replace_line ("", 0); - rl_redisplay (); - ctx->readline_prompt_shown = false; - - // Restore readline state - rl_set_prompt (ctx->readline_prompt); - rl_replace_line (saved_line, 0); - rl_point = saved_point; - rl_mark = saved_mark; - free (saved_line); - return 0; -} - -static void -app_readline_bind_meta (char key, rl_command_func_t cb) -{ - // This one seems to actually work - char keyseq[] = { '\\', 'e', key, 0 }; - rl_bind_keyseq (keyseq, cb); -#if 0 - // While this one only fucks up UTF-8 - // Tested with urxvt and xterm, on Debian Jessie/Arch, default settings - // \M- behaves exactly the same - rl_bind_key (META (key), cb); -#endif -} - -static int -init_readline (void) -{ - // XXX: maybe use rl_make_bare_keymap() and start from there; - // our dear user could potentionally rig things up in a way that might - // result in some funny unspecified behaviour - - rl_add_defun ("previous-buffer", on_readline_previous_buffer, -1); - rl_add_defun ("next-buffer", on_readline_next_buffer, -1); - - // Redefine M-0 through M-9 to switch buffers - for (int i = 0; i <= 9; i++) - app_readline_bind_meta ('0' + i, on_readline_goto_buffer); - - rl_bind_keyseq ("\\C-p", rl_named_function ("previous-buffer")); - rl_bind_keyseq ("\\C-n", rl_named_function ("next-buffer")); - app_readline_bind_meta ('p', rl_named_function ("previous-history")); - app_readline_bind_meta ('n', rl_named_function ("next-history")); - - // We need to hide the prompt first - rl_bind_key (RETURN, on_readline_return); - - return 0; } // --- Input handling ---------------------------------------------------------- @@ -3195,16 +3217,13 @@ irc_process_message (const struct irc_message *msg, if (g_debug_mode) { - struct app_readline_state state; - if (s->ctx->readline_prompt_shown) - app_readline_hide (&state); + input_hide (&s->ctx->input); char *term = irc_to_term (s->ctx, raw); fprintf (stderr, "[IRC] ==> \"%s\"\n", term); free (term); - if (s->ctx->readline_prompt_shown) - app_readline_restore (&state, s->ctx->readline_prompt); + input_show (&s->ctx->input); } // XXX: or is the 001 numeric enough? For what? @@ -4688,6 +4707,132 @@ irc_connect (struct server *s, bool *should_retry, struct error **e) return true; } +// --- User interface actions -------------------------------------------------- + +static int +on_readline_goto_buffer (int count, int key) +{ + (void) count; + + int n = UNMETA (key) - '0'; + if (n < 0 || n > 9) + return 0; + + // There's no buffer zero + if (n == 0) + n = 10; + + struct app_context *ctx = g_ctx; + if (ctx->last_buffer && buffer_get_index (ctx, ctx->current_buffer) == n) + // Fast switching between two buffers + buffer_activate (ctx, ctx->last_buffer); + else if (!buffer_goto (ctx, n == 0 ? 10 : n)) + input_ding (self); + return 0; +} + +static int +on_readline_previous_buffer (int count, int key) +{ + (void) key; + + struct app_context *ctx = g_ctx; + if (ctx->current_buffer) + buffer_activate (ctx, buffer_previous (ctx, count)); + return 0; +} + +static int +on_readline_next_buffer (int count, int key) +{ + (void) key; + + struct app_context *ctx = g_ctx; + if (ctx->current_buffer) + buffer_activate (ctx, buffer_next (ctx, count)); + return 0; +} + +static int +on_readline_return (int count, int key) +{ + (void) count; + (void) key; + + struct input *self = &g_ctx->input; + + // Let readline pass the line to our input handler + rl_done = 1; + + // Hide the line, don't redisplay it + input_hide (self); + input_restore (self); + return 0; +} + +static void +on_readline_input (char *line) +{ + struct input *self = &g_ctx->input; + + if (line) + { + if (*line) + add_history (line); + + process_input (g_ctx, line); + free (line); + } + else + { + input_hide (self); + input_restore (self); + input_ding (self); + } + + if (self->active) + // Readline automatically redisplays it + self->prompt_shown = 1; +} + +static void +app_readline_bind_meta (char key, rl_command_func_t cb) +{ + // This one seems to actually work + char keyseq[] = { '\\', 'e', key, 0 }; + rl_bind_keyseq (keyseq, cb); +#if 0 + // While this one only fucks up UTF-8 + // Tested with urxvt and xterm, on Debian Jessie/Arch, default settings + // \M- behaves exactly the same + rl_bind_key (META (key), cb); +#endif +} + +static int +app_readline_init (void) +{ + // XXX: maybe use rl_make_bare_keymap() and start from there; + // our dear user could potentionally rig things up in a way that might + // result in some funny unspecified behaviour + + rl_add_defun ("previous-buffer", on_readline_previous_buffer, -1); + rl_add_defun ("next-buffer", on_readline_next_buffer, -1); + + // Redefine M-0 through M-9 to switch buffers + for (int i = 0; i <= 9; i++) + app_readline_bind_meta ('0' + i, on_readline_goto_buffer); + + rl_bind_keyseq ("\\C-p", rl_named_function ("previous-buffer")); + rl_bind_keyseq ("\\C-n", rl_named_function ("next-buffer")); + app_readline_bind_meta ('p', rl_named_function ("previous-history")); + app_readline_bind_meta ('n', rl_named_function ("next-history")); + + // We need to hide the prompt first + rl_bind_key (RETURN, on_readline_return); + return 0; +} + // --- I/O event handlers ------------------------------------------------------ static void @@ -4712,10 +4857,7 @@ on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx) if (g_winch_received) { - // This fucks up big time on terminals with automatic wrapping such as - // rxvt-unicode or newer VTE when the current line overflows, however we - // can't do much about that - rl_resize_terminal (); + input_on_terminal_resized (&ctx->input); rl_get_screen_size (&ctx->lines, &ctx->columns); } } @@ -4728,29 +4870,7 @@ on_tty_readable (const struct pollfd *fd, struct app_context *ctx) if (fd->revents & ~(POLLIN | POLLHUP | POLLERR)) print_debug ("fd %d: unexpected revents: %d", fd->fd, fd->revents); - rl_callback_read_char (); -} - -static void -on_readline_input (char *line) -{ - if (line) - { - if (*line) - add_history (line); - - process_input (g_ctx, line); - free (line); - } - else - { - app_readline_erase_to_bol (g_ctx->readline_prompt); - rl_ding (); - } - - // initiate_quit() disables readline; we just wait then - if (!g_ctx->quitting) - g_ctx->readline_prompt_shown = true; + input_on_readable (&ctx->input); } // --- Configuration loading --------------------------------------------------- @@ -4957,18 +5077,15 @@ main (int argc, char *argv[]) init_colors (&ctx); init_poller_events (&ctx); init_buffers (&ctx); - ctx.current_buffer = ctx.server.buffer; + buffer_activate (&ctx, ctx.server.buffer); + refresh_prompt (&ctx); + input_start (&ctx.input); + rl_get_screen_size (&ctx.lines, &ctx.columns); // Connect to the server ASAP poller_timer_set (&ctx.server.reconnect_tmr, 0); - rl_startup_hook = init_readline; - rl_catch_sigwinch = false; - rl_callback_handler_install (ctx.readline_prompt, on_readline_input); - rl_get_screen_size (&ctx.lines, &ctx.columns); - ctx.readline_prompt_shown = true; - ctx.polling = true; while (ctx.polling) poller_run (&ctx.poller);