Files
bshell/bshell.runtime/line-editor/core.c
T
wash ad8b284869 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.
2026-06-20 15:13:34 +01:00

230 lines
6.1 KiB
C

#include <bshell/line-editor/core.h>
#include <bshell/line-editor/line-editor.h>
#include <bshell/line-editor/plugin.h>
#include <fx/collections/hashtable.h>
#include <fx/term/tty.h>
struct bshell_line_ed_core_p {
bshell_line_ed_keybind *c_key_cursor_left;
bshell_line_ed_keybind *c_key_cursor_right;
bshell_line_ed_keybind *c_key_return;
bshell_line_ed_keybind *c_key_eof;
bshell_line_ed_keybind *c_key_generic;
bshell_line_ed_keybind *c_key_backspace;
fx_hashtable *c_data;
};
static void init(fx_object *obj, void *priv)
{
struct bshell_line_ed_core_p *core = priv;
core->c_data = fx_hashtable_create();
}
static void fini(fx_object *obj, void *priv)
{
struct bshell_line_ed_core_p *core = priv;
fx_hashtable_unref(core->c_data);
}
static struct bshell_line_ed_keybind_result delete_backward(
bshell_line_ed *ed,
fx_keycode key)
{
return BSHELL_LINE_EDITOR_KEYBIND_RESULT(
BSHELL_SUCCESS,
BSHELL_LINE_EDITOR_KEYBIND_CONTINUE);
}
static struct bshell_line_ed_keybind_result cursor_left(
bshell_line_ed *ed,
fx_keycode key)
{
bshell_line_ed_move_cursor(ed, -1);
return BSHELL_LINE_EDITOR_KEYBIND_RESULT(
BSHELL_SUCCESS,
BSHELL_LINE_EDITOR_KEYBIND_CONTINUE);
}
static struct bshell_line_ed_keybind_result cursor_right(
bshell_line_ed *ed,
fx_keycode key)
{
bshell_line_ed_move_cursor(ed, 1);
return BSHELL_LINE_EDITOR_KEYBIND_RESULT(
BSHELL_SUCCESS,
BSHELL_LINE_EDITOR_KEYBIND_CONTINUE);
}
static struct bshell_line_ed_keybind_result eof_key(
bshell_line_ed *ed,
fx_keycode key)
{
bshell_line_ed_set_eof(ed);
return BSHELL_LINE_EDITOR_KEYBIND_RESULT(
BSHELL_SUCCESS,
BSHELL_LINE_EDITOR_KEYBIND_CONTINUE);
}
static struct bshell_line_ed_keybind_result return_key(
bshell_line_ed *ed,
fx_keycode key)
{
bshell_line_ed_set_done(ed);
fx_string *buf = bshell_line_ed_get_buffer(ed);
fx_string_append_c(buf, '\n');
return BSHELL_LINE_EDITOR_KEYBIND_RESULT(
BSHELL_SUCCESS,
BSHELL_LINE_EDITOR_KEYBIND_CONTINUE);
}
static struct bshell_line_ed_keybind_result backspace(
bshell_line_ed *ed,
fx_keycode key)
{
fx_string *buf = bshell_line_ed_get_buffer(ed);
unsigned long cursor = bshell_line_ed_get_cursor(ed);
if (cursor > 0) {
fx_string_remove(buf, cursor - 1, 1);
bshell_line_ed_move_cursor(ed, -1);
bshell_line_ed_buffer_changed(ed);
}
return BSHELL_LINE_EDITOR_KEYBIND_RESULT(
BSHELL_SUCCESS,
BSHELL_LINE_EDITOR_KEYBIND_CONTINUE);
}
static struct bshell_line_ed_keybind_result generic_key(
bshell_line_ed *ed,
fx_keycode key)
{
fx_wchar c = key;
bool valid = fx_wchar_utf8_is_valid_scalar(c) && fx_wchar_is_graph(c);
if (valid) {
fx_string *buf = bshell_line_ed_get_buffer(ed);
unsigned long cursor = bshell_line_ed_get_cursor(ed);
char cstr[5] = {0};
fx_string_insert_wc(buf, c, cursor);
bshell_line_ed_move_cursor(ed, 1);
bshell_line_ed_buffer_changed(ed);
}
return BSHELL_LINE_EDITOR_KEYBIND_RESULT(
BSHELL_SUCCESS,
BSHELL_LINE_EDITOR_KEYBIND_CONTINUE);
}
static enum bshell_status load(
bshell_line_ed_plugin *plugin,
bshell_line_ed *ed)
{
struct bshell_line_ed_core_p *core
= fx_object_get_private(plugin, BSHELL_TYPE_LINE_EDITOR_CORE);
core->c_key_eof = bshell_line_ed_bind_key(ed, "^d", eof_key, 100);
core->c_key_cursor_left
= bshell_line_ed_bind_key(ed, "left", cursor_left, 100);
core->c_key_cursor_right
= bshell_line_ed_bind_key(ed, "right", cursor_right, 100);
core->c_key_cursor_right
= bshell_line_ed_bind_key(ed, "return", return_key, 100);
core->c_key_generic
= bshell_line_ed_bind_key(ed, "*", generic_key, 100);
core->c_key_backspace
= bshell_line_ed_bind_key(ed, "backspace", backspace, 100);
return BSHELL_SUCCESS;
}
static enum bshell_status render_prompt(
bshell_line_ed_plugin *plugin,
bshell_line_ed *ed)
{
fx_string *prompt = bshell_line_ed_get_prompt(ed);
fx_tty *tty = bshell_line_ed_get_tty(ed);
const char *s = fx_string_get_cstr(prompt);
fx_tty_puts(tty, 0, s);
return BSHELL_SUCCESS;
}
static enum bshell_status render_buffer(
bshell_line_ed_plugin *plugin,
bshell_line_ed *ed)
{
fx_string *buf = bshell_line_ed_get_buffer(ed);
fx_tty *tty = bshell_line_ed_get_tty(ed);
const char *s = fx_string_get_cstr(buf);
for (unsigned long i = 0; *s; i++) {
fx_wchar c = fx_wchar_utf8_codepoint_decode(s);
unsigned int stride = fx_wchar_utf8_codepoint_size(c);
if (stride == 0) {
break;
}
char cs[5] = {0};
fx_wchar_utf8_codepoint_encode(c, cs);
fx_tty_puts(tty, 0, cs);
s += stride;
}
return BSHELL_SUCCESS;
}
static enum bshell_status get_data(
bshell_line_ed_plugin *plugin,
bshell_line_ed *ed,
const char *key,
fx_value *out)
{
struct bshell_line_ed_core_p *core
= fx_object_get_private(plugin, BSHELL_TYPE_LINE_EDITOR_CORE);
const fx_value *v = fx_hashtable_get(core->c_data, &FX_CSTR(key));
if (v) {
fx_value_copy(out, v);
} else {
*out = FX_VALUE_EMPTY;
}
return BSHELL_SUCCESS;
}
static enum bshell_status put_data(
bshell_line_ed_plugin *plugin,
bshell_line_ed *ed,
const char *key,
const fx_value *value)
{
struct bshell_line_ed_core_p *core
= fx_object_get_private(plugin, BSHELL_TYPE_LINE_EDITOR_CORE);
fx_hashtable_put(core->c_data, &FX_CSTR(key), (fx_value *)value);
return BSHELL_SUCCESS;
}
FX_TYPE_CLASS_BEGIN(bshell_line_ed_core)
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_VTABLE_INTERFACE_BEGIN(
bshell_line_ed_plugin,
BSHELL_TYPE_LINE_EDITOR_PLUGIN)
FX_INTERFACE_ENTRY(p_load) = load;
FX_INTERFACE_ENTRY(p_render_prompt) = render_prompt;
FX_INTERFACE_ENTRY(p_render_buffer) = render_buffer;
FX_INTERFACE_ENTRY(p_get_data) = get_data;
FX_INTERFACE_ENTRY(p_put_data) = put_data;
FX_TYPE_VTABLE_INTERFACE_END(bshell_command, BSHELL_TYPE_COMMAND)
FX_TYPE_CLASS_END(bshell_line_ed_core)
FX_TYPE_DEFINITION_BEGIN(bshell_line_ed_core)
FX_TYPE_ID(0xf5174a63, 0x8fe4, 0x4720, 0xa861, 0x4ebb0d170dd3);
FX_TYPE_NAME("bshell.runtime.line_editor.plugins.core");
FX_TYPE_EXTENDS(BSHELL_TYPE_LINE_EDITOR_PLUGIN);
FX_TYPE_CLASS(bshell_line_ed_core_class);
FX_TYPE_INSTANCE_INIT(init);
FX_TYPE_INSTANCE_FINI(fini);
FX_TYPE_INSTANCE_PRIVATE(struct bshell_line_ed_core_p);
FX_TYPE_DEFINITION_END(bshell_line_ed_core)