From a5a1079a9c0c96242db9d0b894bf92fbb4fbbf55 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Tue, 5 May 2015 08:46:59 +0200
Subject: degesch: add support for libedit
Just another kind of evil.
---
CMakeLists.txt | 24 ++-
README | 5 +-
config.h.in | 3 +
degesch.c | 515 +++++++++++++++++++++++++++++++++++++++++++++++++--------
4 files changed, 476 insertions(+), 71 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a6ee3e9..0840b00 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,10 @@
project (uirc3 C)
cmake_minimum_required (VERSION 2.8.5)
+# Options
+option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
+option (WANT_EDITLINE "Use BSD libedit for the UI" OFF)
+
# Moar warnings
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
# -Wunused-function is pretty annoying here, as everything is static
@@ -38,7 +42,25 @@ else (CURSES_FOUND)
message (SEND_ERROR "Curses not found")
endif (ncursesw_FOUND)
+if ((WANT_READLINE AND WANT_EDITLINE) OR (NOT WANT_READLINE AND NOT WANT_EDITLINE))
+ message (SEND_ERROR "You have to choose either GNU Readline or libedit")
+elseif (WANT_READLINE)
+ list (APPEND project_libraries readline)
+elseif (WANT_EDITLINE)
+ pkg_check_modules (libedit REQUIRED libedit)
+ list (APPEND project_libraries ${libedit_LIBRARIES})
+ include_directories (${libedit_INCLUDE_DIRS})
+endif ((WANT_READLINE AND WANT_EDITLINE) OR (NOT WANT_READLINE AND NOT WANT_EDITLINE))
+
# Generate a configuration file
+if (WANT_READLINE)
+ set (HAVE_READLINE 1)
+endif (WANT_READLINE)
+
+if (WANT_EDITLINE)
+ set (HAVE_EDITLINE 1)
+endif (WANT_EDITLINE)
+
include (GNUInstallDirs)
set (plugin_dir ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME})
configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)
@@ -62,7 +84,7 @@ target_link_libraries (zyklonb ${project_libraries})
add_executable (degesch degesch.c kike-replies.c
${common_sources} ${common_headers})
-target_link_libraries (degesch ${project_libraries} readline)
+target_link_libraries (degesch ${project_libraries})
add_executable (kike kike.c kike-replies.c ${common_sources} ${common_headers})
target_link_libraries (kike ${project_libraries})
diff --git a/README b/README
index 46ff41a..6c8bbfb 100644
--- a/README
+++ b/README
@@ -50,14 +50,15 @@ Notable features:
Building
--------
Build dependencies: CMake, pkg-config, help2man, awk, sh, liberty (included)
-Runtime dependencies: openssl, curses (degesch), readline (degesch)
+Runtime dependencies: openssl, curses (degesch), readline or libedit (degesch)
$ git clone https://github.com/pjanouch/uirc3.git
$ git submodule init
$ git submodule update
$ mkdir build
$ cd build
- $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug
+ $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug \
+ -DWANT_READLINE=ON -DWANT_LIBEDIT=OFF
$ make
To install the application, you can do either the usual:
diff --git a/config.h.in b/config.h.in
index 265735d..63eca68 100644
--- a/config.h.in
+++ b/config.h.in
@@ -4,4 +4,7 @@
#define PROGRAM_VERSION "${project_VERSION}"
#define PLUGIN_DIR "${CMAKE_INSTALL_PREFIX}/${plugin_dir}"
+#cmakedefine HAVE_READLINE
+#cmakedefine HAVE_EDITLINE
+
#endif // ! CONFIG_H
diff --git a/degesch.c b/degesch.c
index 8ac776f..d5716a5 100644
--- a/degesch.c
+++ b/degesch.c
@@ -69,32 +69,55 @@ enum
#undef lines
#undef columns
+#ifdef HAVE_READLINE
#include
#include
+#endif // HAVE_READLINE
+
+#ifdef HAVE_EDITLINE
+#include
+#endif // HAVE_EDITLINE
// --- User interface ----------------------------------------------------------
-// Currently provided by GNU Readline. A libedit backend is also possible.
+// 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_point; ///< Saved cursor position
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" from here
+#elif defined HAVE_EDITLINE
+ history_wend (self->history);
+#endif // HAVE_EDITLINE
free (self->saved_line);
free (self);
}
@@ -103,9 +126,15 @@ 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
+ char *(*saved_prompt) (EditLine *); ///< Saved prompt function
+ char saved_char; ///< Saved char for the prompt
+#endif // HAVE_EDITLINE
char *prompt; ///< The prompt we use
int prompt_shown; ///< Whether the prompt is shown now
@@ -122,12 +151,19 @@ input_init (struct input *self)
static void
input_free (struct input *self)
{
+#ifdef HAVE_READLINE
free (self->saved_line);
+#endif // HAVE_READLINE
free (self->prompt);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+#ifdef HAVE_READLINE
+
+#define INPUT_START_IGNORE RL_PROMPT_START_IGNORE
+#define INPUT_END_IGNORE RL_PROMPT_END_IGNORE
+
#define input_ding(self) rl_ding ()
static void
@@ -181,8 +217,14 @@ static int app_readline_init (void);
static void on_readline_input (char *line);
static void
-input_start (struct input *self)
+input_start (struct input *self, const char *program_name)
{
+ (void) program_name;
+
+ using_history ();
+ // This can cause memory leaks, or maybe even a segfault. Funny, eh?
+ stifle_history (HISTORY_LIMIT);
+
rl_startup_hook = app_readline_init;
rl_catch_sigwinch = false;
rl_callback_handler_install (self->prompt, on_readline_input);
@@ -203,53 +245,6 @@ input_stop (struct input *self)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-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.
@@ -337,7 +332,7 @@ input_destroy_buffer (struct input *self, struct input_buffer *buffer)
#if RL_READLINE_VERSION >= 0x0603
if (buffer->history)
{
- // See buffer_activate() for why we need to do this BS
+ // See input_switch_buffer() for why we need to do this BS
rl_free_undo_list ();
// This is probably the only way we can free the history fully
@@ -355,6 +350,275 @@ input_destroy_buffer (struct input *self, struct input_buffer *buffer)
input_buffer_destroy (buffer);
}
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+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 ();
+}
+
+#endif // HAVE_READLINE
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+#ifdef HAVE_EDITLINE
+
+#define INPUT_START_IGNORE '\x01'
+#define INPUT_END_IGNORE '\x01'
+
+static void app_editline_init (struct input *self);
+static void on_editline_input (struct input *self, char *line);
+
+static void
+input_ding (struct input *self)
+{
+ (void) self;
+
+ // XXX: this isn't probably very portable
+ putc ('\a', stdout);
+}
+
+static void
+input_on_terminal_resized (struct input *self)
+{
+ el_resize (self->editline);
+}
+
+static void
+input_redisplay (struct input *self)
+{
+ // 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 };
+ 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)
+ input_redisplay (self);
+}
+
+static char *
+input_make_prompt (EditLine *editline)
+{
+ struct input *self;
+ el_get (editline, EL_CLIENTDATA, &self);
+ return self->prompt;
+}
+
+static char *
+input_make_empty_prompt (EditLine *editline)
+{
+ (void) editline;
+ return "";
+}
+
+static void
+input_erase (struct input *self)
+{
+ 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);
+
+ // XXX: this doesn't seem to save the escape character
+ el_get (self->editline, EL_PROMPT, &self->saved_prompt, &self->saved_char);
+ el_set (self->editline, EL_PROMPT, input_make_empty_prompt);
+ input_redisplay (self);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+input_start (struct input *self, const char *program_name)
+{
+ 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);
+ el_set (self->editline, EL_SIGNAL, false);
+ el_set (self->editline, EL_UNBUFFERED, true);
+ el_set (self->editline, EL_EDITOR, "emacs");
+
+ app_editline_init (self);
+ self->prompt_shown = 1;
+ self->active = true;
+}
+
+static void
+input_stop (struct input *self)
+{
+ if (self->prompt_shown > 0)
+ input_erase (self);
+
+ el_end (self->editline);
+ self->editline = NULL;
+ self->active = false;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+input_save_buffer (struct input *self, struct input_buffer *buffer)
+{
+ const LineInfoW *info = el_wline (self->editline);
+ int len = info->lastchar - info->buffer;
+ int point = info->cursor - info->buffer;
+
+ wchar_t *line = calloc (sizeof *info->buffer, len + 1);
+ memcpy (line, info->buffer, sizeof *info->buffer * len);
+ el_cursor (self->editline, len - point);
+ el_wdeletestr (self->editline, len);
+
+ buffer->saved_line = line;
+ buffer->saved_point = point;
+ buffer->saved_len = len;
+}
+
+static void
+input_restore_buffer (struct input *self, struct input_buffer *buffer)
+{
+ if (buffer->saved_line)
+ {
+ el_winsertstr (self->editline, buffer->saved_line);
+ el_cursor (self->editline,
+ -(buffer->saved_len - buffer->saved_point));
+ free (buffer->saved_line);
+ buffer->saved_line = NULL;
+ }
+}
+
+static void
+input_switch_buffer (struct input *self, struct input_buffer *buffer)
+{
+ if (self->current)
+ input_save_buffer (self, self->current);
+
+ input_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)
+{
+ (void) self;
+ input_buffer_destroy (buffer);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+input_save (struct input *self)
+{
+ input_save_buffer (self, self->current);
+}
+
+static void
+input_restore (struct input *self)
+{
+ input_restore_buffer (self, self->current);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+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);
+ // Would have used "saved_char" but it doesn't seem to work.
+ // And it doesn't even when it does anyway (it seems to just strip it).
+ el_set (self->editline,
+ EL_PROMPT_ESC, input_make_prompt, INPUT_START_IGNORE);
+ input_redisplay (self);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+input_on_readable (struct input *self)
+{
+ // We bind the return key to process it how we need to
+
+ // el_gets() with EL_UNBUFFERED doesn't work with UTF-8,
+ // we must use the wide-character interface
+ int count = 0;
+ const wchar_t *buf = el_wgets (self->editline, &count);
+ 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))
+ input_ding (self);
+}
+
+#endif // HAVE_EDITLINE
+
// --- Application data --------------------------------------------------------
// All text stored in our data structures is encoded in UTF-8.
@@ -2560,18 +2824,18 @@ refresh_prompt (struct app_context *ctx)
make_prompt (ctx, &prompt);
str_append_c (&prompt, ' ');
- if (!have_attributes)
- input_set_prompt (&ctx->input, xstrdup (prompt.str));
- else
+ if (have_attributes)
{
// XXX: to be completely correct, we should use tputs, but we cannot
input_set_prompt (&ctx->input, xstrdup_printf ("%c%s%c%s%c%s%c",
- RL_PROMPT_START_IGNORE, ctx->attrs[ATTR_PROMPT],
- RL_PROMPT_END_IGNORE,
+ INPUT_START_IGNORE, ctx->attrs[ATTR_PROMPT],
+ INPUT_END_IGNORE,
prompt.str,
- RL_PROMPT_START_IGNORE, ctx->attrs[ATTR_RESET],
- RL_PROMPT_END_IGNORE));
+ INPUT_START_IGNORE, ctx->attrs[ATTR_RESET],
+ INPUT_END_IGNORE));
}
+ else
+ input_set_prompt (&ctx->input, xstrdup (prompt.str));
str_free (&prompt);
}
@@ -4738,6 +5002,8 @@ irc_connect (struct server *s, bool *should_retry, struct error **e)
// --- User interface actions --------------------------------------------------
+#ifdef HAVE_READLINE
+
static int
on_readline_goto_buffer (int count, int key)
{
@@ -4755,7 +5021,7 @@ on_readline_goto_buffer (int count, int key)
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))
+ else if (!buffer_goto (ctx, n))
input_ding (self);
return 0;
}
@@ -4766,8 +5032,7 @@ 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));
+ buffer_activate (ctx, buffer_previous (ctx, count));
return 0;
}
@@ -4777,8 +5042,7 @@ 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));
+ buffer_activate (ctx, buffer_next (ctx, count));
return 0;
}
@@ -4862,6 +5126,125 @@ app_readline_init (void)
return 0;
}
+#endif // HAVE_READLINE
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+#ifdef HAVE_EDITLINE
+
+static unsigned char
+on_editline_goto_buffer (EditLine *editline, int key)
+{
+ (void) editline;
+
+ int n = key - '0';
+ if (n < 0 || n > 9)
+ return CC_ERROR;
+
+ // 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))
+ 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_return (EditLine *editline, int key)
+{
+ (void) key;
+ struct input *self = &g_ctx->input;
+
+ const LineInfoW *info = el_wline (editline);
+ int len = info->lastchar - info->buffer;
+ int point = info->cursor - info->buffer;
+
+ wchar_t *line = calloc (sizeof *info->buffer, len + 1);
+ memcpy (line, info->buffer, sizeof *info->buffer * len);
+
+ // XXX: Editline seems to remember its position in history,
+ // so it's not going to work as you'd expect it to
+ if (*line)
+ {
+ HistEventW ev;
+ history_w (self->current->history, &ev, H_ENTER, line);
+ print_debug ("history: %d %ls", ev.num, ev.str);
+ }
+
+ // Convert it to multibyte to reflect the Readline interface
+ size_t needed = wcstombs (NULL, line, 0) + 1;
+ char converted[needed];
+ if (!needed || wcstombs (converted, line, needed) == (size_t) -1)
+ print_error ("encoding conversion failed");
+ else
+ process_input (g_ctx, converted);
+
+ free (line);
+
+ el_cursor (editline, len - point);
+ el_wdeletestr (editline, len);
+ return CC_REFRESH;
+}
+
+static void
+app_editline_init (struct input *self)
+{
+ el_set (self->editline, EL_ADDFN, "goto-buffer",
+ "Go to buffer", on_editline_goto_buffer);
+ el_set (self->editline, EL_ADDFN, "previous-buffer",
+ "Previous buffer", on_editline_previous_buffer);
+ el_set (self->editline, EL_ADDFN, "next-buffer",
+ "Next buffer", on_editline_next_buffer);
+
+ // Redefine M-0 through M-9 to switch buffers
+ for (size_t i = 0; i < 10; i++)
+ {
+ char keyseq[] = { 'M', '-', '0' + i, 0 };
+ el_set (self->editline, EL_BIND, keyseq, "goto-buffer", NULL);
+ }
+
+ el_set (self->editline, EL_BIND, "M-p", "ed-prev-history", NULL);
+ el_set (self->editline, EL_BIND, "M-n", "ed-next-history", NULL);
+ el_set (self->editline, EL_BIND, "^P", "previous-buffer", NULL);
+ el_set (self->editline, EL_BIND, "^N", "next-buffer", NULL);
+
+ // Source the user's defaults file
+ el_source (self->editline, NULL);
+
+ el_set (self->editline, EL_ADDFN, "send-line",
+ "Send line", on_editline_return);
+ el_set (self->editline, EL_BIND, "\n", "send-line", NULL);
+}
+
+#endif // HAVE_EDITLINE
+
// --- I/O event handlers ------------------------------------------------------
static void
@@ -5095,10 +5478,6 @@ main (int argc, char *argv[])
SSL_load_error_strings ();
atexit (ERR_free_strings);
- using_history ();
- // This can cause memory leaks, or maybe even a segfault. Funny, eh?
- stifle_history (HISTORY_LIMIT);
-
setup_signal_handlers ();
register_config_modules (&ctx);
load_configuration (&ctx);
@@ -5109,7 +5488,7 @@ main (int argc, char *argv[])
buffer_activate (&ctx, ctx.server.buffer);
refresh_prompt (&ctx);
- input_start (&ctx.input);
+ input_start (&ctx.input, argv[0]);
// Connect to the server ASAP
poller_timer_set (&ctx.server.reconnect_tmr, 0);
--
cgit v1.2.3-70-g09d2