degesch: try to abstract GNU Readline

This commit is contained in:
Přemysl Eric Janouch 2015-05-05 03:23:53 +02:00
parent 87afccc568
commit 87843f47e4
1 changed files with 439 additions and 322 deletions

761
degesch.c
View File

@ -67,6 +67,289 @@ enum
#include <readline/readline.h>
#include <readline/history.h>
// --- 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-<key> 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-<key> 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);