Browse Source

degesch: add support for libedit

Just another kind of evil.
tags/v0.9
Přemysl Janouch 4 years ago
parent
commit
a5a1079a9c
4 changed files with 476 additions and 71 deletions
  1. +23
    -1
      CMakeLists.txt
  2. +3
    -2
      README
  3. +3
    -0
      config.h.in
  4. +447
    -68
      degesch.c

+ 23
- 1
CMakeLists.txt View File

@@ -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})

+ 3
- 2
README View File

@@ -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:

+ 3
- 0
config.h.in View File

@@ -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

+ 447
- 68
degesch.c View File

@@ -69,32 +69,55 @@ enum
#undef lines
#undef columns

#ifdef HAVE_READLINE
#include <readline/readline.h>
#include <readline/history.h>
#endif // HAVE_READLINE

#ifdef HAVE_EDITLINE
#include <histedit.h>
#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);

Loading…
Cancel
Save