arif/src/arify_rl.c

370 lines
9.5 KiB
C

/**
* arif/src/arify_rl.c - Readline frontend for the ARIF preload library
* ----
*
* Copyright (C) 2023 CismonX <admin@cismon.net>
*
* This file is part of ARIF, Another Readline Input Framework.
*
* ARIF is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARIF is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ARIF. If not, see <https://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "arify_rl.h"
#ifdef HAVE_READLINE
#include <assert.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <readline/readline.h>
#include "arif_defs.h"
#include "arif_rl.h"
struct arify_rl_engine {
struct arif_engine const *engine;
void *engine_data;
struct arify_rl_engine *next;
};
// Forward declaration start
static char ** complete (char const *, int, int);
static void disable_arify (bool);
static void display_hook (char **, int, int);
static void enable_arify (void);
static void get_engine_info (struct arify_rl_engine const *,
char const **, char const **);
static void finalize (void *);
static struct arify_rl_engine *
get_next_engine (void);
static int initialize (struct arif_ctx *, void **);
static int rlfunc_engine_info (int, int);
static int rlfunc_next_engine (int, int);
static int rlfunc_page_down (int, int);
static int rlfunc_page_up (int, int);
static int rlfunc_toggle (int, int);
static void rlprintf (char const *, ...);
// Forward declaration end
struct arify_funmap_entry {
char const *name;
rl_command_func_t *func;
};
struct arify_rl_ctx {
struct arif_ctx *ctx;
struct arify_rl_engine *engines;
struct arify_rl_engine *current_engine;
bool enabled;
char const *old_basic_quote_chars;
rl_completion_func_t *old_comp_func;
rl_compdisp_func_t *old_disp_func;
int old_ignore_duplicates;
int old_query_items;
int old_sort_matches;
};
struct arify_frontend const arify_frontend_readline = {
.init = initialize,
.finalize = finalize,
};
static struct arify_funmap_entry const funmap_entries[] = {
{ "arify-toggle", rlfunc_toggle },
{ "arify-next-engine", rlfunc_next_engine },
{ "arify-page-up", rlfunc_page_up },
{ "arify-page-down", rlfunc_page_down },
{ "arify-engine-info", rlfunc_engine_info },
};
static struct arify_rl_ctx rlctx;
static char **
complete (
char const *text,
int start,
int end
) {
arify_debug_printf("%s", "complete");
return arif_rl_complete(rlctx.ctx, text, start, end);
}
static void
disable_arify (
bool suppress_message
) {
rl_basic_quote_characters = rlctx.old_basic_quote_chars;
rl_attempted_completion_function = rlctx.old_comp_func;
rl_completion_display_matches_hook = rlctx.old_disp_func;
rl_completion_query_items = rlctx.old_query_items;
rl_ignore_completion_duplicates = rlctx.old_ignore_duplicates;
rl_sort_completion_matches = rlctx.old_sort_matches;
rlctx.enabled = false;
if (!suppress_message) {
rlprintf("%s", "[arify] disabled");
}
arify_debug_printf("%s", "disabled");
}
static void
display_hook (
char **matches,
int num,
int max_len
) {
arify_debug_printf("%s", "display hook");
arif_rl_display(rlctx.ctx, matches, num, max_len);
}
static void
enable_arify (void)
{
if (rlctx.current_engine == NULL) {
struct arify_rl_engine *entry = get_next_engine();
if (entry == NULL) {
arify_err_printf("%s", "no available engines");
return;
}
rlctx.current_engine = rlctx.engines = entry;
arif_set_engine(rlctx.ctx, entry->engine, entry->engine_data);
}
rlctx.old_basic_quote_chars = rl_basic_quote_characters;
rlctx.old_comp_func = rl_attempted_completion_function;
rlctx.old_disp_func = rl_completion_display_matches_hook;
rlctx.old_query_items = rl_completion_query_items;
rlctx.old_ignore_duplicates = rl_ignore_completion_duplicates;
rlctx.old_sort_matches = rl_sort_completion_matches;
// This is necessary, as rl_complete_internal() appends these characters
// even with rl_completion_suppress_quote set to non-zero
rl_basic_quote_characters = "";
rl_attempted_completion_function = complete;
rl_completion_display_matches_hook = display_hook;
rl_completion_query_items = 0;
rl_ignore_completion_duplicates = 0;
rl_sort_completion_matches = 0;
char const *engine_name;
get_engine_info(rlctx.current_engine, &engine_name, NULL);
rlprintf("[arify] enabled (engine: %s)", engine_name);
rlctx.enabled = true;
arify_debug_printf("%s", "enabled");
}
static void
get_engine_info (
struct arify_rl_engine const *entry,
char const **name_ptr,
char const **description_ptr
) {
char const *name, *description;
if (name_ptr == NULL) {
name_ptr = &name;
}
if (description_ptr == NULL) {
description_ptr = &description;
}
struct arif_engine const *engine = entry->engine;
if (engine->info != NULL) {
engine->info(entry->engine_data, name_ptr, description_ptr);
} else {
*name_ptr = "";
*description_ptr = "";
}
}
static void
finalize (
void *ARIF_UNUSED_ARG(frontend_data)
) {
if (rlctx.enabled) {
disable_arify(true);
}
struct arify_rl_engine *next, *entry;
for (entry = rlctx.engines; entry != NULL; entry = next) {
next = entry->next;
free(entry);
if (next == rlctx.engines) {
break;
}
}
arify_debug_printf("%s", "readline frontend finalized");
}
static struct arify_rl_engine *
get_next_engine (void)
{
void *engine_data;
struct arif_engine const *engine = arify_next_engine(&engine_data);
if (engine == NULL) {
return NULL;
}
struct arify_rl_engine *entry = malloc(sizeof(struct arify_rl_engine));
assert(entry != NULL);
*entry = (struct arify_rl_engine) {
.engine = engine,
.engine_data = engine_data,
};
return entry;
}
static int
initialize (
struct arif_ctx *ctx,
void **frontend_data_ptr
) {
// We must not call rl_initialize() here
int entries = sizeof(funmap_entries) / sizeof(funmap_entries[0]);
for (int idx = 0; idx < entries; ++idx) {
struct arify_funmap_entry const *entry = funmap_entries + idx;
rl_add_funmap_entry(entry->name, entry->func);
}
rlctx.ctx = ctx;
*frontend_data_ptr = &rlctx;
arify_debug_printf("%s", "readline frontend initialized");
return 0;
}
static int
rlfunc_engine_info (
int ARIF_UNUSED_ARG(arg),
int ARIF_UNUSED_ARG(key)
) {
if (!rlctx.enabled) {
return 0;
}
char const *name, *description;
get_engine_info(rlctx.current_engine, &name, &description);
rlprintf("[arify] engine: %s\n\n%s", name, description);
arify_debug_printf("%s", "print engine info");
return 0;
}
static int
rlfunc_next_engine (
int ARIF_UNUSED_ARG(arg),
int ARIF_UNUSED_ARG(key)
) {
if (!rlctx.enabled) {
return 0;
}
struct arify_rl_engine *entry = rlctx.current_engine;
if (entry->next == NULL) {
entry->next = get_next_engine();
if (entry->next == NULL) {
entry->next = rlctx.engines;
}
}
rlctx.current_engine = entry = entry->next;
arif_set_engine(rlctx.ctx, entry->engine, entry->engine_data);
char const *engine_name;
get_engine_info(entry, &engine_name, NULL);
rlprintf("[arify] engine: %s", engine_name);
arify_debug_printf("next engine: %s", engine_name);
return 0;
}
static int
rlfunc_page_down (
int ARIF_UNUSED_ARG(arg),
int ARIF_UNUSED_ARG(key)
) {
if (!rlctx.enabled) {
return 0;
}
if (rl_inhibit_completion) {
return 0;
}
int page = arif_select_page(rlctx.ctx, 0);
if (page > 0) {
rl_complete_internal('?');
}
arify_debug_printf("page down: %d", page);
return 0;
}
static int
rlfunc_page_up (
int ARIF_UNUSED_ARG(arg),
int ARIF_UNUSED_ARG(key)
) {
if (!rlctx.enabled) {
return 0;
}
if (rl_inhibit_completion) {
return 0;
}
int page = arif_select_page(rlctx.ctx, -1);
if (page > 0) {
rl_complete_internal('?');
}
arify_debug_printf("page up: %d", page);
return 0;
}
static int
rlfunc_toggle (
int ARIF_UNUSED_ARG(arg),
int ARIF_UNUSED_ARG(key)
) {
if (rlctx.enabled) {
disable_arify(false);
} else {
enable_arify();
}
return 0;
}
static void
rlprintf (
char const *fmt,
...
) {
rl_crlf();
va_list args;
va_start(args, fmt);
vfprintf(rl_outstream, fmt, args);
va_end(args);
rl_crlf();
rl_forced_update_display();
}
#endif // defined(HAVE_READLINE)