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:
parent
3df841f088
commit
4a0c774e75
219
degesch.c
219
degesch.c
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user