From 584d2f0295df01cff4ac24c336af40f4c2a762e2 Mon Sep 17 00:00:00 2001 From: Přemysl Janouch Date: Sun, 6 Mar 2016 17:59:45 +0100 Subject: degesch: use libffi to unify input callbacks And fuck you both, Readline and Editline. --- CMakeLists.txt | 8 +- README.adoc | 8 +- degesch.c | 610 +++++++++++++++++++++++++++------------------------------ 3 files changed, 292 insertions(+), 334 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1213ab6..ad131bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ include (AddThreads) find_package (Curses) find_package (PkgConfig REQUIRED) -pkg_check_modules (libssl REQUIRED libssl libcrypto) +pkg_check_modules (dependencies REQUIRED libssl libcrypto libffi) pkg_check_modules (ncursesw ncursesw) if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD") @@ -50,9 +50,9 @@ if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD") add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1) endif ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD") -list (APPEND project_libraries ${libssl_LIBRARIES}) -include_directories (${libssl_INCLUDE_DIRS}) -link_directories (${libssl_LIBRARY_DIRS}) +list (APPEND project_libraries ${dependencies_LIBRARIES}) +include_directories (${dependencies_INCLUDE_DIRS}) +link_directories (${dependencies_LIBRARY_DIRS}) # FIXME: other Lua versions may be acceptable, don't know yet pkg_search_module (lua lua53 lua5.3 lua-5.3 lua>=5.3) diff --git a/README.adoc b/README.adoc index 20fb718..25b7a16 100644 --- a/README.adoc +++ b/README.adoc @@ -10,7 +10,7 @@ All of them have these potentially interesting properties: - full IPv6 support - TLS support, including client certificates - - minimal dependencies + - lean on dependencies (with the exception of 'degesch') - compact and arguably easy to hack on - permissive license @@ -66,9 +66,9 @@ support (even though socksify can add that easily to any program). Building -------- Build dependencies: CMake, pkg-config, help2man, awk, sh, liberty (included) + -Runtime dependencies: openssl, curses (degesch), - readline >= 6.0 or libedit >= 2013-07-12 (degesch), - lua >= 5.3 (degesch, optional) +Runtime dependencies: openssl + +Additionally for degesch: curses, libffi, lua >= 5.3 (optional), + readline >= 6.0 or libedit >= 2013-07-12 $ git clone --recursive https://github.com/pjanouch/uirc3.git $ mkdir uirc3/build diff --git a/degesch.c b/degesch.c index 57b1bf7..3d0130a 100644 --- a/degesch.c +++ b/degesch.c @@ -68,6 +68,8 @@ enum #undef lines #undef columns +#include + #ifdef HAVE_READLINE #include #include @@ -134,6 +136,22 @@ input_buffer_destroy (struct input_buffer *self) 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? @@ -145,6 +163,7 @@ struct input #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 @@ -164,6 +183,14 @@ input_free (struct input *self) #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); } @@ -279,6 +306,45 @@ input_get_content (struct input *self) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +static void +input_closure_forwarder (ffi_cif *cif, void *ret, void **args, void *user_data) +{ + (void) cif; + + struct input_fn_data *data = user_data; + if (!data->callback + (*(int *) args[0], UNMETA (*(int *) args[1]), data->user_data)) + rl_ding (); + *(int *) ret = 0; +} + +static void +input_add_fn (struct input *self, + const char *name, const char *help, input_fn callback, void *user_data) +{ + (void) help; + + void *bound_fn = NULL; + struct input_fn_data *data = ffi_closure_alloc (sizeof *data, &bound_fn); + hard_assert (data); + + static ffi_cif cif; + static ffi_type *args[2] = { &ffi_type_sint, &ffi_type_sint }; + hard_assert (ffi_prep_cif + (&cif, FFI_DEFAULT_ABI, 2, &ffi_type_sint, args) == FFI_OK); + + data->prev = data->next = NULL; + 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); + + rl_add_defun (name, (rl_command_func_t *) bound_fn, -1); + LIST_PREPEND (self->fns, data); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + static int app_readline_init (void); static void on_readline_input (char *line); static char **app_readline_completion (const char *text, int start, int end); @@ -603,6 +669,52 @@ input_get_content (struct input *self) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +static void +input_closure_forwarder (ffi_cif *cif, void *ret, void **args, void *user_data) +{ + (void) cif; + + struct input_fn_data *data = user_data; + *(unsigned char *) ret = data->callback + (1, *(int *) args[1], data->user_data) ? CC_NORM : CC_ERROR; +} + +static wchar_t * +ascii_to_wide (const char *ascii) +{ + size_t len = strlen (ascii) + 1; + wchar_t *wide = xcalloc (sizeof *wide, len); + while (len--) + hard_assert ((wide[len] = (unsigned char) ascii[len]) < 0x80); + return wide; +} + +static void +input_add_fn (struct input *self, + 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); + hard_assert (data); + + static ffi_cif cif; + static ffi_type *args[2] = { &ffi_type_pointer, &ffi_type_sint }; + hard_assert (ffi_prep_cif + (&cif, FFI_DEFAULT_ABI, 2, &ffi_type_uchar, args) == FFI_OK); + + data->user_data = user_data; + data->callback = callback; + 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); + + el_wset (self->editline, EL_ADDFN, data->name, data->help, bound_fn); + LIST_PREPEND (self->fns, data); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + static void input_start (struct input *self, const char *program_name) { @@ -11161,40 +11273,6 @@ resume_terminal (struct app_context *ctx) input_show (&ctx->input); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -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); - update_screen_size (); - - buffer_print_backlog (ctx, ctx->current_buffer); - - input_show (&ctx->input); -} - -static bool -jump_to_buffer (struct app_context *ctx, int n) -{ - if (n < 0 || n > 9) - return false; - - // There's no buffer zero - if (n == 0) - n = 10; - - 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)) - return false; - return true; -} - static pid_t spawn_helper_child (struct app_context *ctx) { @@ -11222,69 +11300,20 @@ spawn_helper_child (struct app_context *ctx) } static void -launch_backlog_helper (struct app_context *ctx, int backlog_fd) +redraw_screen (struct app_context *ctx) { - hard_assert (!ctx->running_backlog_helper); - switch (spawn_helper_child (ctx)) - { - case 0: - dup2 (backlog_fd, STDIN_FILENO); - execl ("/bin/sh", "/bin/sh", "-c", get_config_string - (ctx->config.root, "behaviour.backlog_helper"), NULL); - print_error ("%s: %s", - "Failed to launch backlog helper", strerror (errno)); - _exit (EXIT_FAILURE); - case -1: - log_global_error (ctx, "#s: #l", - "Failed to launch backlog helper", strerror (errno)); - break; - default: - ctx->running_backlog_helper = true; - } -} + input_hide (&ctx->input); -static void -display_backlog (struct app_context *ctx) -{ - FILE *backlog = tmpfile (); - if (!backlog) - { - log_global_error (ctx, "#s: #l", - "Failed to create a temporary file", strerror (errno)); - return; - } + // If by some circumstance we had the wrong idea + input_on_terminal_resized (&ctx->input); + update_screen_size (); - for (struct buffer_line *line = ctx->current_buffer->lines; - line; line = line->next) - buffer_line_write_to_backlog (ctx, line, backlog); + buffer_print_backlog (ctx, ctx->current_buffer); - rewind (backlog); - set_cloexec (fileno (backlog)); - launch_backlog_helper (ctx, fileno (backlog)); - fclose (backlog); + input_show (&ctx->input); } -static void -display_full_log (struct app_context *ctx) -{ - char *path = buffer_get_log_path (ctx->current_buffer); - FILE *full_log = fopen (path, "rb"); - free (path); - - if (!full_log) - { - log_global_error (ctx, "Failed to open log file for #s: #l", - ctx->current_buffer->name, strerror (errno)); - return; - } - - if (ctx->current_buffer->log_file) - fflush (ctx->current_buffer->log_file); - - set_cloexec (fileno (full_log)); - launch_backlog_helper (ctx, fileno (full_log)); - fclose (full_log); -} +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool dump_input_to_file (struct app_context *ctx, char *template, struct error **e) @@ -11324,12 +11353,16 @@ try_dump_input_to_file (struct app_context *ctx) return NULL; } -static void -launch_input_editor (struct app_context *ctx) +static bool +on_edit_input (int count, int key, void *user_data) { + (void) count; + (void) key; + struct app_context *ctx = user_data; + char *filename; if (!(filename = try_dump_input_to_file (ctx))) - return; + return false; const char *command; if (!(command = getenv ("VISUAL")) @@ -11353,6 +11386,7 @@ launch_input_editor (struct app_context *ctx) ctx->running_editor = true; ctx->editor_filename = filename; } + return true; } static void @@ -11390,162 +11424,222 @@ input_editor_cleanup (struct app_context *ctx) ctx->running_editor = false; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + static void -bind_common_keys (struct app_context *ctx) +launch_backlog_helper (struct app_context *ctx, int backlog_fd) { - struct input *self = &ctx->input; - input_bind_control (self, 'p', "previous-buffer"); - input_bind_control (self, '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"); - - 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"); - - 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 (clear_screen) - input_bind_control (self, 'l', "redraw-screen"); - - input_bind (self, "\x1b[200~", "start-paste-mode"); + hard_assert (!ctx->running_backlog_helper); + switch (spawn_helper_child (ctx)) + { + case 0: + dup2 (backlog_fd, STDIN_FILENO); + execl ("/bin/sh", "/bin/sh", "-c", get_config_string + (ctx->config.root, "behaviour.backlog_helper"), NULL); + print_error ("%s: %s", + "Failed to launch backlog helper", strerror (errno)); + _exit (EXIT_FAILURE); + case -1: + log_global_error (ctx, "#s: #l", + "Failed to launch backlog helper", strerror (errno)); + break; + default: + ctx->running_backlog_helper = true; + } } -// --- GNU Readline user actions ----------------------------------------------- - -#ifdef HAVE_READLINE - -static int -on_readline_goto_buffer (int count, int key) +static bool +on_display_backlog (int count, int key, void *user_data) { (void) count; + (void) key; + struct app_context *ctx = user_data; - struct app_context *ctx = g_ctx; - if (!jump_to_buffer (ctx, UNMETA (key) - '0')) - input_ding (&ctx->input); - return 0; -} + FILE *backlog = tmpfile (); + if (!backlog) + { + log_global_error (ctx, "#s: #l", + "Failed to create a temporary file", strerror (errno)); + return false; + } -static int -on_readline_previous_buffer (int count, int key) -{ - (void) key; + for (struct buffer_line *line = ctx->current_buffer->lines; + line; line = line->next) + buffer_line_write_to_backlog (ctx, line, backlog); - struct app_context *ctx = g_ctx; - buffer_activate (ctx, buffer_previous (ctx, count)); - return 0; + rewind (backlog); + set_cloexec (fileno (backlog)); + launch_backlog_helper (ctx, fileno (backlog)); + fclose (backlog); + return true; } -static int -on_readline_next_buffer (int count, int key) +static bool +on_display_full_log (int count, int key, void *user_data) { + (void) count; (void) key; + struct app_context *ctx = user_data; - struct app_context *ctx = g_ctx; - buffer_activate (ctx, buffer_next (ctx, count)); - return 0; + char *path = buffer_get_log_path (ctx->current_buffer); + FILE *full_log = fopen (path, "rb"); + free (path); + + if (!full_log) + { + log_global_error (ctx, "Failed to open log file for #s: #l", + ctx->current_buffer->name, strerror (errno)); + return false; + } + + if (ctx->current_buffer->log_file) + fflush (ctx->current_buffer->log_file); + + set_cloexec (fileno (full_log)); + launch_backlog_helper (ctx, fileno (full_log)); + fclose (full_log); + return true; } -static int -on_readline_switch_buffer (int count, int key) +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static bool +on_goto_buffer (int count, int key, void *user_data) { (void) count; - (void) key; + struct app_context *ctx = user_data; - struct app_context *ctx = g_ctx; - if (ctx->last_buffer) - buffer_activate (ctx, ctx->last_buffer); - else - input_ding (&ctx->input); - return 0; + int n = key - '0'; + if (n < 0 || n > 9) + return false; + + // There's no buffer zero + if (n == 0) + n = 10; + + if (!ctx->last_buffer || buffer_get_index (ctx, ctx->current_buffer) != n) + return buffer_goto (ctx, n); + + // Fast switching between two buffers + buffer_activate (ctx, ctx->last_buffer); + return true; } -static int -on_readline_display_backlog (int count, int key) +static bool +on_previous_buffer (int count, int key, void *user_data) { - (void) count; (void) key; - - struct app_context *ctx = g_ctx; - display_backlog (ctx); - return 0; + buffer_activate (user_data, buffer_previous (user_data, count)); + return true; } -static int -on_readline_display_full_log (int count, int key) +static bool +on_next_buffer (int count, int key, void *user_data) { - (void) count; (void) key; - - struct app_context *ctx = g_ctx; - display_full_log (ctx); - return 0; + buffer_activate (user_data, buffer_next (user_data, count)); + return true; } -static int -on_readline_edit_input (int count, int key) +static bool +on_switch_buffer (int count, int key, void *user_data) { (void) count; (void) key; + struct app_context *ctx = user_data; - struct app_context *ctx = g_ctx; - launch_input_editor (ctx); - return 0; + if (!ctx->last_buffer) + return false; + buffer_activate (ctx, ctx->last_buffer); + return true; } -static int -on_readline_redraw_screen (int count, int key) +static bool +on_redraw_screen (int count, int key, void *user_data) { (void) count; (void) key; - struct app_context *ctx = g_ctx; - redraw_screen (ctx); - return 0; + redraw_screen (user_data); + return true; } -static int -on_readline_insert_attribute (int count, int key) +static bool +on_insert_attribute (int count, int key, void *user_data) { (void) count; (void) key; - struct app_context *ctx = g_ctx; + struct app_context *ctx = user_data; ctx->awaiting_mirc_escape = true; - return 0; + return true; } -static int -on_readline_start_paste_mode (int count, int key) +static bool +on_start_paste_mode (int count, int key, void *user_data) { (void) count; (void) key; - struct app_context *ctx = g_ctx; + struct app_context *ctx = user_data; ctx->in_bracketed_paste = true; - return 0; + return true; } +static void +bind_common_keys (struct app_context *ctx) +{ + struct input *self = &ctx->input; +#define XX(...) input_add_fn (self, __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) + XX ("switch-buffer", "Switch buffer", on_switch_buffer) + XX ("display-backlog", "Show backlog", on_display_backlog) + XX ("display-full-log", "Show full log", on_display_full_log) + XX ("edit-input", "Edit input", on_edit_input) + XX ("redraw-screen", "Redraw screen", on_redraw_screen) + XX ("insert-attribute", "mIRC formatting", on_insert_attribute) + 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"); + + // Redefine M-0 through M-9 to switch buffers + for (int i = 0; i <= 9; i++) + input_bind_meta (self, '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"); + + 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 (clear_screen) + input_bind_control (self, 'l', "redraw-screen"); + + input_bind (self, "\x1b[200~", "start-paste-mode"); +} + +// --- GNU Readline user actions ----------------------------------------------- + +#ifdef HAVE_READLINE + 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; // Hide the line, don't redisplay it + struct app_context *ctx = g_ctx; input_hide (&ctx->input); input_restore (&ctx->input); return 0; @@ -11602,18 +11696,7 @@ app_readline_init (void) // 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); - rl_add_defun ("goto-buffer", on_readline_goto_buffer, -1); - rl_add_defun ("switch-buffer", on_readline_switch_buffer, -1); - rl_add_defun ("display-backlog", on_readline_display_backlog, -1); - rl_add_defun ("display-full-log", on_readline_display_full_log, -1); - rl_add_defun ("edit-input", on_readline_edit_input, -1); - rl_add_defun ("redraw-screen", on_readline_redraw_screen, -1); - rl_add_defun ("insert-attribute", on_readline_insert_attribute, -1); - rl_add_defun ("start-paste-mode", on_readline_start_paste_mode, -1); - rl_add_defun ("send-line", on_readline_return, -1); - + rl_add_defun ("send-line", on_readline_return, -1); bind_common_keys (ctx); // Move native history commands @@ -11636,113 +11719,6 @@ app_readline_init (void) #ifdef HAVE_EDITLINE -static unsigned char -on_editline_goto_buffer (EditLine *editline, int key) -{ - (void) editline; - - struct app_context *ctx = g_ctx; - if (!jump_to_buffer (ctx, key - '0')) - return CC_ERROR; - return CC_NORM; -} - -static unsigned char -on_editline_previous_buffer (EditLine *editline, int key) -{ - (void) editline; - (void) key; - - struct app_context *ctx = g_ctx; - buffer_activate (ctx, buffer_previous (ctx, 1)); - return CC_NORM; -} - -static unsigned char -on_editline_next_buffer (EditLine *editline, int key) -{ - (void) editline; - (void) key; - - struct app_context *ctx = g_ctx; - buffer_activate (ctx, buffer_next (ctx, 1)); - return CC_NORM; -} - -static unsigned char -on_editline_switch_buffer (EditLine *editline, int key) -{ - (void) editline; - (void) key; - - struct app_context *ctx = g_ctx; - if (ctx->last_buffer) - buffer_activate (ctx, ctx->last_buffer); - else - input_ding (&ctx->input); - return CC_NORM; -} - -static unsigned char -on_editline_display_backlog (EditLine *editline, int key) -{ - (void) editline; - (void) key; - - display_backlog (g_ctx); - return CC_NORM; -} - -static unsigned char -on_editline_display_full_log (EditLine *editline, int key) -{ - (void) editline; - (void) key; - - display_full_log (g_ctx); - return CC_NORM; -} - -static unsigned char -on_editline_edit_input (EditLine *editline, int key) -{ - (void) editline; - (void) key; - - launch_input_editor (g_ctx); - return CC_NORM; -} - -static unsigned char -on_editline_redraw_screen (EditLine *editline, int key) -{ - (void) editline; - (void) key; - - redraw_screen (g_ctx); - return CC_NORM; -} - -static unsigned char -on_editline_insert_attribute (EditLine *editline, int key) -{ - (void) editline; - (void) key; - - g_ctx->awaiting_mirc_escape = true; - return CC_NORM; -} - -static unsigned char -on_editline_start_paste_mode (EditLine *editline, int key) -{ - (void) editline; - (void) key; - - g_ctx->in_bracketed_paste = true; - return CC_NORM; -} - static unsigned char on_editline_complete (EditLine *editline, int key) { @@ -11831,29 +11807,11 @@ on_editline_return (EditLine *editline, int key) static void app_editline_init (struct input *self) { -#define XX(name, help, fn) { name, help, on_editline_ ## fn }, - // el_set() leaks memory in 20150325 and other versions, we need wchar_t - static const struct { const wchar_t *name; const wchar_t *help; - unsigned char (*func) (EditLine *, int); } x[] = - { - XX( L"goto-buffer", L"Go to buffer", goto_buffer ) - XX( L"previous-buffer", L"Previous buffer", previous_buffer ) - XX( L"next-buffer", L"Next buffer", next_buffer ) - XX( L"switch-buffer", L"Switch buffer", switch_buffer ) - XX( L"display-backlog", L"Show backlog", display_backlog ) - XX( L"display-full-log", L"Show full log", display_full_log ) - XX( L"edit-input", L"Edit input", edit_input ) - XX( L"redraw-screen", L"Redraw screen", redraw_screen ) - XX( L"insert-attribute", L"mIRC formatting", insert_attribute ) - XX( L"start-paste-mode", L"Bracketed paste", start_paste_mode ) - XX( L"send-line", L"Send line", return ) - XX( L"complete", L"Complete word", complete ) - }; - for (size_t i = 0; i < N_ELEMENTS (x); i++) - el_wset (self->editline, EL_ADDFN, x[i].name, x[i].help, x[i].func); - -#undef XX + el_wset (self->editline, EL_ADDFN, + L"send-line", L"Send line", on_editline_return); + el_wset (self->editline, EL_ADDFN, + L"complete", L"Complete word", on_editline_complete); bind_common_keys (g_ctx); -- cgit v1.2.3-70-g09d2