From 4a0c774e75dc610100b0a068cb569513c6b4e4cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Wed, 15 Apr 2015 02:10:21 +0200 Subject: [PATCH] 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. --- degesch.c | 221 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 175 insertions(+), 46 deletions(-) diff --git a/degesch.c b/degesch.c index 7b9d141..1b2e817 100644 --- a/degesch.c +++ b/degesch.c @@ -187,6 +187,11 @@ struct buffer enum buffer_type type; ///< Type 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: struct buffer_line *lines; ///< All lines in this buffer @@ -215,6 +220,8 @@ static void buffer_destroy (struct buffer *self) { free (self->name); + // Can't really free "history" here + free (self->saved_line); free (self->topic); str_map_free (&self->nicks); free (self); @@ -361,6 +368,8 @@ app_context_free (struct app_context *self) free (self->readline_prompt); } +static void refresh_prompt (struct app_context *ctx); + // --- Attributed output ------------------------------------------------------- static struct @@ -417,12 +426,14 @@ 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_point = rl_mark; state->saved_line = rl_copy_text (0, rl_end); rl_set_prompt (""); rl_replace_line ("", 0); @@ -435,6 +446,7 @@ 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); } @@ -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 *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) { case BUFFER_LINE_PRIVMSG: @@ -724,12 +742,15 @@ buffer_line_display (struct app_context *ctx, struct buffer_line *line) free (object); 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); str_free (&text); - // TODO: unhide readline if hidden + if (ctx->readline_prompt_shown) + app_readline_restore (&state, ctx->readline_prompt); } 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); LIST_APPEND_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer); - // TODO: refresh the prompt? + // TODO: refresh the prompt? Or caller? } static void @@ -781,6 +802,21 @@ buffer_remove (struct app_context *ctx, struct buffer *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); LIST_UNLINK_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer); buffer_destroy (buffer); @@ -792,22 +828,85 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer) if (buffer == ctx->server_buffer) ctx->server_buffer = NULL; - // TODO: refresh the prompt? + // TODO: refresh the prompt? Or caller? } static void buffer_activate (struct app_context *ctx, struct buffer *buffer) { - ctx->current_buffer = buffer; + if (ctx->current_buffer == buffer) + return; + print_status ("%s", buffer->name); // That is, minus the buffer switch line and the readline prompt - int to_display = ctx->lines - 2; - // TODO: find the to_display-th line from the back - // TODO: print all the lines in order + int to_display = MAX (10, ctx->lines - 2); + struct buffer_line *line = buffer->lines_tail; + while (line && line->prev && --to_display > 0) + line = line->prev; - // TODO: switch readline history stack - // TODO: refresh the prompt? + // Once we've found where we want to start with the backlog, print it + 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 @@ -1099,6 +1198,8 @@ irc_establish_connection (struct app_context *ctx, return true; } +// --- More readline funky stuff ----------------------------------------------- + static void refresh_prompt (struct app_context *ctx) { @@ -1140,8 +1241,67 @@ refresh_prompt (struct app_context *ctx) } str_free (&prompt); - // FIXME: when the program hasn't displayed the prompt yet, is this okay? - rl_redisplay (); + // We need to be somehow able to initialize it + 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; + + 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 ---------------------------------------------------------- @@ -1268,24 +1428,6 @@ fail: 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) --------------------------------------------- enum irc_read_result @@ -1898,6 +2040,7 @@ main (int argc, char *argv[]) atexit (ERR_free_strings); using_history (); + // This can cause memory leaks, or maybe even a segfault. Funny, eh? stifle_history (HISTORY_LIMIT); setup_signal_handlers (); @@ -1905,6 +2048,7 @@ main (int argc, char *argv[]) init_colors (&ctx); init_poller_events (&ctx); init_buffers (&ctx); + ctx.current_buffer = ctx.global_buffer; refresh_prompt (&ctx); // TODO: connect asynchronously (first step towards multiple servers) @@ -1917,22 +2061,7 @@ main (int argc, char *argv[]) exit (EXIT_FAILURE); } - // TODO: maybe use rl_make_bare_keymap() and start from there - - // 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_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);