runtime: implement an extensible line editor
the line editor supports custom keybinds and data storage, allowing for
a wide range of functionality to be implemented via plugins.
included plugins include:
* core: basic input and cursor movement.
* history: input history storage and retrieval (unfinished)
* highlight: support for colour-highlighting the line buffer (unfinished)
* syntax: parses the line buffer and uses `highlight` to apply syntax highlighting (unfinished).
* shell-integration:
- allows a `prompt` function to be defined, which will be called to get the text to
be used for the prompt
- exposes key properties of the line editor to the shell environment, allowing line editor
functionality to be extended via shell scripts.
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
#include "keybind.h"
|
||||
|
||||
#include "line-editor.h"
|
||||
|
||||
#include <fx/term/tty.h>
|
||||
|
||||
#define KEYCODE_WILDCARD FX_KEY_RESERVED_START
|
||||
|
||||
static FX_BST_DEFINE_SIMPLE_INSERT(
|
||||
struct keymap_entry,
|
||||
e_node,
|
||||
e_code,
|
||||
put_keymap_entry);
|
||||
static FX_BST_DEFINE_SIMPLE_GET(
|
||||
struct keymap_entry,
|
||||
fx_keycode,
|
||||
e_node,
|
||||
e_code,
|
||||
get_keymap_entry);
|
||||
static FX_BST_DEFINE_SIMPLE_INSERT(
|
||||
struct keybind,
|
||||
b_node,
|
||||
b_priority,
|
||||
put_keybind);
|
||||
|
||||
struct bshell_line_ed_keybind_p {
|
||||
bshell_line_ed_keybind *kb_self;
|
||||
struct keybind kb_bind;
|
||||
};
|
||||
|
||||
static void keymap_entry_destroy(struct keymap_entry *entry)
|
||||
{
|
||||
fx_bst_node *cur_bind = fx_bst_first(&entry->e_binds);
|
||||
while (cur_bind) {
|
||||
fx_bst_node *next_bind = fx_bst_next(cur_bind);
|
||||
fx_bst_delete(&entry->e_binds, cur_bind);
|
||||
|
||||
struct keybind *keybind
|
||||
= fx_unbox(struct keybind, cur_bind, b_node);
|
||||
struct bshell_line_ed_keybind_p *keybind_object_p = fx_unbox(
|
||||
struct bshell_line_ed_keybind_p,
|
||||
keybind,
|
||||
kb_bind);
|
||||
bshell_line_ed_keybind_unref(keybind_object_p->kb_self);
|
||||
|
||||
cur_bind = next_bind;
|
||||
}
|
||||
|
||||
free(entry);
|
||||
}
|
||||
|
||||
void line_ed_cleanup_keymap(fx_bst *keymap)
|
||||
{
|
||||
fx_bst_node *cur_entry = fx_bst_first(keymap);
|
||||
while (cur_entry) {
|
||||
fx_bst_node *next_entry = fx_bst_next(cur_entry);
|
||||
fx_bst_delete(keymap, cur_entry);
|
||||
|
||||
struct keymap_entry *keymap_entry
|
||||
= fx_unbox(struct keymap_entry, cur_entry, e_node);
|
||||
|
||||
keymap_entry_destroy(keymap_entry);
|
||||
|
||||
cur_entry = next_entry;
|
||||
}
|
||||
}
|
||||
|
||||
static fx_keycode key_string_to_code(const char *key)
|
||||
{
|
||||
const char *tmp = key;
|
||||
fx_keycode result = 0;
|
||||
|
||||
if (*key == '^') {
|
||||
result |= FX_MOD_CTRL;
|
||||
key++;
|
||||
}
|
||||
|
||||
if (!strcmp(key, "*")) {
|
||||
result |= KEYCODE_WILDCARD;
|
||||
} else if (!strcmp(key, "left")) {
|
||||
result |= FX_KEY_ARROW_LEFT;
|
||||
} else if (!strcmp(key, "right")) {
|
||||
result |= FX_KEY_ARROW_RIGHT;
|
||||
} else if (!strcmp(key, "up")) {
|
||||
result |= FX_KEY_ARROW_UP;
|
||||
} else if (!strcmp(key, "down")) {
|
||||
result |= FX_KEY_ARROW_DOWN;
|
||||
} else if (!strcmp(key, "backspace")) {
|
||||
result |= FX_KEY_BACKSPACE;
|
||||
} else if (!strcmp(key, "return")) {
|
||||
result |= FX_KEY_RETURN;
|
||||
} else {
|
||||
result |= fx_wchar_utf8_codepoint_decode(key);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bshell_line_ed_keybind *line_ed_bind_key(
|
||||
struct bshell_line_ed_p *ed,
|
||||
const char *key,
|
||||
bshell_line_ed_keybind_callback_t callback,
|
||||
unsigned long priority)
|
||||
{
|
||||
fx_keycode keycode = key_string_to_code(key);
|
||||
if (!keycode) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bshell_line_ed_keybind *bind = NULL;
|
||||
bind = bshell_line_ed_keybind_create();
|
||||
|
||||
if (!bind) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct bshell_line_ed_keybind_p *bind_p
|
||||
= fx_object_get_private(bind, BSHELL_TYPE_LINE_EDITOR_KEYBIND);
|
||||
|
||||
struct keymap_entry *keymap_entry
|
||||
= get_keymap_entry(&ed->ed_keymap, keycode);
|
||||
if (!keymap_entry) {
|
||||
keymap_entry = calloc(1, sizeof *keymap_entry);
|
||||
if (!keymap_entry) {
|
||||
bshell_line_ed_keybind_unref(bind);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
keymap_entry->e_code = keycode;
|
||||
put_keymap_entry(&ed->ed_keymap, keymap_entry);
|
||||
}
|
||||
|
||||
bind_p->kb_self = bind;
|
||||
bind_p->kb_bind.b_priority = priority;
|
||||
bind_p->kb_bind.b_callback = callback;
|
||||
put_keybind(&keymap_entry->e_binds, &bind_p->kb_bind);
|
||||
|
||||
return bind;
|
||||
}
|
||||
|
||||
bshell_line_ed_keybind *bshell_line_ed_bind_key(
|
||||
bshell_line_ed *ed,
|
||||
const char *key,
|
||||
bshell_line_ed_keybind_callback_t callback,
|
||||
unsigned long priority)
|
||||
{
|
||||
FX_CLASS_DISPATCH_STATIC(
|
||||
BSHELL_TYPE_LINE_EDITOR,
|
||||
line_ed_bind_key,
|
||||
ed,
|
||||
key,
|
||||
callback,
|
||||
priority);
|
||||
}
|
||||
|
||||
static struct bshell_line_ed_keybind_result do_handle_key(
|
||||
struct bshell_line_ed_p *ed,
|
||||
fx_keycode match_code,
|
||||
fx_keycode real_code)
|
||||
{
|
||||
struct bshell_line_ed_keybind_result result
|
||||
= BSHELL_LINE_EDITOR_KEYBIND_RESULT(
|
||||
BSHELL_SUCCESS,
|
||||
BSHELL_LINE_EDITOR_KEYBIND_CONTINUE);
|
||||
|
||||
struct keymap_entry *keymap_entry
|
||||
= get_keymap_entry(&ed->ed_keymap, match_code);
|
||||
if (!keymap_entry) {
|
||||
return result;
|
||||
}
|
||||
|
||||
fx_bst_node *cur = fx_bst_last(&keymap_entry->e_binds);
|
||||
while (cur) {
|
||||
struct keybind *bind = fx_unbox(struct keybind, cur, b_node);
|
||||
result = bind->b_callback(ed->ed_self, real_code);
|
||||
|
||||
if (result.r_status != BSHELL_SUCCESS
|
||||
|| result.r_action != BSHELL_LINE_EDITOR_KEYBIND_CONTINUE) {
|
||||
break;
|
||||
}
|
||||
|
||||
cur = fx_bst_prev(cur);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void line_ed_handle_key(struct bshell_line_ed_p *ed, fx_keycode code)
|
||||
{
|
||||
struct bshell_line_ed_keybind_result result
|
||||
= do_handle_key(ed, code, code);
|
||||
if (result.r_status != BSHELL_SUCCESS
|
||||
|| result.r_action != BSHELL_LINE_EDITOR_KEYBIND_CONTINUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (code & FX_MOD_CTRL) {
|
||||
result = do_handle_key(
|
||||
ed,
|
||||
FX_MOD_CTRL | KEYCODE_WILDCARD,
|
||||
code);
|
||||
} else {
|
||||
result = do_handle_key(ed, KEYCODE_WILDCARD, code);
|
||||
}
|
||||
}
|
||||
|
||||
static void fini(fx_object *obj, void *priv)
|
||||
{
|
||||
}
|
||||
|
||||
FX_TYPE_CLASS_BEGIN(bshell_line_ed_keybind)
|
||||
FX_TYPE_VTABLE_INTERFACE_BEGIN(fx_object, FX_TYPE_OBJECT)
|
||||
FX_INTERFACE_ENTRY(to_string) = NULL;
|
||||
FX_TYPE_VTABLE_INTERFACE_END(fx_object, FX_TYPE_OBJECT)
|
||||
FX_TYPE_CLASS_END(bshell_line_ed_keybind)
|
||||
|
||||
FX_TYPE_DEFINITION_BEGIN(bshell_line_ed_keybind)
|
||||
FX_TYPE_ID(0x5d842371, 0x40a3, 0x4683, 0xaff2, 0x05daa53d36b7);
|
||||
FX_TYPE_NAME("bshell.line_editor.keybind");
|
||||
FX_TYPE_CLASS(bshell_line_ed_keybind_class);
|
||||
FX_TYPE_INSTANCE_PRIVATE(struct bshell_line_ed_keybind_p);
|
||||
FX_TYPE_INSTANCE_FINI(fini);
|
||||
FX_TYPE_DEFINITION_END(bshell_line_ed_keybind)
|
||||
Reference in New Issue
Block a user