From 63df918482ee7c3f672ec5db7518fa2057cdf4ef Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Thu, 24 Dec 2015 15:45:49 +0100
Subject: Rewrite the input layer
Now we also support libedit for the backend.
---
CMakeLists.txt | 19 +-
README.adoc | 3 +-
config.h.in | 3 +
json-rpc-shell.c | 827 ++++++++++++++++++++++++++++++++++++++++++++++++-------
4 files changed, 749 insertions(+), 103 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a706b0a..1e2f51d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,10 @@
project (json-rpc-shell C)
cmake_minimum_required (VERSION 2.8.5)
+# Options
+option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
+option (WANT_LIBEDIT "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
@@ -28,7 +32,7 @@ find_package (LibEV REQUIRED)
pkg_check_modules (ncursesw ncursesw)
set (project_libraries ${dependencies_LIBRARIES}
- ${libssl_LIBRARIES} ${LIBEV_LIBRARIES} readline)
+ ${libssl_LIBRARIES} ${LIBEV_LIBRARIES})
include_directories (${dependencies_INCLUDE_DIRS}
${libssl_INCLUDE_DIRS} ${LIBEV_INCLUDE_DIRS})
@@ -42,7 +46,20 @@ else (CURSES_FOUND)
message (SEND_ERROR "Curses not found")
endif (ncursesw_FOUND)
+if ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT))
+ message (SEND_ERROR "You have to choose either GNU Readline or libedit")
+elseif (WANT_READLINE)
+ list (APPEND project_libraries readline)
+elseif (WANT_LIBEDIT)
+ pkg_check_modules (libedit REQUIRED libedit)
+ list (APPEND project_libraries ${libedit_LIBRARIES})
+ include_directories (${libedit_INCLUDE_DIRS})
+endif ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT))
+
# Generate a configuration file
+set (HAVE_READLINE "${WANT_READLINE}")
+set (HAVE_EDITLINE "${WANT_LIBEDIT}")
+
configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)
include_directories (${PROJECT_BINARY_DIR})
diff --git a/README.adoc b/README.adoc
index 4856080..dcf8128 100644
--- a/README.adoc
+++ b/README.adoc
@@ -29,7 +29,8 @@ Building and Running
--------------------
Build dependencies: CMake, pkg-config, help2man,
liberty (included), http-parser (included) +
-Runtime dependencies: libev, Jansson, cURL, readline, openssl
+Runtime dependencies: libev, Jansson, cURL, openssl,
+ readline or libedit >= 2013-07-12,
$ git clone --recursive https://github.com/pjanouch/json-rpc-shell.git
$ mkdir json-rpc-shell/build
diff --git a/config.h.in b/config.h.in
index 89cd306..b7b4101 100644
--- a/config.h.in
+++ b/config.h.in
@@ -4,5 +4,8 @@
#define PROGRAM_NAME "${PROJECT_NAME}"
#define PROGRAM_VERSION "${project_VERSION}"
+#cmakedefine HAVE_READLINE
+#cmakedefine HAVE_EDITLINE
+
#endif // ! CONFIG_H
diff --git a/json-rpc-shell.c b/json-rpc-shell.c
index a1b3ae4..16bc31e 100644
--- a/json-rpc-shell.c
+++ b/json-rpc-shell.c
@@ -56,8 +56,6 @@ enum
#include
#include
-#include
-#include
#include
#include
#include
@@ -80,6 +78,7 @@ static struct
bool stdout_is_tty; ///< `stdout' is a terminal
bool stderr_is_tty; ///< `stderr' is a terminal
+ struct termios termios; ///< Terminal attributes
char *color_set[8]; ///< Codes to set the foreground colour
}
g_terminal;
@@ -97,6 +96,9 @@ init_terminal (void)
if (tty_fd == -1 || setupterm (NULL, tty_fd, &err) == ERR)
return false;
+ if (tcgetattr (tty_fd, &g_terminal.termios))
+ return false;
+
// Make sure all terminal features used by us are supported
if (!set_a_foreground || !enter_bold_mode || !exit_attribute_mode)
{
@@ -122,6 +124,672 @@ free_terminal (void)
del_curterm (cur_term);
}
+// --- User interface ----------------------------------------------------------
+
+// Not trying to do anything crazy here like switchable buffers.
+// Not trying to be too universal here either, it's not going to be reusable.
+
+struct input
+{
+ struct input_vtable *vtable; ///< Virtual methods
+ void *user_data; ///< User data for callbacks
+
+ /// Process a single line input by the user
+ void (*on_input) (char *line, void *user_data);
+};
+
+struct input_vtable
+{
+ /// Start the interface under the given program name
+ void (*start) (struct input *input, const char *program_name);
+ /// Stop the interface
+ void (*stop) (struct input *input);
+ /// Destroy the object
+ void (*destroy) (struct input *input);
+
+ /// Hide the prompt if shown
+ void (*hide) (struct input *input);
+ /// Show the prompt if hidden
+ void (*show) (struct input *input);
+ /// Change the prompt string; takes ownership
+ void (*set_prompt) (struct input *input, char *prompt);
+ /// Ring the terminal bell
+ void (*ding) (struct input *input);
+
+ /// Load history from file
+ bool (*load_history) (struct input *input, const char *filename,
+ struct error **e);
+ /// Save history to file
+ bool (*save_history) (struct input *input, const char *filename,
+ struct error **e);
+
+ /// Handle terminal resize
+ void (*on_terminal_resized) (struct input *input);
+ /// Handle terminal input
+ void (*on_tty_readable) (struct input *input);
+};
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+#ifdef HAVE_READLINE
+
+#include
+#include
+
+#define INPUT_START_IGNORE RL_PROMPT_START_IGNORE
+#define INPUT_END_IGNORE RL_PROMPT_END_IGNORE
+
+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
+};
+
+/// Unfortunately Readline cannot pass us any pointer value in its callbacks
+/// that would eliminate the need to use global variables ourselves
+static struct input_rl *g_input_rl;
+
+static void
+input_rl_erase (void)
+{
+ rl_set_prompt ("");
+ rl_replace_line ("", false);
+ rl_redisplay ();
+}
+
+static void
+input_rl_on_input (char *line)
+{
+ struct input_rl *self = g_input_rl;
+
+ // The prompt should always be visible at the moment we process input keys;
+ // confirming it de facto hides it because we move onto a new line
+ if (line)
+ self->prompt_shown = 0;
+ if (line && *line)
+ add_history (line);
+
+ self->super.on_input (line, self->super.user_data);
+ free (line);
+
+ // Readline automatically redisplays the prompt after we're done here;
+ // we could have actually hidden it by now in preparation of a quit though
+ if (line)
+ self->prompt_shown++;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+input_rl_start (struct input *input, const char *program_name)
+{
+ struct input_rl *self = (struct input_rl *) input;
+ using_history ();
+ // This can cause memory leaks, or maybe even a segfault. Funny, eh?
+ stifle_history (HISTORY_LIMIT);
+
+ const char *slash = strrchr (program_name, '/');
+ rl_readline_name = slash ? ++slash : program_name;
+ rl_catch_sigwinch = false;
+
+ hard_assert (self->prompt != NULL);
+ rl_callback_handler_install (self->prompt, input_rl_on_input);
+
+ self->active = true;
+ self->prompt_shown = 1;
+ g_input_rl = self;
+}
+
+static void
+input_rl_stop (struct input *input)
+{
+ struct input_rl *self = (struct input_rl *) input;
+ if (self->prompt_shown > 0)
+ input_rl_erase ();
+
+ // This is okay so long as we're not called from within readline
+ rl_callback_handler_remove ();
+
+ self->active = false;
+ self->prompt_shown = 0;
+ g_input_rl = NULL;
+}
+
+static void
+input_rl_destroy (struct input *input)
+{
+ struct input_rl *self = (struct input_rl *) input;
+
+ if (self->active)
+ input_rl_stop (input);
+
+ free (self->saved_line);
+ free (self->prompt);
+ free (self);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+input_rl_hide (struct input *input)
+{
+ struct input_rl *self = (struct input_rl *) input;
+ if (!self->active || self->prompt_shown-- < 1)
+ return;
+
+ hard_assert (!self->saved_line);
+
+ self->saved_point = rl_point;
+ self->saved_mark = rl_mark;
+ self->saved_line = rl_copy_text (0, rl_end);
+
+ input_rl_erase ();
+}
+
+static void
+input_rl_show (struct input *input)
+{
+ struct input_rl *self = (struct input_rl *) input;
+ if (!self->active || ++self->prompt_shown < 1)
+ return;
+
+ hard_assert (self->saved_line);
+
+ rl_set_prompt (self->prompt);
+ rl_replace_line (self->saved_line, false);
+ rl_point = self->saved_point;
+ rl_mark = self->saved_mark;
+ free (self->saved_line);
+ self->saved_line = NULL;
+
+ rl_redisplay ();
+}
+
+static void
+input_rl_set_prompt (struct input *input, char *prompt)
+{
+ struct input_rl *self = (struct input_rl *) input;
+ free (self->prompt);
+ self->prompt = prompt;
+
+ if (!self->active)
+ return;
+
+ // First reset the prompt to work around a bug in readline
+ rl_set_prompt ("");
+ if (self->prompt_shown > 0)
+ rl_redisplay ();
+
+ rl_set_prompt (self->prompt);
+ if (self->prompt_shown > 0)
+ rl_redisplay ();
+}
+
+static void
+input_rl_ding (struct input *input)
+{
+ (void) input;
+ rl_ding ();
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+input_rl_load_history (struct input *input, const char *filename,
+ struct error **e)
+{
+ (void) input;
+
+ if (!(errno = read_history (filename)))
+ return true;
+
+ error_set (e, "%s", strerror (errno));
+ return false;
+}
+
+static bool
+input_rl_save_history (struct input *input, const char *filename,
+ struct error **e)
+{
+ (void) input;
+
+ if (!(errno = write_history (filename)))
+ return true;
+
+ error_set (e, "%s", strerror (errno));
+ return false;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+input_rl_on_terminal_resized (struct input *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
+ (void) input;
+ rl_resize_terminal ();
+}
+
+static void
+input_rl_on_tty_readable (struct input *input)
+{
+ (void) input;
+ rl_callback_read_char ();
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static struct input_vtable input_rl_vtable =
+{
+ .start = input_rl_start,
+ .stop = input_rl_stop,
+ .destroy = input_rl_destroy,
+
+ .hide = input_rl_hide,
+ .show = input_rl_show,
+ .set_prompt = input_rl_set_prompt,
+ .ding = input_rl_ding,
+
+ .load_history = input_rl_load_history,
+ .save_history = input_rl_save_history,
+
+ .on_terminal_resized = input_rl_on_terminal_resized,
+ .on_tty_readable = input_rl_on_tty_readable,
+};
+
+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
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+#ifdef HAVE_EDITLINE
+
+#include
+
+#define INPUT_START_IGNORE '\x01'
+#define INPUT_END_IGNORE '\x01'
+
+struct input_el
+{
+ struct input super; ///< Parent class
+
+ EditLine *editline; ///< The EditLine object
+ HistoryW *history; ///< The history object
+ char *entered_line; ///< Buffers the entered line
+
+ bool active; ///< Interface has been started
+ char *prompt; ///< The prompt we use
+ int prompt_shown; ///< Whether the prompt is shown now
+
+ wchar_t *saved_line; ///< Saved line content
+ int saved_point; ///< Saved cursor position
+ int saved_len; ///< Saved line length
+};
+
+static char *
+input_el_wcstombs (const wchar_t *s)
+{
+ size_t len = wcstombs (NULL, s, 0);
+ if (len++ == (size_t) -1)
+ return NULL;
+
+ char *mb = xmalloc (len);
+ mb[wcstombs (mb, s, len)] = 0;
+ return mb;
+}
+
+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_el_redisplay (struct input_el *self)
+{
+ 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 char *
+input_el_make_prompt (EditLine *editline)
+{
+ struct input_el *self;
+ el_get (editline, EL_CLIENTDATA, &self);
+ return self->prompt ? self->prompt : "";
+}
+
+static char *
+input_el_make_empty_prompt (EditLine *editline)
+{
+ (void) editline;
+ return "";
+}
+
+static void
+input_el_erase (struct input_el *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);
+
+ el_set (self->editline, EL_PROMPT, input_el_make_empty_prompt);
+ input_el_redisplay (self);
+}
+
+static unsigned char
+input_el_on_return (EditLine *editline, int key)
+{
+ (void) key;
+
+ struct input_el *self;
+ el_get (editline, EL_CLIENTDATA, &self);
+
+ 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);
+
+ if (*line)
+ {
+ HistEventW ev;
+ history_w (self->history, &ev, H_ENTER, line);
+ }
+ free (line);
+
+ // Convert to a multibyte string and store it for later
+ const LineInfo *info_mb = el_line (editline);
+ self->entered_line = xstrndup
+ (info_mb->buffer, info_mb->lastchar - info_mb->buffer);
+
+ // Now we need to force editline to actually print the newline
+ el_cursor (editline, len++ - point);
+ el_insertstr (editline, "\n");
+ input_el_redisplay (self);
+
+ // Finally we need to discard the old line's contents
+ el_wdeletestr (editline, len);
+ return CC_NEWLINE;
+}
+
+static void
+input_el_install_prompt (struct input_el *self)
+{
+ // XXX: the ignore doesn't quite work, see https://gnats.netbsd.org/47539
+ el_set (self->editline, EL_PROMPT_ESC,
+ input_el_make_prompt, INPUT_START_IGNORE);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+input_el_start (struct input *input, const char *program_name)
+{
+ struct input_el *self = (struct input_el *) input;
+ self->editline = el_init (program_name, stdin, stdout, stderr);
+ el_set (self->editline, EL_CLIENTDATA, self);
+ input_el_install_prompt (self);
+ el_set (self->editline, EL_SIGNAL, false);
+ el_set (self->editline, EL_UNBUFFERED, true);
+ el_set (self->editline, EL_EDITOR, "emacs");
+ el_wset (self->editline, EL_HIST, history_w, self->history);
+
+ // No, editline, it's not supposed to kill the entire line
+ el_set (self->editline, EL_BIND, "^w", "ed-delete-prev-word", NULL);
+ // Just what are you doing?
+ el_set (self->editline, EL_BIND, "^u", "vi-kill-line-prev", NULL);
+
+ // It's probably better to handle this ourselves
+ el_set (self->editline, EL_ADDFN,
+ "send-line", "Send line", input_el_on_return);
+ el_set (self->editline, EL_BIND, "\n", "send-line", NULL);
+
+ // Source the user's defaults file
+ el_source (self->editline, NULL);
+
+ self->active = true;
+ self->prompt_shown = 1;
+}
+
+static void
+input_el_stop (struct input *input)
+{
+ struct input_el *self = (struct input_el *) input;
+ if (self->prompt_shown > 0)
+ input_el_erase (self);
+
+ el_end (self->editline);
+ self->editline = NULL;
+
+ self->active = false;
+ self->prompt_shown = 0;
+}
+
+static void
+input_el_destroy (struct input *input)
+{
+ struct input_el *self = (struct input_el *) input;
+
+ if (self->active)
+ input_el_stop (input);
+
+ history_wend (self->history);
+
+ free (self->saved_line);
+ free (self->prompt);
+ free (self);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+input_el_hide (struct input *input)
+{
+ struct input_el *self = (struct input_el *) input;
+ if (!self->active || self->prompt_shown-- < 1)
+ return;
+
+ hard_assert (!self->saved_line);
+
+ 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);
+
+ self->saved_line = line;
+ self->saved_point = point;
+ self->saved_len = len;
+
+ input_el_erase (self);
+}
+
+static void
+input_el_show (struct input *input)
+{
+ struct input_el *self = (struct input_el *) input;
+ if (!self->active || ++self->prompt_shown < 1)
+ return;
+
+ hard_assert (self->saved_line);
+
+ el_winsertstr (self->editline, self->saved_line);
+ el_cursor (self->editline,
+ -(self->saved_len - self->saved_point));
+ free (self->saved_line);
+ self->saved_line = NULL;
+
+ input_el_install_prompt (self);
+ input_el_redisplay (self);
+}
+
+static void
+input_el_set_prompt (struct input *input, char *prompt)
+{
+ struct input_el *self = (struct input_el *) input;
+ free (self->prompt);
+ self->prompt = prompt;
+
+ if (self->prompt_shown > 0)
+ input_el_redisplay (self);
+}
+
+static void
+input_el_ding (struct input *input)
+{
+ (void) input;
+
+ const char *ding = bell ? bell : "\a";
+ write (STDOUT_FILENO, ding, strlen (ding));
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static bool
+input_el_load_history (struct input *input, const char *filename,
+ struct error **e)
+{
+ struct input_el *self = (struct input_el *) input;
+
+ HistEventW ev;
+ if (history_w (self->history, &ev, H_LOAD, filename) != -1)
+ return true;
+
+ char *error = input_el_wcstombs (ev.str);
+ error_set (e, "%s", error);
+ free (error);
+ return false;
+}
+
+static bool
+input_el_save_history (struct input *input, const char *filename,
+ struct error **e)
+{
+ struct input_el *self = (struct input_el *) input;
+
+ HistEventW ev;
+ if (history_w (self->history, &ev, H_SAVE, filename) != -1)
+ return true;
+
+ char *error = input_el_wcstombs (ev.str);
+ error_set (e, "%s", error);
+ free (error);
+ return false;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+input_el_on_terminal_resized (struct input *input)
+{
+ struct input_el *self = (struct input_el *) input;
+ el_resize (self->editline);
+}
+
+static void
+input_el_on_tty_readable (struct input *input)
+{
+ // We bind the return key to process it how we need to
+ struct input_el *self = (struct input_el *) input;
+
+ // 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);
+
+ // Process data from our newline handler (async-friendly handling)
+ if (self->entered_line)
+ {
+ // We can't have anything try to hide the old prompt with the appended
+ // newline, it needs to stay where it is and as it is
+ self->prompt_shown = 0;
+
+ self->super.on_input (self->entered_line, self->super.user_data);
+ free (self->entered_line);
+ self->entered_line = NULL;
+
+ // Forbid editline from trying to erase the old prompt (or worse)
+ // and let it redisplay the prompt in its clean state
+ el_set (self->editline, EL_REFRESH);
+ self->prompt_shown = 1;
+ }
+
+ if (count == 1 && buf[0] == ('D' - 0x40) /* hardcoded VEOF in editline */)
+ {
+ el_deletestr (self->editline, 1);
+ input_el_redisplay (self);
+ self->super.on_input (NULL, self->super.user_data);
+ }
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static struct input_vtable input_el_vtable =
+{
+ .start = input_el_start,
+ .stop = input_el_stop,
+ .destroy = input_el_destroy,
+
+ .hide = input_el_hide,
+ .show = input_el_show,
+ .set_prompt = input_el_set_prompt,
+ .ding = input_el_ding,
+
+ .load_history = input_el_load_history,
+ .save_history = input_el_save_history,
+
+ .on_terminal_resized = input_el_on_terminal_resized,
+ .on_tty_readable = input_el_on_tty_readable,
+};
+
+static struct input *
+input_el_new (void)
+{
+ struct input_el *self = xcalloc (1, sizeof *self);
+ self->super.vtable = &input_el_vtable;
+
+ HistEventW ev;
+ self->history = history_winit ();
+ history_w (self->history, &ev, H_SETSIZE, HISTORY_LIMIT);
+ return &self->super;
+}
+
+#define input_new input_el_new
+#endif // HAVE_EDITLINE
+
// --- Main program ------------------------------------------------------------
// HTTP/S and WS/S require significantly different handling. While for HTTP we
@@ -251,14 +919,15 @@ enum color_mode
static struct app_context
{
+ struct input *input; ///< Input interface
+ char *attrs_defaults[ATTR_COUNT]; ///< Default terminal attributes
+ char *attrs[ATTR_COUNT]; ///< Terminal attributes
+
struct backend_iface *backend; ///< Our current backend
struct ws_context ws; ///< WebSockets backend data
struct curl_context curl; ///< cURL backend data
- char *attrs_defaults[ATTR_COUNT]; ///< Default terminal attributes
- char *attrs[ATTR_COUNT]; ///< Terminal attributes
-
struct config config; ///< Program configuration
enum color_mode color_mode; ///< Colour output mode
bool pretty_print; ///< Whether to pretty print
@@ -270,9 +939,6 @@ static struct app_context
iconv_t term_to_utf8; ///< Terminal encoding to UTF-8
iconv_t term_from_utf8; ///< UTF-8 to terminal encoding
-
- char *readline_prompt; ///< The prompt we use for readline
- bool readline_prompt_shown; ///< Whether the prompt is shown now
}
g_ctx;
@@ -468,34 +1134,13 @@ log_message_attributed (void *user_data, const char *quote, const char *fmt,
va_list ap)
{
FILE *stream = stderr;
-
- // GNU readline is a huge piece of total crap; it seems that we must do
- // these incredible shenanigans in order to intersperse readline output
- // with asynchronous status messages
- char *saved_line;
- int saved_point;
-
- if (g_ctx.readline_prompt_shown)
- {
- saved_point = rl_point;
- saved_line = rl_copy_text (0, rl_end);
- rl_set_prompt ("");
- rl_replace_line ("", 0);
- rl_redisplay ();
- }
+ g_ctx.input->vtable->hide (g_ctx.input);
print_attributed (&g_ctx, stream, (intptr_t) user_data, "%s", quote);
vprint_attributed (&g_ctx, stream, (intptr_t) user_data, fmt, ap);
fputs ("\n", stream);
- if (g_ctx.readline_prompt_shown)
- {
- rl_set_prompt (g_ctx.readline_prompt);
- rl_replace_line (saved_line, 0);
- rl_point = saved_point;
- rl_redisplay ();
- free (saved_line);
- }
+ g_ctx.input->vtable->show (g_ctx.input);
}
static void
@@ -1659,6 +2304,18 @@ static struct backend_iface g_backend_curl =
// --- Main program ------------------------------------------------------------
+static void
+quit (struct app_context *ctx)
+{
+ if (ctx->backend->on_quit)
+ ctx->backend->on_quit (ctx);
+
+ ev_break (EV_DEFAULT_ EVBREAK_ALL);
+ ctx->input->vtable->hide (ctx->input);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
#define PARSE_FAIL(...) \
BLOCK_START \
print_error (__VA_ARGS__); \
@@ -1842,8 +2499,15 @@ fail:
}
static void
-process_input (struct app_context *ctx, char *user_input)
+process_input (char *user_input, void *user_data)
{
+ struct app_context *ctx = user_data;
+ if (!user_input)
+ {
+ quit (ctx);
+ return;
+ }
+
char *input;
size_t len;
@@ -1940,68 +2604,28 @@ on_winch (EV_P_ ev_signal *handle, int revents)
(void) handle;
(void) revents;
- // 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
-quit (struct app_context *ctx)
-{
- if (ctx->backend->on_quit)
- ctx->backend->on_quit (ctx);
-
- ev_break (EV_DEFAULT_ EVBREAK_ALL);
+ struct app_context *ctx = ev_userdata (loop);
+ ctx->input->vtable->on_terminal_resized (ctx->input);
}
static void
on_terminated (EV_P_ ev_signal *handle, int revents)
{
- (void) loop;
(void) handle;
(void) revents;
- quit (&g_ctx);
-}
-
-static void
-on_readline_input (char *line)
-{
- // Otherwise the prompt is shown at all times
- // Stupid readline forces us to use a global variable
- g_ctx.readline_prompt_shown = false;
-
- if (!line)
- {
- quit (&g_ctx);
-
- // We must do this here, or the prompt gets printed twice. *shrug*
- rl_callback_handler_remove ();
-
- // Note that we don't set "readline_prompt_shown" back to true.
- // This is so that we can safely do rl_callback_handler_remove when
- // the program is terminated in an unusual manner (other than ^D).
- return;
- }
-
- if (*line)
- add_history (line);
-
- process_input (&g_ctx, line);
- free (line);
-
- g_ctx.readline_prompt_shown = true;
+ struct app_context *ctx = ev_userdata (loop);
+ quit (ctx);
}
static void
on_tty_readable (EV_P_ ev_io *handle, int revents)
{
- (void) loop;
(void) handle;
+ struct app_context *ctx = ev_userdata (loop);
if (revents & EV_READ)
- rl_callback_read_char ();
+ ctx->input->vtable->on_tty_readable (ctx->input);
}
static void
@@ -2158,23 +2782,26 @@ main (int argc, char *argv[])
data_home = xstrdup_printf ("%s/.local/share", home);
}
- using_history ();
- stifle_history (HISTORY_LIMIT);
+ g_ctx.input = input_new ();
+ g_ctx.input->user_data = &g_ctx;
+ g_ctx.input->on_input = process_input;
char *history_path =
xstrdup_printf ("%s/" PROGRAM_NAME "/history", data_home);
- (void) read_history (history_path);
+ (void) g_ctx.input->vtable->load_history (g_ctx.input, history_path, NULL);
if (!get_attribute_printer (stdout))
- g_ctx.readline_prompt = xstrdup_printf ("json-rpc> ");
+ g_ctx.input->vtable->set_prompt (g_ctx.input,
+ xstrdup_printf ("json-rpc> "));
else
{
// XXX: to be completely correct, we should use tputs, but we cannot
- g_ctx.readline_prompt = xstrdup_printf ("%c%s%cjson-rpc> %c%s%c",
- RL_PROMPT_START_IGNORE, g_ctx.attrs[ATTR_PROMPT],
- RL_PROMPT_END_IGNORE,
- RL_PROMPT_START_IGNORE, g_ctx.attrs[ATTR_RESET],
- RL_PROMPT_END_IGNORE);
+ g_ctx.input->vtable->set_prompt (g_ctx.input,
+ xstrdup_printf ("%c%s%cjson-rpc> %c%s%c",
+ INPUT_START_IGNORE, g_ctx.attrs[ATTR_PROMPT],
+ INPUT_END_IGNORE,
+ INPUT_START_IGNORE, g_ctx.attrs[ATTR_RESET],
+ INPUT_END_IGNORE));
}
// So that if the remote end closes the connection, attempts to write to
@@ -2202,35 +2829,33 @@ main (int argc, char *argv[])
ev_io_init (&tty_watcher, on_tty_readable, STDIN_FILENO, EV_READ);
ev_io_start (EV_DEFAULT_ &tty_watcher);
- // readline 6.3 doesn't immediately redraw the terminal upon reception
- // of SIGWINCH, so we must run it in an event loop to remediate that
- rl_catch_sigwinch = false;
- g_ctx.readline_prompt_shown = true;
- rl_callback_handler_install (g_ctx.readline_prompt, on_readline_input);
+ g_ctx.input->vtable->start (g_ctx.input, PROGRAM_NAME);
+ ev_set_userdata (loop, &g_ctx);
ev_run (loop, 0);
- if (g_ctx.readline_prompt_shown)
- rl_callback_handler_remove ();
- putchar ('\n');
-
// User has terminated the program, let's save the history and clean up
+ struct error *e = NULL;
char *dir = xstrdup (history_path);
- (void) mkdir_with_parents (dirname (dir), NULL);
- free (dir);
- if (write_history (history_path))
+ if (!mkdir_with_parents (dirname (dir), &e)
+ || !g_ctx.input->vtable->save_history (g_ctx.input, history_path, &e))
+ {
print_error ("writing the history file `%s' failed: %s",
- history_path, strerror (errno));
+ history_path, e->message);
+ error_free (e);
+ }
+ free (dir);
free (history_path);
+
iconv_close (g_ctx.term_from_utf8);
iconv_close (g_ctx.term_to_utf8);
g_ctx.backend->destroy (&g_ctx);
free (origin);
- free (g_ctx.readline_prompt);
config_free (&g_ctx.config);
free_terminal ();
+ g_ctx.input->vtable->destroy (g_ctx.input);
ev_loop_destroy (loop);
return EXIT_SUCCESS;
}
--
cgit v1.2.3-70-g09d2