degesch: more GNU Readline work

I'm not so sure anymore I will be able to achieve my goals with this library.

It's really a terrible mess.  A consistent and neatly formatted terrible mess.
This commit is contained in:
Přemysl Eric Janouch 2015-04-15 02:10:21 +02:00
parent 3df841f088
commit 4a0c774e75
1 changed files with 175 additions and 46 deletions

219
degesch.c
View File

@ -187,6 +187,11 @@ struct buffer
enum buffer_type type; ///< Type of the buffer enum buffer_type type; ///< Type of the buffer
char *name; ///< The name of the buffer char *name; ///< The name of the buffer
HISTORY_STATE *history; ///< Saved history state
char *saved_line; ///< Saved line
int saved_point; ///< Saved position in line
int saved_mark; ///< Saved mark
// Buffer contents: // Buffer contents:
struct buffer_line *lines; ///< All lines in this buffer struct buffer_line *lines; ///< All lines in this buffer
@ -215,6 +220,8 @@ static void
buffer_destroy (struct buffer *self) buffer_destroy (struct buffer *self)
{ {
free (self->name); free (self->name);
// Can't really free "history" here
free (self->saved_line);
free (self->topic); free (self->topic);
str_map_free (&self->nicks); str_map_free (&self->nicks);
free (self); free (self);
@ -361,6 +368,8 @@ app_context_free (struct app_context *self)
free (self->readline_prompt); free (self->readline_prompt);
} }
static void refresh_prompt (struct app_context *ctx);
// --- Attributed output ------------------------------------------------------- // --- Attributed output -------------------------------------------------------
static struct static struct
@ -417,12 +426,14 @@ struct app_readline_state
{ {
char *saved_line; char *saved_line;
int saved_point; int saved_point;
int saved_mark;
}; };
static void static void
app_readline_hide (struct app_readline_state *state) app_readline_hide (struct app_readline_state *state)
{ {
state->saved_point = rl_point; state->saved_point = rl_point;
state->saved_point = rl_mark;
state->saved_line = rl_copy_text (0, rl_end); state->saved_line = rl_copy_text (0, rl_end);
rl_set_prompt (""); rl_set_prompt ("");
rl_replace_line ("", 0); rl_replace_line ("", 0);
@ -435,6 +446,7 @@ app_readline_restore (struct app_readline_state *state, const char *prompt)
rl_set_prompt (prompt); rl_set_prompt (prompt);
rl_replace_line (state->saved_line, 0); rl_replace_line (state->saved_line, 0);
rl_point = state->saved_point; rl_point = state->saved_point;
rl_mark = state->saved_mark;
rl_redisplay (); rl_redisplay ();
free (state->saved_line); free (state->saved_line);
} }
@ -677,6 +689,12 @@ buffer_line_display (struct app_context *ctx, struct buffer_line *line)
char *object = iconv_xstrdup (ctx->term_from_utf8, line->object, -1, NULL); char *object = iconv_xstrdup (ctx->term_from_utf8, line->object, -1, NULL);
char *reason = iconv_xstrdup (ctx->term_from_utf8, line->reason, -1, NULL); char *reason = iconv_xstrdup (ctx->term_from_utf8, line->reason, -1, NULL);
// TODO: colorize the output, note that we shouldn't put everything through
// tputs but only the attribute strings. That might prove a bit
// challenging. Maybe we could create a helper object to pust text
// and formatting into. We could have a varargs function to make it a bit
// more friendly, e.g. push(&x, ATTR_JOIN, "--> ", ATTR_RESET, who, NULL)
switch (line->type) switch (line->type)
{ {
case BUFFER_LINE_PRIVMSG: case BUFFER_LINE_PRIVMSG:
@ -724,12 +742,15 @@ buffer_line_display (struct app_context *ctx, struct buffer_line *line)
free (object); free (object);
free (reason); free (reason);
// TODO: hide readline if needed struct app_readline_state state;
if (ctx->readline_prompt_shown)
app_readline_hide (&state);
printf ("%s\n", text.str); printf ("%s\n", text.str);
str_free (&text); str_free (&text);
// TODO: unhide readline if hidden if (ctx->readline_prompt_shown)
app_readline_restore (&state, ctx->readline_prompt);
} }
static void static void
@ -773,7 +794,7 @@ buffer_add (struct app_context *ctx, struct buffer *buffer)
str_map_set (&ctx->buffers_by_name, buffer->name, buffer); str_map_set (&ctx->buffers_by_name, buffer->name, buffer);
LIST_APPEND_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer); LIST_APPEND_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer);
// TODO: refresh the prompt? // TODO: refresh the prompt? Or caller?
} }
static void static void
@ -781,6 +802,21 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer)
{ {
hard_assert (buffer != ctx->current_buffer); hard_assert (buffer != ctx->current_buffer);
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);
rl_clear_history ();
history_set_history_state (state);
free (state);
}
str_map_set (&ctx->buffers_by_name, buffer->name, NULL); str_map_set (&ctx->buffers_by_name, buffer->name, NULL);
LIST_UNLINK_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer); LIST_UNLINK_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer);
buffer_destroy (buffer); buffer_destroy (buffer);
@ -792,22 +828,85 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer)
if (buffer == ctx->server_buffer) if (buffer == ctx->server_buffer)
ctx->server_buffer = NULL; ctx->server_buffer = NULL;
// TODO: refresh the prompt? // TODO: refresh the prompt? Or caller?
} }
static void static void
buffer_activate (struct app_context *ctx, struct buffer *buffer) buffer_activate (struct app_context *ctx, struct buffer *buffer)
{ {
ctx->current_buffer = buffer; if (ctx->current_buffer == buffer)
return;
print_status ("%s", buffer->name); print_status ("%s", buffer->name);
// That is, minus the buffer switch line and the readline prompt // That is, minus the buffer switch line and the readline prompt
int to_display = ctx->lines - 2; int to_display = MAX (10, ctx->lines - 2);
// TODO: find the to_display-th line from the back struct buffer_line *line = buffer->lines_tail;
// TODO: print all the lines in order while (line && line->prev && --to_display > 0)
line = line->prev;
// TODO: switch readline history stack // Once we've found where we want to start with the backlog, print it
// TODO: refresh the prompt? for (; line; line = line->next)
buffer_line_display (ctx, line);
// 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
rl_clear_history ();
// Now at last we can switch the pointers
ctx->current_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);
if (ctx->readline_prompt_shown)
rl_redisplay ();
}
// TODO: refresh the prompt? Or caller?
} }
static void static void
@ -1099,6 +1198,8 @@ irc_establish_connection (struct app_context *ctx,
return true; return true;
} }
// --- More readline funky stuff -----------------------------------------------
static void static void
refresh_prompt (struct app_context *ctx) refresh_prompt (struct app_context *ctx)
{ {
@ -1140,10 +1241,69 @@ refresh_prompt (struct app_context *ctx)
} }
str_free (&prompt); str_free (&prompt);
// FIXME: when the program hasn't displayed the prompt yet, is this okay? // We need to be somehow able to initialize it
rl_set_prompt (ctx->readline_prompt);
if (ctx->readline_prompt_shown)
rl_redisplay (); rl_redisplay ();
} }
static int
on_readline_goto_buffer (int count, int key)
{
(void) count;
if (!(key & 0x80))
return 0;
int n = (key & 0x7F) - '0';
if (n < 0 || n > 9)
return 0;
// TODO: switch to the n-th buffer
return 0;
}
static int
on_readline_previous_buffer (int count, int key)
{
(void) key;
// TODO: switch "count" times to the previous buffer
return 0;
}
static int
on_readline_next_buffer (int count, int key)
{
(void) key;
// TODO: switch "count" times to the next buffer
return 0;
}
static int
init_readline (void)
{
// TODO: 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++)
rl_bind_key (0x80 /* this is the Meta modifier for Readline */
| ('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"));
rl_bind_keyseq ("M-p", rl_named_function ("previous-history"));
rl_bind_keyseq ("M-n", rl_named_function ("next-history"));
return 0;
}
// --- Input handling ---------------------------------------------------------- // --- Input handling ----------------------------------------------------------
static void static void
@ -1268,24 +1428,6 @@ fail:
free (input); free (input);
} }
static int
on_readline_previous_buffer (int count, int key)
{
(void) key;
// TODO: switch to the previous buffer
return 0;
}
static int
on_readline_next_buffer (int count, int key)
{
(void) key;
// TODO: switch to the next buffer
return 0;
}
// --- Supporting code (continued) --------------------------------------------- // --- Supporting code (continued) ---------------------------------------------
enum irc_read_result enum irc_read_result
@ -1898,6 +2040,7 @@ main (int argc, char *argv[])
atexit (ERR_free_strings); atexit (ERR_free_strings);
using_history (); using_history ();
// This can cause memory leaks, or maybe even a segfault. Funny, eh?
stifle_history (HISTORY_LIMIT); stifle_history (HISTORY_LIMIT);
setup_signal_handlers (); setup_signal_handlers ();
@ -1905,6 +2048,7 @@ main (int argc, char *argv[])
init_colors (&ctx); init_colors (&ctx);
init_poller_events (&ctx); init_poller_events (&ctx);
init_buffers (&ctx); init_buffers (&ctx);
ctx.current_buffer = ctx.global_buffer;
refresh_prompt (&ctx); refresh_prompt (&ctx);
// TODO: connect asynchronously (first step towards multiple servers) // TODO: connect asynchronously (first step towards multiple servers)
@ -1917,22 +2061,7 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE); exit (EXIT_FAILURE);
} }
// TODO: maybe use rl_make_bare_keymap() and start from there rl_startup_hook = init_readline;
// XXX: Since readline() installs a set of default key bindings the first
// time it is called, there is always the danger that a custom binding
// installed before the first call to readline() will be overridden.
// An alternate mechanism is to install custom key bindings in an
// initialization function assigned to the rl_startup_hook variable.
rl_add_defun ("previous-buffer", on_readline_previous_buffer, -1);
rl_add_defun ("next-buffer", on_readline_next_buffer, -1);
// TODO: redefine M-0 through M-9 to switch buffers
rl_bind_keyseq ("C-p", rl_named_function ("previous-buffer"));
rl_bind_keyseq ("C-n", rl_named_function ("next-buffer"));
rl_bind_keyseq ("M-p", rl_named_function ("previous-history"));
rl_bind_keyseq ("M-n", rl_named_function ("next-history"));
rl_catch_sigwinch = false; rl_catch_sigwinch = false;
rl_callback_handler_install (ctx.readline_prompt, on_readline_input); rl_callback_handler_install (ctx.readline_prompt, on_readline_input);
rl_get_screen_size (&ctx.lines, &ctx.columns); rl_get_screen_size (&ctx.lines, &ctx.columns);