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/readline.h>
|
||||||
#include <readline/history.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 --------------------------------------------------------
|
// --- Application data --------------------------------------------------------
|
||||||
|
|
||||||
// All text stored in our data structures is encoded in UTF-8.
|
// 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
|
enum buffer_type type; ///< Type of the buffer
|
||||||
char *name; ///< The name of the buffer
|
char *name; ///< The name of the buffer
|
||||||
|
|
||||||
// Readline state:
|
struct input_buffer *input_data; ///< User interface data
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
|
@ -336,6 +614,7 @@ static struct buffer *
|
||||||
buffer_new (void)
|
buffer_new (void)
|
||||||
{
|
{
|
||||||
struct buffer *self = xcalloc (1, sizeof *self);
|
struct buffer *self = xcalloc (1, sizeof *self);
|
||||||
|
self->input_data = input_buffer_new ();
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,8 +622,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
|
if (self->input_data)
|
||||||
free (self->saved_line);
|
input_buffer_destroy (self->input_data);
|
||||||
LIST_FOR_EACH (struct buffer_line, iter, self->lines)
|
LIST_FOR_EACH (struct buffer_line, iter, self->lines)
|
||||||
buffer_line_destroy (iter);
|
buffer_line_destroy (iter);
|
||||||
if (self->user)
|
if (self->user)
|
||||||
|
@ -501,8 +780,7 @@ struct app_context
|
||||||
int lines; ///< Current terminal height
|
int lines; ///< Current terminal height
|
||||||
int columns; ///< Current ternimal width
|
int columns; ///< Current ternimal width
|
||||||
|
|
||||||
char *readline_prompt; ///< The prompt we use for readline
|
struct input input; ///< User interface
|
||||||
bool readline_prompt_shown; ///< Whether the prompt is shown now
|
|
||||||
}
|
}
|
||||||
*g_ctx;
|
*g_ctx;
|
||||||
|
|
||||||
|
@ -539,6 +817,8 @@ app_context_init (struct app_context *self)
|
||||||
strerror (errno));
|
strerror (errno));
|
||||||
|
|
||||||
free (encoding);
|
free (encoding);
|
||||||
|
|
||||||
|
input_init (&self->input);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -560,7 +840,7 @@ app_context_free (struct app_context *self)
|
||||||
iconv_close (self->term_from_utf8);
|
iconv_close (self->term_from_utf8);
|
||||||
iconv_close (self->term_to_utf8);
|
iconv_close (self->term_to_utf8);
|
||||||
|
|
||||||
free (self->readline_prompt);
|
input_free (&self->input);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void refresh_prompt (struct app_context *ctx);
|
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);
|
typedef int (*terminal_printer_fn) (int);
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -991,16 +1230,13 @@ log_message_attributed (void *user_data, const char *quote, const char *fmt,
|
||||||
{
|
{
|
||||||
FILE *stream = stderr;
|
FILE *stream = stderr;
|
||||||
|
|
||||||
struct app_readline_state state;
|
input_hide (&g_ctx->input);
|
||||||
if (g_ctx->readline_prompt_shown)
|
|
||||||
app_readline_hide (&state);
|
|
||||||
|
|
||||||
print_attributed (g_ctx, stream, (intptr_t) user_data, "%s", quote);
|
print_attributed (g_ctx, stream, (intptr_t) user_data, "%s", quote);
|
||||||
vprint_attributed (g_ctx, stream, (intptr_t) user_data, fmt, ap);
|
vprint_attributed (g_ctx, stream, (intptr_t) user_data, fmt, ap);
|
||||||
fputs ("\n", stream);
|
fputs ("\n", stream);
|
||||||
|
|
||||||
if (g_ctx->readline_prompt_shown)
|
input_show (&g_ctx->input);
|
||||||
app_readline_restore (&state, g_ctx->readline_prompt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -1550,9 +1786,7 @@ buffer_line_display (struct app_context *ctx,
|
||||||
|
|
||||||
free (nick);
|
free (nick);
|
||||||
|
|
||||||
struct app_readline_state state;
|
input_hide (&ctx->input);
|
||||||
if (ctx->readline_prompt_shown)
|
|
||||||
app_readline_hide (&state);
|
|
||||||
|
|
||||||
// TODO: write the line to a log file; note that the global and server
|
// TODO: write the line to a log file; note that the global and server
|
||||||
// buffers musn't collide with filenames
|
// buffers musn't collide with filenames
|
||||||
|
@ -1561,8 +1795,7 @@ buffer_line_display (struct app_context *ctx,
|
||||||
formatter_flush (&f, stdout);
|
formatter_flush (&f, stdout);
|
||||||
formatter_free (&f);
|
formatter_free (&f);
|
||||||
|
|
||||||
if (ctx->readline_prompt_shown)
|
input_show (&ctx->input);
|
||||||
app_readline_restore (&state, ctx->readline_prompt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -1638,25 +1871,8 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer)
|
||||||
|
|
||||||
// TODO: part from the channel if needed
|
// TODO: part from the channel if needed
|
||||||
|
|
||||||
// rl_clear_history, being the only way I know of to get rid of the complete
|
input_destroy_buffer (&ctx->input, buffer->input_data);
|
||||||
// history including attached data, is a pretty recent addition. *sigh*
|
buffer->input_data = NULL;
|
||||||
#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
|
|
||||||
|
|
||||||
// And make sure to unlink the buffer from "irc_buffer_map"
|
// And make sure to unlink the buffer from "irc_buffer_map"
|
||||||
struct server *s = buffer->server;
|
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_line_display (ctx, line, false);
|
||||||
buffer->unseen_messages_count = 0;
|
buffer->unseen_messages_count = 0;
|
||||||
|
|
||||||
// The following part shows you why it's not a good idea to use
|
input_switch_buffer (&ctx->input, buffer->input_data);
|
||||||
// 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 ();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now at last we can switch the pointers
|
// Now at last we can switch the pointers
|
||||||
ctx->last_buffer = ctx->current_buffer;
|
ctx->last_buffer = ctx->current_buffer;
|
||||||
|
@ -2039,15 +2197,8 @@ try_finish_quit (struct app_context *ctx)
|
||||||
static void
|
static void
|
||||||
initiate_quit (struct app_context *ctx)
|
initiate_quit (struct app_context *ctx)
|
||||||
{
|
{
|
||||||
// First get rid of readline
|
// Destroy the user interface
|
||||||
if (ctx->readline_prompt_shown)
|
input_stop (&ctx->input);
|
||||||
{
|
|
||||||
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 ();
|
|
||||||
|
|
||||||
buffer_send_status (ctx, ctx->global_buffer, "Shutting down");
|
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)
|
if (g_debug_mode)
|
||||||
{
|
{
|
||||||
struct app_readline_state state;
|
input_hide (&s->ctx->input);
|
||||||
if (s->ctx->readline_prompt_shown)
|
|
||||||
app_readline_hide (&state);
|
|
||||||
|
|
||||||
char *term = irc_to_term (s->ctx, str.str);
|
char *term = irc_to_term (s->ctx, str.str);
|
||||||
fprintf (stderr, "[IRC] <== \"%s\"\n", term);
|
fprintf (stderr, "[IRC] <== \"%s\"\n", term);
|
||||||
free (term);
|
free (term);
|
||||||
|
|
||||||
if (s->ctx->readline_prompt_shown)
|
input_show (&s->ctx->input);
|
||||||
app_readline_restore (&state, s->ctx->readline_prompt);
|
|
||||||
}
|
}
|
||||||
str_append (&str, "\r\n");
|
str_append (&str, "\r\n");
|
||||||
|
|
||||||
|
@ -2383,145 +2531,19 @@ refresh_prompt (struct app_context *ctx)
|
||||||
make_prompt (ctx, &prompt);
|
make_prompt (ctx, &prompt);
|
||||||
str_append_c (&prompt, ' ');
|
str_append_c (&prompt, ' ');
|
||||||
|
|
||||||
// After building the new prompt, replace the old one
|
|
||||||
free (ctx->readline_prompt);
|
|
||||||
|
|
||||||
if (!have_attributes)
|
if (!have_attributes)
|
||||||
ctx->readline_prompt = xstrdup (prompt.str);
|
input_set_prompt (&ctx->input, xstrdup (prompt.str));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// XXX: to be completely correct, we should use tputs, but we cannot
|
// 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_START_IGNORE, ctx->attrs[ATTR_PROMPT],
|
||||||
RL_PROMPT_END_IGNORE,
|
RL_PROMPT_END_IGNORE,
|
||||||
prompt.str,
|
prompt.str,
|
||||||
RL_PROMPT_START_IGNORE, ctx->attrs[ATTR_RESET],
|
RL_PROMPT_START_IGNORE, ctx->attrs[ATTR_RESET],
|
||||||
RL_PROMPT_END_IGNORE);
|
RL_PROMPT_END_IGNORE));
|
||||||
}
|
}
|
||||||
str_free (&prompt);
|
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 ----------------------------------------------------------
|
// --- Input handling ----------------------------------------------------------
|
||||||
|
@ -3195,16 +3217,13 @@ irc_process_message (const struct irc_message *msg,
|
||||||
|
|
||||||
if (g_debug_mode)
|
if (g_debug_mode)
|
||||||
{
|
{
|
||||||
struct app_readline_state state;
|
input_hide (&s->ctx->input);
|
||||||
if (s->ctx->readline_prompt_shown)
|
|
||||||
app_readline_hide (&state);
|
|
||||||
|
|
||||||
char *term = irc_to_term (s->ctx, raw);
|
char *term = irc_to_term (s->ctx, raw);
|
||||||
fprintf (stderr, "[IRC] ==> \"%s\"\n", term);
|
fprintf (stderr, "[IRC] ==> \"%s\"\n", term);
|
||||||
free (term);
|
free (term);
|
||||||
|
|
||||||
if (s->ctx->readline_prompt_shown)
|
input_show (&s->ctx->input);
|
||||||
app_readline_restore (&state, s->ctx->readline_prompt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: or is the 001 numeric enough? For what?
|
// 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;
|
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 ------------------------------------------------------
|
// --- I/O event handlers ------------------------------------------------------
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -4712,10 +4857,7 @@ on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx)
|
||||||
|
|
||||||
if (g_winch_received)
|
if (g_winch_received)
|
||||||
{
|
{
|
||||||
// This fucks up big time on terminals with automatic wrapping such as
|
input_on_terminal_resized (&ctx->input);
|
||||||
// rxvt-unicode or newer VTE when the current line overflows, however we
|
|
||||||
// can't do much about that
|
|
||||||
rl_resize_terminal ();
|
|
||||||
rl_get_screen_size (&ctx->lines, &ctx->columns);
|
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))
|
if (fd->revents & ~(POLLIN | POLLHUP | POLLERR))
|
||||||
print_debug ("fd %d: unexpected revents: %d", fd->fd, fd->revents);
|
print_debug ("fd %d: unexpected revents: %d", fd->fd, fd->revents);
|
||||||
|
|
||||||
rl_callback_read_char ();
|
input_on_readable (&ctx->input);
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Configuration loading ---------------------------------------------------
|
// --- Configuration loading ---------------------------------------------------
|
||||||
|
@ -4957,18 +5077,15 @@ 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.server.buffer;
|
buffer_activate (&ctx, ctx.server.buffer);
|
||||||
|
|
||||||
refresh_prompt (&ctx);
|
refresh_prompt (&ctx);
|
||||||
|
input_start (&ctx.input);
|
||||||
|
rl_get_screen_size (&ctx.lines, &ctx.columns);
|
||||||
|
|
||||||
// Connect to the server ASAP
|
// Connect to the server ASAP
|
||||||
poller_timer_set (&ctx.server.reconnect_tmr, 0);
|
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;
|
ctx.polling = true;
|
||||||
while (ctx.polling)
|
while (ctx.polling)
|
||||||
poller_run (&ctx.poller);
|
poller_run (&ctx.poller);
|
||||||
|
|
Loading…
Reference in New Issue