From 696273558ea6f9a766e340fb9349560a209cdaac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Mon, 7 Mar 2016 01:08:52 +0100 Subject: [PATCH] degesch: rewrite input layer Now with less #ifdefs. --- degesch.c | 1162 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 684 insertions(+), 478 deletions(-) diff --git a/degesch.c b/degesch.c index 3d0130a..838ec34 100644 --- a/degesch.c +++ b/degesch.c @@ -70,160 +70,256 @@ enum #include -#ifdef HAVE_READLINE -#include -#include -#endif // HAVE_READLINE - -#ifdef HAVE_EDITLINE -#include -#endif // HAVE_EDITLINE - #ifdef HAVE_LUA #include #include #include #endif // HAVE_LUA -/// Some arbitrary limit for the history file +// --- Terminal information ---------------------------------------------------- + +static struct +{ + bool initialized; ///< Terminal is available + bool stdout_is_tty; ///< `stdout' is a terminal + bool stderr_is_tty; ///< `stderr' is a terminal + + struct termios termios; ///< Terminal attributes + char *color_set_fg[256]; ///< Codes to set the foreground colour + char *color_set_bg[256]; ///< Codes to set the background colour + + int lines; ///< Number of lines + int columns; ///< Number of columns +} +g_terminal; + +static void +update_screen_size (void) +{ +#ifdef TIOCGWINSZ + if (!g_terminal.stdout_is_tty) + return; + + struct winsize size; + if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size)) + { + char *row = getenv ("LINES"); + char *col = getenv ("COLUMNS"); + unsigned long tmp; + g_terminal.lines = + (row && xstrtoul (&tmp, row, 10)) ? tmp : size.ws_row; + g_terminal.columns = + (col && xstrtoul (&tmp, col, 10)) ? tmp : size.ws_col; + } +#endif // TIOCGWINSZ +} + +static bool +init_terminal (void) +{ + int tty_fd = -1; + if ((g_terminal.stderr_is_tty = isatty (STDERR_FILENO))) + tty_fd = STDERR_FILENO; + if ((g_terminal.stdout_is_tty = isatty (STDOUT_FILENO))) + tty_fd = STDOUT_FILENO; + + int err; + if (tty_fd == -1 || setupterm (NULL, tty_fd, &err) == ERR) + return false; + + // Make sure all terminal features used by us are supported + if (!set_a_foreground || !set_a_background + || !enter_bold_mode || !exit_attribute_mode + || tcgetattr (tty_fd, &g_terminal.termios)) + { + del_curterm (cur_term); + return false; + } + + // Make sure newlines are output correctly + g_terminal.termios.c_oflag |= ONLCR; + (void) tcsetattr (tty_fd, TCSADRAIN, &g_terminal.termios); + + g_terminal.lines = tigetnum ("lines"); + g_terminal.columns = tigetnum ("cols"); + update_screen_size (); + + int max = MIN (256, max_colors); + for (int i = 0; i < max; i++) + { + g_terminal.color_set_fg[i] = xstrdup (tparm (set_a_foreground, + i, 0, 0, 0, 0, 0, 0, 0, 0)); + g_terminal.color_set_bg[i] = xstrdup (tparm (set_a_background, + i, 0, 0, 0, 0, 0, 0, 0, 0)); + } + return g_terminal.initialized = true; +} + +static void +free_terminal (void) +{ + if (!g_terminal.initialized) + return; + + for (int i = 0; i < 256; i++) + { + free (g_terminal.color_set_fg[i]); + free (g_terminal.color_set_bg[i]); + } + del_curterm (cur_term); +} + +// --- User interface ---------------------------------------------------------- + +// I'm not sure which one of these backends is worse: whether it's GNU Readline +// or BSD Editline. They both have their own annoying problems. We use lots +// of hacks to get the results we want and need. +// +// The abstraction is a necessary evil. It's still not 100%, though. + +/// Some arbitrary limit for the history #define HISTORY_LIMIT 10000 /// Characters that separate words #define WORD_BREAKING_CHARS " \f\n\r\t\v" -// --- User interface ---------------------------------------------------------- - -// I'm not sure which one of these backends is worse: whether it's GNU Readline -// or BSD Editline. They both have their own annoying problems. - -struct input_buffer -{ -#ifdef HAVE_READLINE - HISTORY_STATE *history; ///< Saved history state - char *saved_line; ///< Saved line content - int saved_mark; ///< Saved mark -#elif defined HAVE_EDITLINE - HistoryW *history; ///< The history object - wchar_t *saved_line; ///< Saved line content - int saved_len; ///< Length of the saved line -#endif // HAVE_EDITLINE - int saved_point; ///< Saved cursor position -}; - -static struct input_buffer * -input_buffer_new (void) -{ - struct input_buffer *self = xcalloc (1, sizeof *self); -#ifdef HAVE_EDITLINE - self->history = history_winit (); - - HistEventW ev; - history_w (self->history, &ev, H_SETSIZE, HISTORY_LIMIT); -#endif // HAVE_EDITLINE - return self; -} - -static void -input_buffer_destroy (struct input_buffer *self) -{ -#ifdef HAVE_READLINE - // Can't really free "history" contents from here - free (self->history); -#elif defined HAVE_EDITLINE - history_wend (self->history); -#endif // HAVE_EDITLINE - free (self->saved_line); - free (self); -} - -typedef bool (*input_fn) (int count, int key, void *user_data); - -struct input_fn_data -{ - ffi_closure closure; ///< Closure - - LIST_HEADER (struct input_fn_data) - input_fn callback; ///< Real callback - void *user_data; ///< Real callback user data - -#ifdef HAVE_EDITLINE - wchar_t *name; ///< Function name - wchar_t *help; ///< Function help -#endif // HAVE_EDITLINE -}; - struct input { - bool active; ///< Are we a thing? - -#if defined HAVE_READLINE - char *saved_line; ///< Saved line content - int saved_point; ///< Saved cursor position - int saved_mark; ///< Saved mark -#elif defined HAVE_EDITLINE - EditLine *editline; ///< The EditLine object -#endif // HAVE_EDITLINE - struct input_fn_data *fns; ///< Functions - - char *prompt; ///< The prompt we use - int prompt_shown; ///< Whether the prompt is shown now - - struct input_buffer *current; ///< Current input buffer + struct input_vtable *vtable; ///< Virtual methods + void *user_data; ///< User data for callbacks }; -static void -input_init (struct input *self) -{ - memset (self, 0, sizeof *self); -} +typedef void *input_buffer_t; ///< Pointer alias for input buffers -static void -input_free (struct input *self) +/// Named function that can be bound to a sequence of characters +typedef bool (*input_fn) (int count, int key, void *user_data); + +// A little bit better than tons of forwarder functions in our case +#define CALL(self, name) ((self)->vtable->name ((self))) +#define CALL_(self, name, ...) ((self)->vtable->name ((self), __VA_ARGS__)) + +struct input_vtable { -#ifdef HAVE_READLINE - free (self->saved_line); -#endif // HAVE_READLINE - LIST_FOR_EACH (struct input_fn_data, iter, self->fns) - { -#ifdef HAVE_EDITLINE - free (iter->name); - free (iter->help); -#endif // HAVE_EDITLINE - ffi_closure_free (iter); - } - free (self->prompt); -} + /// Start the interface under the given program name + void (*start) (void *input, const char *program_name); + /// Stop the interface + void (*stop) (void *input); + /// Prepare or unprepare terminal for our needs + void (*prepare) (void *input, bool enabled); + /// Destroy the object + void (*destroy) (void *input); + + /// Hide the prompt if shown + void (*hide) (void *input); + /// Show the prompt if hidden + void (*show) (void *input); + /// Change the prompt string; takes ownership + const char *(*get_prompt) (void *input); + /// Change the prompt string; takes ownership + void (*set_prompt) (void *input, char *prompt); + /// Ring the terminal bell + void (*ding) (void *input); + + /// Create a new input buffer + input_buffer_t (*buffer_new) (void *input); + // XXX: there's also destroy_buffer + /// Destroy an input buffer + void (*buffer_destroy) (void *input, input_buffer_t buffer); + /// Switch to a different input buffer + void (*buffer_switch) (void *input, input_buffer_t buffer); + + /// Register a function that can be bound to character sequences + void (*register_fn) (void *input, + const char *name, const char *help, input_fn fn, void *user_data); + /// Bind an arbitrary sequence of characters to the given named function + void (*bind) (void *input, const char *seq, const char *fn); + /// Bind Ctrl+key to the given named function + void (*bind_control) (void *input, char key, const char *fn); + /// Bind Alt+key to the given named function + void (*bind_meta) (void *input, char key, const char *fn); + + /// Get the current line input + char *(*get_line) (void *input); + /// Clear the current line input + void (*clear_line) (void *input); + /// Insert text at current position + bool (*insert) (void *input, const char *text); + + /// Handle terminal resize + void (*on_tty_resized) (void *input); + /// Handle terminal input + void (*on_tty_readable) (void *input); +}; + +#define INPUT_VTABLE(XX) \ + XX (start) XX (stop) XX (prepare) XX (destroy) \ + XX (hide) XX (show) XX (get_prompt) XX (set_prompt) XX (ding) \ + XX (buffer_new) XX (buffer_destroy) XX (buffer_switch) \ + XX (register_fn) XX (bind) XX (bind_control) XX (bind_meta) \ + XX (get_line) XX (clear_line) XX (insert) \ + XX (on_tty_resized) XX (on_tty_readable) // --- GNU Readline ------------------------------------------------------------ #ifdef HAVE_READLINE +#include +#include + #define INPUT_START_IGNORE RL_PROMPT_START_IGNORE #define INPUT_END_IGNORE RL_PROMPT_END_IGNORE -#define input_ding(self) rl_ding () +struct input_rl_fn +{ + ffi_closure closure; ///< Closure + + LIST_HEADER (struct input_rl_fn) + input_fn callback; ///< Real callback + void *user_data; ///< Real callback user data +}; + +struct input_rl_buffer +{ + HISTORY_STATE *history; ///< Saved history state + char *saved_line; ///< Saved line content + int saved_point; ///< Saved cursor position + int saved_mark; ///< Saved mark +}; + +struct input_rl +{ + struct input super; ///< Parent class + + bool active; ///< Interface has been started + char *prompt; ///< The prompt we use + int prompt_shown; ///< Whether the prompt is shown now + + char *saved_line; ///< Saved line content + int saved_point; ///< Saved cursor position + int saved_mark; ///< Saved mark + + struct input_rl_fn *fns; ///< Named functions + struct input_rl_buffer *current; ///< Current input buffer +}; static void -input_on_terminal_resized (struct input *self) +input_rl_ding (void *input) { - (void) self; + (void) input; + rl_ding (); +} - // 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 const char * +input_rl_get_prompt (void *input) +{ + struct input_rl *self = input; + return self->prompt; } static void -input_on_readable (struct input *self) -{ - (void) self; - rl_callback_read_char (); -} - -static void -input_set_prompt (struct input *self, char *prompt) +input_rl_set_prompt (void *input, char *prompt) { + struct input_rl *self = input; free (self->prompt); self->prompt = prompt; @@ -241,54 +337,24 @@ input_set_prompt (struct input *self, char *prompt) } static void -input_erase_content (struct input *self) +input_rl_clear_line (void *input) { - (void) self; - + (void) input; rl_replace_line ("", false); rl_redisplay (); } static void -input_erase (struct input *self) +input_rl__erase (struct input_rl *self) { - (void) self; - rl_set_prompt (""); - input_erase_content (self); -} - -static void -input_bind (struct input *self, const char *seq, const char *function_name) -{ - (void) self; - rl_bind_keyseq (seq, rl_named_function (function_name)); -} - -static void -input_bind_meta (struct input *self, char key, const char *function_name) -{ - // This one seems to actually work - char keyseq[] = { '\\', 'e', key, 0 }; - input_bind (self, keyseq, function_name); -#if 0 - // While this one only fucks up UTF-8 - // Tested with urxvt and xterm, on Debian Jessie/Arch, default settings - // \M- behaves exactly the same - rl_bind_key (META (key), rl_named_function (function_name)); -#endif -} - -static void -input_bind_control (struct input *self, char key, const char *function_name) -{ - char keyseq[] = { '\\', 'C', '-', key, 0 }; - input_bind (self, keyseq, function_name); + input_rl_clear_line (self); } static bool -input_insert (struct input *self, const char *s) +input_rl_insert (void *input, const char *s) { + struct input_rl *self = input; rl_insert_text (s); if (self->prompt_shown > 0) rl_redisplay (); @@ -298,20 +364,48 @@ input_insert (struct input *self, const char *s) } static char * -input_get_content (struct input *self) +input_rl_get_line (void *input) { - (void) self; + (void) input; return rl_copy_text (0, rl_end); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void -input_closure_forwarder (ffi_cif *cif, void *ret, void **args, void *user_data) +input_rl_bind (void *input, const char *seq, const char *function_name) +{ + (void) input; + rl_bind_keyseq (seq, rl_named_function (function_name)); +} + +static void +input_rl_bind_meta (void *input, char key, const char *function_name) +{ + // This one seems to actually work + char keyseq[] = { '\\', 'e', key, 0 }; + input_rl_bind (input, keyseq, function_name); +#if 0 + // While this one only fucks up UTF-8 + // Tested with urxvt and xterm, on Debian Jessie/Arch, default settings + // \M- behaves exactly the same + rl_bind_key (META (key), rl_named_function (function_name)); +#endif +} + +static void +input_rl_bind_control (void *input, char key, const char *function_name) +{ + char keyseq[] = { '\\', 'C', '-', key, 0 }; + input_rl_bind (input, keyseq, function_name); +} + +static void +input_rl__forward (ffi_cif *cif, void *ret, void **args, void *user_data) { (void) cif; - struct input_fn_data *data = user_data; + struct input_rl_fn *data = user_data; if (!data->callback (*(int *) args[0], UNMETA (*(int *) args[1]), data->user_data)) rl_ding (); @@ -319,13 +413,14 @@ input_closure_forwarder (ffi_cif *cif, void *ret, void **args, void *user_data) } static void -input_add_fn (struct input *self, +input_rl_register_fn (void *input, const char *name, const char *help, input_fn callback, void *user_data) { + struct input_rl *self = input; (void) help; void *bound_fn = NULL; - struct input_fn_data *data = ffi_closure_alloc (sizeof *data, &bound_fn); + struct input_rl_fn *data = ffi_closure_alloc (sizeof *data, &bound_fn); hard_assert (data); static ffi_cif cif; @@ -337,7 +432,7 @@ input_add_fn (struct input *self, data->callback = callback; data->user_data = user_data; hard_assert (ffi_prep_closure_loc (&data->closure, - &cif, input_closure_forwarder, data, bound_fn) == FFI_OK); + &cif, input_rl__forward, data, bound_fn) == FFI_OK); rl_add_defun (name, (rl_command_func_t *) bound_fn, -1); LIST_PREPEND (self->fns, data); @@ -350,8 +445,9 @@ static void on_readline_input (char *line); static char **app_readline_completion (const char *text, int start, int end); static void -input_start (struct input *self, const char *program_name) +input_rl_start (void *input, const char *program_name) { + struct input_rl *self = input; using_history (); // This can cause memory leaks, or maybe even a segfault. Funny, eh? stifle_history (HISTORY_LIMIT); @@ -373,10 +469,11 @@ input_start (struct input *self, const char *program_name) } static void -input_stop (struct input *self) +input_rl_stop (void *input) { + struct input_rl *self = input; if (self->prompt_shown > 0) - input_erase (self); + input_rl__erase (self); // This is okay as long as we're not called from within readline rl_callback_handler_remove (); @@ -384,13 +481,23 @@ input_stop (struct input *self) self->prompt_shown = false; } +static void +input_rl_prepare (void *input, bool enabled) +{ + (void) input; + if (enabled) + rl_prep_terminal (true); + else + rl_deprep_terminal (); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // 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) +input_rl__save_buffer (struct input_rl *self, struct input_rl_buffer *buffer) { (void) self; @@ -405,7 +512,7 @@ input_save_buffer (struct input *self, struct input_buffer *buffer) } static void -input_restore_buffer (struct input *self, struct input_buffer *buffer) +input_rl__restore_buffer (struct input_rl *self, struct input_rl_buffer *buffer) { if (buffer->history) { @@ -442,8 +549,10 @@ input_restore_buffer (struct input *self, struct input_buffer *buffer) } static void -input_switch_buffer (struct input *self, struct input_buffer *buffer) +input_rl_buffer_switch (void *input, input_buffer_t input_buffer) { + struct input_rl *self = input; + struct input_rl_buffer *buffer = input_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 @@ -452,7 +561,7 @@ input_switch_buffer (struct input *self, struct input_buffer *buffer) // Save this buffer's history so that it's independent for each buffer if (self->current) - input_save_buffer (self, self->current); + input_rl__save_buffer (self, self->current); else // Just throw it away; there should always be an active buffer however #if RL_READLINE_VERSION >= 0x0603 @@ -462,21 +571,30 @@ input_switch_buffer (struct input *self, struct input_buffer *buffer) clear_history (); #endif // RL_READLINE_VERSION < 0x0603 - input_restore_buffer (self, buffer); + input_rl__restore_buffer (self, buffer); self->current = buffer; } static void -input_destroy_buffer (struct input *self, struct input_buffer *buffer) +input_rl__buffer_destroy_wo_history (struct input_rl_buffer *self) { - (void) self; + free (self->history); + free (self->saved_line); + free (self); +} + +static void +input_rl_buffer_destroy (void *input, input_buffer_t input_buffer) +{ + (void) input; + struct input_rl_buffer *buffer = input_buffer; // 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 input_switch_buffer() for why we need to do this BS + // See input_rl_buffer_switch() for why we need to do this BS rl_free_undo_list (); // This is probably the only way we can free the history fully @@ -495,13 +613,25 @@ input_destroy_buffer (struct input *self, struct input_buffer *buffer) } #endif // RL_READLINE_VERSION - input_buffer_destroy (buffer); + input_rl__buffer_destroy_wo_history (buffer); +} + +static input_buffer_t +input_rl_buffer_new (void *input) +{ + (void) input; + struct input_rl_buffer *self = xcalloc (1, sizeof *self); + return self; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Since {save,restore}_buffer() store history, we can't use them here like we +// do with libedit, because then buffer_destroy() can free memory that's still +// being used by readline. This situation is bound to happen on quit. + static void -input_save (struct input *self) +input_rl__save (struct input_rl *self) { hard_assert (!self->saved_line); @@ -511,7 +641,7 @@ input_save (struct input *self) } static void -input_restore (struct input *self) +input_rl__restore (struct input_rl *self) { hard_assert (self->saved_line); @@ -526,100 +656,146 @@ input_restore (struct input *self) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void -input_hide (struct input *self) +input_rl_hide (void *input) { + struct input_rl *self = input; if (!self->active || self->prompt_shown-- < 1) return; - input_save (self); - input_erase (self); + input_rl__save (self); + input_rl__erase (self); } static void -input_show (struct input *self) +input_rl_show (void *input) { + struct input_rl *self = input; if (!self->active || ++self->prompt_shown < 1) return; - input_restore (self); + input_rl__restore (self); rl_redisplay (); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static void +input_rl_on_tty_resized (void *input) +{ + (void) input; + // 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_rl_on_tty_readable (void *input) +{ + (void) input; + rl_callback_read_char (); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static void +input_rl_destroy (void *input) +{ + struct input_rl *self = input; + free (self->saved_line); + LIST_FOR_EACH (struct input_rl_fn, iter, self->fns) + ffi_closure_free (iter); + free (self->prompt); + free (self); +} + +#define XX(a) .a = input_rl_ ## a, +static struct input_vtable input_rl_vtable = { INPUT_VTABLE (XX) }; +#undef XX + +static struct input * +input_rl_new (void) +{ + struct input_rl *self = xcalloc (1, sizeof *self); + self->super.vtable = &input_rl_vtable; + return &self->super; +} + +#define input_new input_rl_new #endif // HAVE_READLINE // --- BSD Editline ------------------------------------------------------------ #ifdef HAVE_EDITLINE +#include + #define INPUT_START_IGNORE '\x01' #define INPUT_END_IGNORE '\x01' -static void app_editline_init (struct input *self); - -static void -input_ding (struct input *self) +struct input_el_fn { - (void) self; + ffi_closure closure; ///< Closure - // XXX: this isn't probably very portable; - // we could use "bell" from terminfo but that creates a dependency - write (STDOUT_FILENO, "\a", 1); + LIST_HEADER (struct input_el_fn) + input_fn callback; ///< Real callback + void *user_data; ///< Real callback user data + + wchar_t *name; ///< Function name + wchar_t *help; ///< Function help +}; + +struct input_el_buffer +{ + HistoryW *history; ///< The history object + wchar_t *saved_line; ///< Saved line content + int saved_len; ///< Length of the saved line + int saved_point; ///< Saved cursor position +}; + +struct input_el +{ + struct input super; ///< Parent class + EditLine *editline; ///< The EditLine object + + bool active; ///< Are we a thing? + char *prompt; ///< The prompt we use + int prompt_shown; ///< Whether the prompt is shown now + + struct input_el_fn *fns; ///< Named functions + struct input_el_buffer *current; ///< Current input buffer +}; + +static void app_editline_init (struct input_el *self); + +static int +input_el__get_termios (int character, int fallback) +{ + if (!g_terminal.initialized) + return fallback; + + cc_t value = g_terminal.termios.c_cc[character]; + if (value == _POSIX_VDISABLE) + return fallback; + return value; } static void -input_on_terminal_resized (struct input *self) -{ - el_resize (self->editline); -} - -static void -input_bind (struct input *self, const char *seq, const char *function_name) -{ - el_set (self->editline, EL_BIND, seq, function_name, NULL); -} - -static void -input_bind_meta (struct input *self, char key, const char *function_name) -{ - char keyseq[] = { 'M', '-', key, 0 }; - input_bind (self, keyseq, function_name); -} - -static void -input_bind_control (struct input *self, char key, const char *function_name) -{ - char keyseq[] = { '^', key, 0 }; - input_bind (self, keyseq, function_name); -} - -static void -input_redisplay (struct input *self) +input_el__redisplay (void *input) { // See rl_redisplay() - // The character is VREPRINT (usually C-r) - // TODO: read it from terminal info - // XXX: could we potentially break UTF-8 with this? - char x[] = { ('R' - 'A' + 1), 0 }; + struct input_el *self = input; + char x[] = { input_el__get_termios (VREPRINT, 'R' - 0x40), 0 }; el_push (self->editline, x); // We have to do this or it gets stuck and nothing is done (void) el_gets (self->editline, NULL); } -static void -input_set_prompt (struct input *self, char *prompt) -{ - free (self->prompt); - self->prompt = prompt; - - if (self->prompt_shown > 0) - input_redisplay (self); -} - static char * -input_make_prompt (EditLine *editline) +input_el__make_prompt (EditLine *editline) { - struct input *self; + struct input_el *self; el_get (editline, EL_CLIENTDATA, &self); if (!self->prompt) return ""; @@ -627,42 +803,74 @@ input_make_prompt (EditLine *editline) } static char * -input_make_empty_prompt (EditLine *editline) +input_el__make_empty_prompt (EditLine *editline) { (void) editline; return ""; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + static void -input_erase_content (struct input *self) +input_el_ding (void *input) { + // XXX: this isn't probably very portable; + // we could use "bell" from terminfo but that creates a dependency + (void) input; + write (STDOUT_FILENO, "\a", 1); +} + +static const char * +input_el_get_prompt (void *input) +{ + struct input_el *self = input; + return self->prompt; +} + +static void +input_el_set_prompt (void *input, char *prompt) +{ + struct input_el *self = input; + free (self->prompt); + self->prompt = prompt; + + if (self->prompt_shown > 0) + input_el__redisplay (self); +} + +static void +input_el_clear_line (void *input) +{ + struct input_el *self = input; const LineInfoW *info = el_wline (self->editline); int len = info->lastchar - info->buffer; int point = info->cursor - info->buffer; el_cursor (self->editline, len - point); el_wdeletestr (self->editline, len); - input_redisplay (self); + input_el__redisplay (self); } static void -input_erase (struct input *self) +input_el__erase (struct input_el *self) { - el_set (self->editline, EL_PROMPT, input_make_empty_prompt); - input_erase_content (self); + el_set (self->editline, EL_PROMPT, input_el__make_empty_prompt); + input_el_clear_line (self); } static bool -input_insert (struct input *self, const char *s) +input_el_insert (void *input, const char *s) { + struct input_el *self = input; bool success = !*s || !el_insertstr (self->editline, s); if (self->prompt_shown > 0) - input_redisplay (self); + input_el__redisplay (self); return success; } static char * -input_get_content (struct input *self) +input_el_get_line (void *input) { + struct input_el *self = input; const LineInfo *info = el_line (self->editline); return xstrndup (info->buffer, info->lastchar - info->buffer); } @@ -670,11 +878,32 @@ input_get_content (struct input *self) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void -input_closure_forwarder (ffi_cif *cif, void *ret, void **args, void *user_data) +input_el_bind (void *input, const char *seq, const char *function_name) +{ + struct input_el *self = input; + el_set (self->editline, EL_BIND, seq, function_name, NULL); +} + +static void +input_el_bind_meta (void *input, char key, const char *function_name) +{ + char keyseq[] = { 'M', '-', key, 0 }; + input_el_bind (input, keyseq, function_name); +} + +static void +input_el_bind_control (void *input, char key, const char *function_name) +{ + char keyseq[] = { '^', key, 0 }; + input_el_bind (input, keyseq, function_name); +} + +static void +input_el__forward (ffi_cif *cif, void *ret, void **args, void *user_data) { (void) cif; - struct input_fn_data *data = user_data; + struct input_el_fn *data = user_data; *(unsigned char *) ret = data->callback (1, *(int *) args[1], data->user_data) ? CC_NORM : CC_ERROR; } @@ -690,11 +919,11 @@ ascii_to_wide (const char *ascii) } static void -input_add_fn (struct input *self, +input_el_register_fn (void *input, const char *name, const char *help, input_fn callback, void *user_data) { void *bound_fn = NULL; - struct input_fn_data *data = ffi_closure_alloc (sizeof *data, &bound_fn); + struct input_el_fn *data = ffi_closure_alloc (sizeof *data, &bound_fn); hard_assert (data); static ffi_cif cif; @@ -707,8 +936,9 @@ input_add_fn (struct input *self, data->name = ascii_to_wide (name); data->help = ascii_to_wide (help); hard_assert (ffi_prep_closure_loc (&data->closure, - &cif, input_closure_forwarder, data, bound_fn) == FFI_OK); + &cif, input_el__forward, data, bound_fn) == FFI_OK); + struct input_el *self = input; el_wset (self->editline, EL_ADDFN, data->name, data->help, bound_fn); LIST_PREPEND (self->fns, data); } @@ -716,12 +946,13 @@ input_add_fn (struct input *self, // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void -input_start (struct input *self, const char *program_name) +input_el_start (void *input, const char *program_name) { + struct input_el *self = input; self->editline = el_init (program_name, stdin, stdout, stderr); el_set (self->editline, EL_CLIENTDATA, self); el_set (self->editline, EL_PROMPT_ESC, - input_make_prompt, INPUT_START_IGNORE); + input_el__make_prompt, INPUT_START_IGNORE); el_set (self->editline, EL_SIGNAL, false); el_set (self->editline, EL_UNBUFFERED, true); el_set (self->editline, EL_EDITOR, "emacs"); @@ -733,10 +964,11 @@ input_start (struct input *self, const char *program_name) } static void -input_stop (struct input *self) +input_el_stop (void *input) { + struct input_el *self = input; if (self->prompt_shown > 0) - input_erase (self); + input_el__erase (self); el_end (self->editline); self->editline = NULL; @@ -744,10 +976,17 @@ input_stop (struct input *self) self->prompt_shown = false; } +static void +input_el_prepare (void *input, bool enabled) +{ + struct input_el *self = input; + el_set (self->editline, EL_PREP_TERM, enabled); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void -input_save_buffer (struct input *self, struct input_buffer *buffer) +input_el__save_buffer (struct input_el *self, struct input_el_buffer *buffer) { const LineInfoW *info = el_wline (self->editline); int len = info->lastchar - info->buffer; @@ -764,7 +1003,14 @@ input_save_buffer (struct input *self, struct input_buffer *buffer) } static void -input_restore_buffer (struct input *self, struct input_buffer *buffer) +input_el__save (struct input_el *self) +{ + if (self->current) + input_el__save_buffer (self, self->current); +} + +static void +input_el__restore_buffer (struct input_el *self, struct input_el_buffer *buffer) { if (buffer->saved_line) { @@ -777,70 +1023,92 @@ input_restore_buffer (struct input *self, struct input_buffer *buffer) } static void -input_switch_buffer (struct input *self, struct input_buffer *buffer) +input_el__restore (struct input_el *self) { if (self->current) - input_save_buffer (self, self->current); + input_el__restore_buffer (self, self->current); +} - input_restore_buffer (self, buffer); +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static void +input_el_buffer_switch (void *input, input_buffer_t input_buffer) +{ + struct input_el *self = input; + struct input_el_buffer *buffer = input_buffer; + + if (self->current) + input_el__save_buffer (self, self->current); + + input_el__restore_buffer (self, buffer); el_wset (self->editline, EL_HIST, history, buffer->history); self->current = buffer; } static void -input_destroy_buffer (struct input *self, struct input_buffer *buffer) +input_el_buffer_destroy (void *input, input_buffer_t input_buffer) { - (void) self; - input_buffer_destroy (buffer); + (void) input; + struct input_el_buffer *buffer = input_buffer; + + history_wend (buffer->history); + free (buffer->saved_line); + free (buffer); +} + +static input_buffer_t +input_el_buffer_new (void *input) +{ + (void) input; + struct input_el_buffer *self = xcalloc (1, sizeof *self); + self->history = history_winit (); + + HistEventW ev; + history_w (self->history, &ev, H_SETSIZE, HISTORY_LIMIT); + return self; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void -input_save (struct input *self) -{ - if (self->current) - input_save_buffer (self, self->current); -} - -static void -input_restore (struct input *self) -{ - if (self->current) - input_restore_buffer (self, self->current); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void -input_hide (struct input *self) +input_el_hide (void *input) { + struct input_el *self = input; if (!self->active || self->prompt_shown-- < 1) return; - input_save (self); - input_erase (self); + input_el__save (self); + input_el__erase (self); } static void -input_show (struct input *self) +input_el_show (void *input) { + struct input_el *self = input; if (!self->active || ++self->prompt_shown < 1) return; - input_restore (self); + input_el__restore (self); // XXX: the ignore doesn't quite work, see https://gnats.netbsd.org/47539 el_set (self->editline, - EL_PROMPT_ESC, input_make_prompt, INPUT_START_IGNORE); - input_redisplay (self); + EL_PROMPT_ESC, input_el__make_prompt, INPUT_START_IGNORE); + input_el__redisplay (self); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void -input_on_readable (struct input *self) +input_el_on_tty_resized (void *input) +{ + struct input_el *self = input; + el_resize (self->editline); +} + +static void +input_el_on_tty_readable (void *input) { // We bind the return key to process it how we need to + struct input_el *self = input; // el_gets() with EL_UNBUFFERED doesn't work with UTF-8, // we must use the wide-character interface @@ -849,16 +1117,43 @@ input_on_readable (struct input *self) if (!buf || count-- <= 0) return; - // The character is VEOF (usually C-d) - // TODO: read it from terminal info - if (count == 0 && buf[0] == ('D' - 'A' + 1)) + if (count == 0 && buf[0] == ('D' - 0x40) /* hardcoded VEOF in editline */) { el_deletestr (self->editline, 1); - input_redisplay (self); - input_ding (self); + input_el__redisplay (self); + input_el_ding (self); } } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static void +input_el_destroy (void *input) +{ + struct input_el *self = input; + LIST_FOR_EACH (struct input_el_fn, iter, self->fns) + { + free (iter->name); + free (iter->help); + ffi_closure_free (iter); + } + free (self->prompt); + free (self); +} + +#define XX(a) .a = input_el_ ## a, +static struct input_vtable input_el_vtable = { INPUT_VTABLE (XX) }; +#undef XX + +static struct input * +input_el_new (void) +{ + struct input_el *self = xcalloc (1, sizeof *self); + self->super.vtable = &input_el_vtable; + return &self->super; +} + +#define input_new input_el_new #endif // HAVE_EDITLINE // --- Application data -------------------------------------------------------- @@ -1198,7 +1493,8 @@ struct buffer enum buffer_type type; ///< Type of the buffer char *name; ///< The name of the buffer - struct input_buffer *input_data; ///< User interface data + struct input *input; ///< API for "input_data" + input_buffer_t input_data; ///< User interface data // Buffer contents: @@ -1220,11 +1516,12 @@ struct buffer }; static struct buffer * -buffer_new (void) +buffer_new (struct input *input) { struct buffer *self = xcalloc (1, sizeof *self); self->ref_count = 1; - self->input_data = input_buffer_new (); + self->input = input; + self->input_data = CALL (input, buffer_new); return self; } @@ -1233,7 +1530,15 @@ buffer_destroy (struct buffer *self) { free (self->name); if (self->input_data) - input_buffer_destroy (self->input_data); + { +#ifdef HAVE_READLINE + // FIXME: can't really free "history" contents from here, as we cannot + // be sure that the user interface pointer is valid and usable + input_rl__buffer_destroy_wo_history (self->input_data); +#else // ! HAVE_READLINE + CALL_ (self->input, buffer_destroy, self->input_data); +#endif // ! HAVE_READLINE + } LIST_FOR_EACH (struct buffer_line, iter, self->lines) buffer_line_destroy (iter); if (self->log_file) @@ -1667,7 +1972,7 @@ struct app_context iconv_t term_from_utf8; ///< UTF-8 to terminal encoding iconv_t latin1_to_utf8; ///< ISO Latin 1 to UTF-8 - struct input input; ///< User interface + struct input *input; ///< User interface struct poller_idle input_event; ///< Pending input event struct str_vector pending_input; ///< Pending input lines @@ -1751,7 +2056,7 @@ app_context_init (struct app_context *self) free (encoding); - input_init (&self->input); + self->input = input_new (); str_vector_init (&self->pending_input); str_init (&self->input_buffer); @@ -1776,7 +2081,8 @@ app_context_free (struct app_context *self) LIST_FOR_EACH (struct buffer, iter, self->buffers) { #ifdef HAVE_READLINE - input_destroy_buffer (&self->input, iter->input_data); + // We can use the user interface here; see buffer_destroy() + CALL_ (self->input, buffer_destroy, iter->input_data); iter->input_data = NULL; #endif // HAVE_READLINE buffer_unref (iter); @@ -1790,7 +2096,7 @@ app_context_free (struct app_context *self) iconv_close (self->term_from_utf8); iconv_close (self->term_to_utf8); - input_free (&self->input); + CALL (self->input, destroy); str_vector_free (&self->pending_input); str_free (&self->input_buffer); @@ -2198,102 +2504,6 @@ serialize_configuration (struct config_item *root, struct str *output) #define COLOR_256(name, c256) \ (((COLOR_ ## name) & 0xFFFF) | (((c256) & 0xFFFF) << 16)) -static struct -{ - bool initialized; ///< Terminal is available - bool stdout_is_tty; ///< `stdout' is a terminal - bool stderr_is_tty; ///< `stderr' is a terminal - - char *color_set_fg[256]; ///< Codes to set the foreground colour - char *color_set_bg[256]; ///< Codes to set the background colour - - int lines; ///< Number of lines - int columns; ///< Number of columns -} -g_terminal; - -static void -update_screen_size (void) -{ -#ifdef TIOCGWINSZ - if (!g_terminal.stdout_is_tty) - return; - - struct winsize size; - if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size)) - { - char *row = getenv ("LINES"); - char *col = getenv ("COLUMNS"); - unsigned long tmp; - g_terminal.lines = - (row && xstrtoul (&tmp, row, 10)) ? tmp : size.ws_row; - g_terminal.columns = - (col && xstrtoul (&tmp, col, 10)) ? tmp : size.ws_col; - } -#endif // TIOCGWINSZ -} - -static bool -init_terminal (void) -{ - int tty_fd = -1; - if ((g_terminal.stderr_is_tty = isatty (STDERR_FILENO))) - tty_fd = STDERR_FILENO; - if ((g_terminal.stdout_is_tty = isatty (STDOUT_FILENO))) - tty_fd = STDOUT_FILENO; - - int err; - if (tty_fd == -1 || setupterm (NULL, tty_fd, &err) == ERR) - return false; - - // Make sure all terminal features used by us are supported - if (!set_a_foreground || !set_a_background - || !enter_bold_mode || !exit_attribute_mode) - { - del_curterm (cur_term); - return false; - } - - // Make sure newlines are output correctly - struct termios termios; - if (!tcgetattr (tty_fd, &termios)) - { - termios.c_oflag |= ONLCR; - (void) tcsetattr (tty_fd, TCSADRAIN, &termios); - } - - g_terminal.lines = tigetnum ("lines"); - g_terminal.columns = tigetnum ("cols"); - update_screen_size (); - - int max = MIN (256, max_colors); - for (int i = 0; i < max; i++) - { - g_terminal.color_set_fg[i] = xstrdup (tparm (set_a_foreground, - i, 0, 0, 0, 0, 0, 0, 0, 0)); - g_terminal.color_set_bg[i] = xstrdup (tparm (set_a_background, - i, 0, 0, 0, 0, 0, 0, 0, 0)); - } - - return g_terminal.initialized = true; -} - -static void -free_terminal (void) -{ - if (!g_terminal.initialized) - return; - - for (int i = 0; i < 256; i++) - { - free (g_terminal.color_set_fg[i]); - free (g_terminal.color_set_bg[i]); - } - del_curterm (cur_term); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - typedef int (*terminal_printer_fn) (int); static int @@ -2346,13 +2556,13 @@ log_message_attributed (void *user_data, const char *quote, const char *fmt, FILE *stream = stderr; struct app_context *ctx = g_ctx; - input_hide (&ctx->input); + CALL (ctx->input, hide); print_attributed (ctx, stream, (intptr_t) user_data, "%s", quote); vprint_attributed (ctx, stream, (intptr_t) user_data, fmt, ap); fputs ("\n", stream); - input_show (&ctx->input); + CALL (ctx->input, show); } static ssize_t @@ -3216,11 +3426,11 @@ buffer_line_display (struct app_context *ctx, FORMATTER_ADD_ITEM (&f, IGNORE_ATTR, .attribute = 1); } - input_hide (&ctx->input); + CALL (ctx->input, hide); buffer_line_flush (line, &f, stdout, false); // Flush the trailing formatting reset item fflush (stdout); - input_show (&ctx->input); + CALL (ctx->input, show); } static void @@ -3289,7 +3499,7 @@ log_formatter (struct app_context *ctx, && !(flags & BUFFER_LINE_UNIMPORTANT); bool important = (flags & BUFFER_LINE_HIGHLIGHT) || unseen_pm; if (ctx->beep_on_highlight && important) - input_ding (&ctx->input); + CALL (ctx->input, ding); bool can_leak = false; if ((buffer == ctx->global_buffer) @@ -3487,7 +3697,7 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer) hard_assert (buffer != ctx->current_buffer); hard_assert (buffer != ctx->global_buffer); - input_destroy_buffer (&ctx->input, buffer->input_data); + CALL_ (ctx->input, buffer_destroy, buffer->input_data); buffer->input_data = NULL; // And make sure to unlink the buffer from "irc_buffer_map" @@ -3525,7 +3735,7 @@ static void buffer_print_backlog (struct app_context *ctx, struct buffer *buffer) { // The prompt can take considerable time to redraw - input_hide (&ctx->input); + CALL (ctx->input, hide); // That is, minus the readline prompt int display_limit = MAX (10, g_terminal.lines - 1); @@ -3575,7 +3785,7 @@ buffer_print_backlog (struct app_context *ctx, struct buffer *buffer) buffer_update_time (ctx, time (NULL)); refresh_prompt (ctx); - input_show (&ctx->input); + CALL (ctx->input, show); } static void @@ -3585,7 +3795,7 @@ buffer_activate (struct app_context *ctx, struct buffer *buffer) return; buffer_print_backlog (ctx, buffer); - input_switch_buffer (&ctx->input, buffer->input_data); + CALL_ (ctx->input, buffer_switch, buffer->input_data); // Now at last we can switch the pointers ctx->last_buffer = ctx->current_buffer; @@ -3736,7 +3946,7 @@ buffer_remove_safe (struct app_context *ctx, struct buffer *buffer) static void init_global_buffer (struct app_context *ctx) { - struct buffer *global = ctx->global_buffer = buffer_new (); + struct buffer *global = ctx->global_buffer = buffer_new (ctx->input); global->type = BUFFER_GLOBAL; global->name = xstrdup (PROGRAM_NAME); @@ -3786,7 +3996,7 @@ irc_get_or_make_user_buffer (struct server *s, const char *nickname) struct user *user = irc_get_or_make_user (s, nickname); // Open a new buffer for the user - buffer = buffer_new (); + buffer = buffer_new (s->ctx->input); buffer->type = BUFFER_PM; buffer->name = xstrdup_printf ("%s.%s", s->name, nickname); buffer->server = s; @@ -4202,7 +4412,7 @@ initiate_quit (struct app_context *ctx) log_global_status (ctx, "Shutting down"); // Hide the user interface - input_hide (&ctx->input); + CALL (ctx->input, hide); // Initiate a connection close struct str_map_iter iter; @@ -5281,10 +5491,11 @@ static void input_maybe_set_prompt (struct input *self, char *new_prompt) { // Redisplay can be an expensive operation - if (self->prompt && !strcmp (new_prompt, self->prompt)) + const char *prompt = CALL (self, get_prompt); + if (prompt && !strcmp (new_prompt, prompt)) free (new_prompt); else - input_set_prompt (self, new_prompt); + CALL_ (self, set_prompt, new_prompt); } static void @@ -5302,7 +5513,7 @@ refresh_prompt (struct app_context *ctx) if (have_attributes) { // XXX: to be completely correct, we should use tputs, but we cannot - input_maybe_set_prompt (&ctx->input, xstrdup_printf ("%c%s%c%s%c%s%c", + input_maybe_set_prompt (ctx->input, xstrdup_printf ("%c%s%c%s%c%s%c", INPUT_START_IGNORE, ctx->attrs[ATTR_PROMPT], INPUT_END_IGNORE, localized, @@ -5311,7 +5522,7 @@ refresh_prompt (struct app_context *ctx) free (localized); } else - input_maybe_set_prompt (&ctx->input, localized); + input_maybe_set_prompt (ctx->input, localized); } // --- Helpers ----------------------------------------------------------------- @@ -5829,7 +6040,7 @@ irc_handle_join (struct server *s, const struct irc_message *msg) // We've joined a new channel if (!channel && irc_is_this_us (s, msg->prefix)) { - buffer = buffer_new (); + buffer = buffer_new (s->ctx->input); buffer->type = BUFFER_CHANNEL; buffer->name = xstrdup_printf ("%s.%s", s->name, channel_name); buffer->server = s; @@ -7462,7 +7673,7 @@ server_add (struct app_context *ctx, s->config = subtree; // Add a buffer and activate it - struct buffer *buffer = s->buffer = buffer_new (); + struct buffer *buffer = s->buffer = buffer_new (ctx->input); buffer->type = BUFFER_SERVER; buffer->name = xstrdup (s->name); buffer->server = s; @@ -11241,15 +11452,11 @@ suspend_terminal (struct app_context *ctx) if (ctx->terminal_suspended++ > 0) return; -#ifdef HAVE_READLINE - rl_deprep_terminal (); -#elif defined (HAVE_EDITLINE) - el_set (ctx->input.editline, EL_PREP_TERM, 0); -#endif - toggle_bracketed_paste (false); - input_hide (&ctx->input); + CALL (ctx->input, hide); poller_fd_reset (&ctx->tty_event); + + CALL_ (ctx->input, prepare, false); } static void @@ -11259,18 +11466,14 @@ resume_terminal (struct app_context *ctx) return; update_screen_size (); -#ifdef HAVE_READLINE - rl_prep_terminal (true); -#elif defined (HAVE_EDITLINE) - el_set (ctx->input.editline, EL_PREP_TERM, 1); -#endif + CALL_ (ctx->input, prepare, true); toggle_bracketed_paste (true); // In theory we could just print all unseen messages but this is safer buffer_print_backlog (ctx, ctx->current_buffer); // Now it's safe to process any user input poller_fd_set (&ctx->tty_event, POLLIN); - input_show (&ctx->input); + CALL (ctx->input, show); } static pid_t @@ -11302,15 +11505,13 @@ spawn_helper_child (struct app_context *ctx) static void redraw_screen (struct app_context *ctx) { - input_hide (&ctx->input); - // If by some circumstance we had the wrong idea - input_on_terminal_resized (&ctx->input); + CALL (ctx->input, on_tty_resized); update_screen_size (); + CALL (ctx->input, hide); buffer_print_backlog (ctx, ctx->current_buffer); - - input_show (&ctx->input); + CALL (ctx->input, show); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -11325,7 +11526,7 @@ dump_input_to_file (struct app_context *ctx, char *template, struct error **e) if (fd < 0) FAIL ("%s", strerror (errno)); - char *input = input_get_content (&ctx->input); + char *input = CALL (ctx->input, get_line); bool success = xwrite (fd, input, strlen (input), e); free (input); @@ -11402,9 +11603,9 @@ input_editor_process (struct app_context *ctx) error_free (e); } else - input_erase_content (&ctx->input); + CALL (ctx->input, clear_line); - if (!input_insert (&ctx->input, input.str)) + if (!CALL_ (ctx->input, insert, input.str)) log_global_error (ctx, "#s: #s", "Input editing failed", "could not re-insert the modified text"); @@ -11589,8 +11790,8 @@ on_start_paste_mode (int count, int key, void *user_data) static void bind_common_keys (struct app_context *ctx) { - struct input *self = &ctx->input; -#define XX(...) input_add_fn (self, __VA_ARGS__, ctx); + struct input *self = ctx->input; +#define XX(...) CALL_ (self, register_fn, __VA_ARGS__, ctx); XX ("previous-buffer", "Previous buffer", on_previous_buffer) XX ("next-buffer", "Next buffer", on_next_buffer) XX ("goto-buffer", "Go to buffer", on_goto_buffer) @@ -11603,26 +11804,26 @@ bind_common_keys (struct app_context *ctx) XX ("start-paste-mode", "Bracketed paste", on_start_paste_mode) #undef XX - input_bind_control (self, 'p', "previous-buffer"); - input_bind_control (self, 'n', "next-buffer"); + CALL_ (self, bind_control, 'p', "previous-buffer"); + CALL_ (self, bind_control, 'n', "next-buffer"); // Redefine M-0 through M-9 to switch buffers for (int i = 0; i <= 9; i++) - input_bind_meta (self, '0' + i, "goto-buffer"); + CALL_ (self, bind_meta, '0' + i, "goto-buffer"); - input_bind_meta (self, '\t', "switch-buffer"); - input_bind_meta (self, 'm', "insert-attribute"); - input_bind_meta (self, 'h', "display-full-log"); - input_bind_meta (self, 'e', "edit-input"); + CALL_ (self, bind_meta, '\t', "switch-buffer"); + CALL_ (self, bind_meta, 'm', "insert-attribute"); + CALL_ (self, bind_meta, 'h', "display-full-log"); + CALL_ (self, bind_meta, 'e', "edit-input"); - if (key_f5) input_bind (self, key_f5, "previous-buffer"); - if (key_f6) input_bind (self, key_f6, "next-buffer"); - if (key_ppage) input_bind (self, key_ppage, "display-backlog"); + if (key_f5) CALL_ (self, bind, key_f5, "previous-buffer"); + if (key_f6) CALL_ (self, bind, key_f6, "next-buffer"); + if (key_ppage) CALL_ (self, bind, key_ppage, "display-backlog"); if (clear_screen) - input_bind_control (self, 'l', "redraw-screen"); + CALL_ (self, bind_control, 'l', "redraw-screen"); - input_bind (self, "\x1b[200~", "start-paste-mode"); + CALL_ (self, bind, "\x1b[200~", "start-paste-mode"); } // --- GNU Readline user actions ----------------------------------------------- @@ -11638,10 +11839,12 @@ on_readline_return (int count, int key) // Let readline pass the line to our input handler rl_done = 1; - // Hide the line, don't redisplay it struct app_context *ctx = g_ctx; - input_hide (&ctx->input); - input_restore (&ctx->input); + struct input_rl *self = (struct input_rl *) ctx->input; + + // Hide the line, don't redisplay it + CALL (ctx->input, hide); + input_rl__restore (self); return 0; } @@ -11649,6 +11852,7 @@ static void on_readline_input (char *line) { struct app_context *ctx = g_ctx; + struct input_rl *self = (struct input_rl *) ctx->input; if (line) { @@ -11663,15 +11867,15 @@ on_readline_input (char *line) else { // Prevent readline from showing the prompt twice for w/e reason - input_hide (&ctx->input); - input_restore (&ctx->input); + CALL (ctx->input, hide); + input_rl__restore (self); - input_ding (&ctx->input); + CALL (ctx->input, ding); } - if (ctx->input.active) + if (self->active) // Readline automatically redisplays it - ctx->input.prompt_shown = 1; + self->prompt_shown = 1; } static char ** @@ -11690,7 +11894,7 @@ static int app_readline_init (void) { struct app_context *ctx = g_ctx; - struct input *self = &ctx->input; + struct input *self = ctx->input; // XXX: maybe use rl_make_bare_keymap() and start from there; // our dear user could potentionally rig things up in a way that might @@ -11700,8 +11904,8 @@ app_readline_init (void) bind_common_keys (ctx); // Move native history commands - input_bind_meta (self, 'p', "previous-history"); - input_bind_meta (self, 'n', "next-history"); + CALL_ (self, bind_meta, 'p', "previous-history"); + CALL_ (self, bind_meta, 'n', "next-history"); // We need to hide the prompt and input first rl_bind_key (RETURN, rl_named_function ("send-line")); @@ -11709,7 +11913,7 @@ app_readline_init (void) rl_variable_bind ("completion-ignore-case", "on"); rl_bind_key (TAB, rl_named_function ("menu-complete")); if (key_btab) - input_bind (self, key_btab, "menu-complete-backward"); + CALL_ (self, bind, key_btab, "menu-complete-backward"); return 0; } @@ -11775,6 +11979,7 @@ on_editline_return (EditLine *editline, int key) { (void) key; struct app_context *ctx = g_ctx; + struct input_el *self = (struct input_el *) ctx->input; const LineInfoW *info = el_wline (editline); int len = info->lastchar - info->buffer; @@ -11788,7 +11993,7 @@ on_editline_return (EditLine *editline, int key) if (*line) { HistEventW ev; - history_w (ctx->input.current->history, &ev, H_ENTER, line); + history_w (self->current->history, &ev, H_ENTER, line); print_debug ("history: %d %ls", ev.num, ev.str); } free (line); @@ -11805,7 +12010,7 @@ on_editline_return (EditLine *editline, int key) } static void -app_editline_init (struct input *self) +app_editline_init (struct input_el *self) { // el_set() leaks memory in 20150325 and other versions, we need wchar_t el_wset (self->editline, EL_ADDFN, @@ -11814,20 +12019,21 @@ app_editline_init (struct input *self) L"complete", L"Complete word", on_editline_complete); bind_common_keys (g_ctx); + struct input *input = &self->super; // Move native history commands - input_bind_meta (self, 'p', "ed-prev-history"); - input_bind_meta (self, 'n', "ed-next-history"); + CALL_ (input, bind_meta, 'p', "ed-prev-history"); + CALL_ (input, bind_meta, 'n', "ed-next-history"); // No, editline, it's not supposed to kill the entire line - input_bind_control (self, 'w', "ed-delete-prev-word"); + CALL_ (input, bind_control, 'w', "ed-delete-prev-word"); // Just what are you doing? - input_bind_control (self, 'u', "vi-kill-line-prev"); + CALL_ (input, bind_control, 'u', "vi-kill-line-prev"); // We need to hide the prompt and input first - input_bind (self, "\n", "send-line"); + CALL_ (input, bind, "\n", "send-line"); - input_bind_control (self, 'i', "complete"); + CALL_ (input, bind_control, 'i', "complete"); // Source the user's defaults file el_source (self->editline, NULL); @@ -12157,14 +12363,14 @@ process_mirc_escape (const struct pollfd *fd, struct app_context *ctx) goto error; switch (buf->str[0]) { - case 'b': input_insert (&ctx->input, "\x02"); break; - case 'c': input_insert (&ctx->input, "\x03"); break; + case 'b': CALL_ (ctx->input, insert, "\x02"); break; + case 'c': CALL_ (ctx->input, insert, "\x03"); break; case 'i': - case ']': input_insert (&ctx->input, "\x1d"); break; + case ']': CALL_ (ctx->input, insert, "\x1d"); break; case 'u': - case '_': input_insert (&ctx->input, "\x1f"); break; - case 'v': input_insert (&ctx->input, "\x16"); break; - case 'o': input_insert (&ctx->input, "\x0f"); break; + case '_': CALL_ (ctx->input, insert, "\x1f"); break; + case 'v': CALL_ (ctx->input, insert, "\x16"); break; + case 'o': CALL_ (ctx->input, insert, "\x0f"); break; default: goto error; @@ -12172,7 +12378,7 @@ process_mirc_escape (const struct pollfd *fd, struct app_context *ctx) goto done; error: - input_ding (&ctx->input); + CALL (ctx->input, ding); done: str_reset (buf); ctx->awaiting_mirc_escape = false; @@ -12204,11 +12410,11 @@ process_bracketed_paste (const struct pollfd *fd, struct app_context *ctx) (text_len = BRACKETED_PASTE_LIMIT)); buf->str[text_len] = '\0'; - if (input_insert (&ctx->input, buf->str)) + if (CALL_ (ctx->input, insert, buf->str)) goto done; error: - input_ding (&ctx->input); + CALL (ctx->input, ding); log_global_error (ctx, "Paste failed"); done: str_reset (buf); @@ -12278,7 +12484,7 @@ on_tty_readable (const struct pollfd *fd, struct app_context *ctx) else if (ctx->in_bracketed_paste) process_bracketed_paste (fd, ctx); else if (!ctx->quitting) - input_on_readable (&ctx->input); + CALL (ctx->input, on_tty_readable); // User activity detected, stop current auto-away and start anew; // since they might have just changed the settings, do this last @@ -12573,7 +12779,7 @@ main (int argc, char *argv[]) // Initialize input so that we can switch to new buffers refresh_prompt (&ctx); - input_start (&ctx.input, argv[0]); + CALL_ (ctx.input, start, argv[0]); toggle_bracketed_paste (true); reset_autoaway (&ctx); @@ -12585,7 +12791,7 @@ main (int argc, char *argv[]) while (ctx.polling) poller_run (&ctx.poller); - input_stop (&ctx.input); + CALL (ctx.input, stop); if (get_config_boolean (ctx.config.root, "behaviour.save_on_quit")) save_configuration (&ctx);