degesch: try to abstract GNU Readline
This commit is contained in:
parent
87afccc568
commit
87843f47e4
761
degesch.c
761
degesch.c
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user