diff --git a/bshell.runtime/CMakeLists.txt b/bshell.runtime/CMakeLists.txt index 9540fa0..794e7cd 100644 --- a/bshell.runtime/CMakeLists.txt +++ b/bshell.runtime/CMakeLists.txt @@ -1,6 +1,6 @@ set(source_dirs ast compile parse parse/lex parse/syntax - format runtime command) + format runtime command line-editor) file(GLOB sources *.c diff --git a/bshell.runtime/include/bshell/line-editor/core.h b/bshell.runtime/include/bshell/line-editor/core.h new file mode 100644 index 0000000..dee6c8a --- /dev/null +++ b/bshell.runtime/include/bshell/line-editor/core.h @@ -0,0 +1,21 @@ +#ifndef BSHELL_LINE_EDITOR_CORE_H_ +#define BSHELL_LINE_EDITOR_CORE_H_ + +#include + +FX_DECLS_BEGIN; + +#define BSHELL_TYPE_LINE_EDITOR_CORE (bshell_line_ed_core_get_type()) + +FX_DECLARE_TYPE(bshell_line_ed_core); + +FX_TYPE_CLASS_DECLARATION_BEGIN(bshell_line_ed_core) +FX_TYPE_CLASS_DECLARATION_END(bshell_line_ed_core) + +extern fx_type_id bshell_line_ed_core_get_type(void); + +FX_TYPE_DEFAULT_CONSTRUCTOR(bshell_line_ed_core, BSHELL_TYPE_LINE_EDITOR_CORE); + +FX_DECLS_END; + +#endif diff --git a/bshell.runtime/include/bshell/line-editor/highlight.h b/bshell.runtime/include/bshell/line-editor/highlight.h new file mode 100644 index 0000000..db0d59f --- /dev/null +++ b/bshell.runtime/include/bshell/line-editor/highlight.h @@ -0,0 +1,23 @@ +#ifndef BSHELL_LINE_EDITOR_HIGHLIGHT_H_ +#define BSHELL_LINE_EDITOR_HIGHLIGHT_H_ + +#include + +FX_DECLS_BEGIN; + +#define BSHELL_TYPE_LINE_EDITOR_HIGHLIGHT (bshell_line_ed_highlight_get_type()) + +FX_DECLARE_TYPE(bshell_line_ed_highlight); + +FX_TYPE_CLASS_DECLARATION_BEGIN(bshell_line_ed_highlight) +FX_TYPE_CLASS_DECLARATION_END(bshell_line_ed_highlight) + +extern fx_type_id bshell_line_ed_highlight_get_type(void); + +FX_TYPE_DEFAULT_CONSTRUCTOR( + bshell_line_ed_highlight, + BSHELL_TYPE_LINE_EDITOR_HIGHLIGHT); + +FX_DECLS_END; + +#endif diff --git a/bshell.runtime/include/bshell/line-editor/history.h b/bshell.runtime/include/bshell/line-editor/history.h new file mode 100644 index 0000000..dd3ec61 --- /dev/null +++ b/bshell.runtime/include/bshell/line-editor/history.h @@ -0,0 +1,23 @@ +#ifndef BSHELL_LINE_EDITOR_HISTORY_H_ +#define BSHELL_LINE_EDITOR_HISTORY_H_ + +#include + +FX_DECLS_BEGIN; + +#define BSHELL_TYPE_LINE_EDITOR_HISTORY (bshell_line_ed_history_get_type()) + +FX_DECLARE_TYPE(bshell_line_ed_history); + +FX_TYPE_CLASS_DECLARATION_BEGIN(bshell_line_ed_history) +FX_TYPE_CLASS_DECLARATION_END(bshell_line_ed_history) + +extern fx_type_id bshell_line_ed_history_get_type(void); + +FX_TYPE_DEFAULT_CONSTRUCTOR( + bshell_line_ed_history, + BSHELL_TYPE_LINE_EDITOR_HISTORY); + +FX_DECLS_END; + +#endif diff --git a/bshell.runtime/include/bshell/line-editor/keybind.h b/bshell.runtime/include/bshell/line-editor/keybind.h new file mode 100644 index 0000000..48ebb7e --- /dev/null +++ b/bshell.runtime/include/bshell/line-editor/keybind.h @@ -0,0 +1,44 @@ +#ifndef BSHELL_LINE_EDITOR_KEYBIND_H_ +#define BSHELL_LINE_EDITOR_KEYBIND_H_ + +#include +#include +#include + +FX_DECLS_BEGIN; + +#define BSHELL_LINE_EDITOR_KEYBIND_RESULT(status, action) \ + ((struct bshell_line_ed_keybind_result) { \ + .r_status = (status), \ + .r_action = (action), \ + }) + +typedef FX_TYPE_FWDREF(bshell_line_ed) bshell_line_ed; + +struct bshell_line_ed_keybind_result { + enum bshell_status r_status; + enum { + BSHELL_LINE_EDITOR_KEYBIND_CONTINUE = 0, + BSHELL_LINE_EDITOR_KEYBIND_STOP, + } r_action; +}; + +typedef struct bshell_line_ed_keybind_result ( + *bshell_line_ed_keybind_callback_t)(bshell_line_ed *, fx_keycode); + +#define BSHELL_TYPE_LINE_EDITOR_KEYBIND (bshell_line_ed_keybind_get_type()) + +FX_DECLARE_TYPE(bshell_line_ed_keybind); + +FX_TYPE_CLASS_DECLARATION_BEGIN(bshell_line_ed_keybind) +FX_TYPE_CLASS_DECLARATION_END(bshell_line_ed_keybind) + +extern fx_type_id bshell_line_ed_keybind_get_type(void); + +FX_TYPE_DEFAULT_CONSTRUCTOR( + bshell_line_ed_keybind, + BSHELL_TYPE_LINE_EDITOR_KEYBIND); + +FX_DECLS_END; + +#endif diff --git a/bshell.runtime/include/bshell/line-editor/line-editor.h b/bshell.runtime/include/bshell/line-editor/line-editor.h new file mode 100644 index 0000000..57886ac --- /dev/null +++ b/bshell.runtime/include/bshell/line-editor/line-editor.h @@ -0,0 +1,68 @@ +#ifndef BSHELL_LINE_EDITOR_H_ +#define BSHELL_LINE_EDITOR_H_ + +#include +#include +#include +#include +#include + +FX_DECLS_BEGIN; + +#define BSHELL_TYPE_LINE_EDITOR (bshell_line_ed_get_type()) + +FX_DECLARE_TYPE(bshell_line_ed); + +FX_TYPE_CLASS_DECLARATION_BEGIN(bshell_line_ed) +FX_TYPE_CLASS_DECLARATION_END(bshell_line_ed) + +extern fx_type_id bshell_line_ed_get_type(void); + +FX_TYPE_DEFAULT_CONSTRUCTOR(bshell_line_ed, BSHELL_TYPE_LINE_EDITOR); + +extern enum bshell_status bshell_line_ed_readline( + bshell_line_ed *ed, + fx_string *out); + +extern struct bshell_line_source *bshell_line_ed_get_line_source( + bshell_line_ed *ed); + +extern 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); +extern enum bshell_status bshell_line_ed_unbind_key( + bshell_line_ed *ed, + bshell_line_ed_keybind *bind); + +extern unsigned long bshell_line_ed_get_cursor(const bshell_line_ed *ed); +extern void bshell_line_ed_set_cursor(bshell_line_ed *ed, unsigned long cursor); +extern unsigned long bshell_line_ed_move_cursor( + bshell_line_ed *ed, + long move_amount); +extern enum bshell_status bshell_line_ed_set_done(bshell_line_ed *ed); +extern enum bshell_status bshell_line_ed_set_eof(bshell_line_ed *ed); +extern bool bshell_line_ed_is_done(const bshell_line_ed *ed); +extern bool bshell_line_ed_is_eof(const bshell_line_ed *ed); +extern fx_string *bshell_line_ed_get_prompt(bshell_line_ed *ed); +extern fx_string *bshell_line_ed_get_buffer(bshell_line_ed *ed); +extern fx_tty *bshell_line_ed_get_tty(bshell_line_ed *ed); + +extern fx_value bshell_line_ed_get_data( + const bshell_line_ed *ed, + const char *key); +extern void bshell_line_ed_put_data( + bshell_line_ed *ed, + const char *key, + fx_value value); + +extern void bshell_line_ed_buffer_changed(bshell_line_ed *ed); + +extern enum bshell_status bshell_line_ed_add_plugin( + bshell_line_ed *ed, + bshell_line_ed_plugin *plugin); + +FX_DECLS_END; + +#endif diff --git a/bshell.runtime/include/bshell/line-editor/plugin.h b/bshell.runtime/include/bshell/line-editor/plugin.h new file mode 100644 index 0000000..41c9902 --- /dev/null +++ b/bshell.runtime/include/bshell/line-editor/plugin.h @@ -0,0 +1,42 @@ +#ifndef BSHELL_LINE_EDITOR_PLUGIN_H_ +#define BSHELL_LINE_EDITOR_PLUGIN_H_ + +#include +#include +#include + +FX_DECLS_BEGIN; + +typedef FX_TYPE_FWDREF(bshell_line_ed) bshell_line_ed; + +#define BSHELL_TYPE_LINE_EDITOR_PLUGIN (bshell_line_ed_plugin_get_type()) + +FX_DECLARE_TYPE(bshell_line_ed_plugin); + +FX_TYPE_CLASS_DECLARATION_BEGIN(bshell_line_ed_plugin) + enum bshell_status (*p_load)(bshell_line_ed_plugin *, bshell_line_ed *); + enum bshell_status ( + *p_unload)(bshell_line_ed_plugin *, bshell_line_ed *); + enum bshell_status ( + *p_render_prompt)(bshell_line_ed_plugin *, bshell_line_ed *); + enum bshell_status ( + *p_render_buffer)(bshell_line_ed_plugin *, bshell_line_ed *); + enum bshell_status ( + *p_buffer_changed)(bshell_line_ed_plugin *, bshell_line_ed *); + enum bshell_status (*p_get_data)( + bshell_line_ed_plugin *, + bshell_line_ed *, + const char *, + fx_value *); + enum bshell_status (*p_put_data)( + bshell_line_ed_plugin *, + bshell_line_ed *, + const char *, + const fx_value *); +FX_TYPE_CLASS_DECLARATION_END(bshell_line_ed_plugin) + +extern fx_type_id bshell_line_ed_plugin_get_type(void); + +FX_DECLS_END; + +#endif diff --git a/bshell.runtime/include/bshell/line-editor/shell-integration.h b/bshell.runtime/include/bshell/line-editor/shell-integration.h new file mode 100644 index 0000000..578679a --- /dev/null +++ b/bshell.runtime/include/bshell/line-editor/shell-integration.h @@ -0,0 +1,25 @@ +#ifndef BSHELL_LINE_EDITOR_SHELL_INTEGRATION_H_ +#define BSHELL_LINE_EDITOR_SHELL_INTEGRATION_H_ + +#include + +FX_DECLS_BEGIN; + +struct bshell_runtime; + +#define BSHELL_TYPE_LINE_EDITOR_SHELL_INTEGRATION \ + (bshell_line_ed_shell_integration_get_type()) + +FX_DECLARE_TYPE(bshell_line_ed_shell_integration); + +FX_TYPE_CLASS_DECLARATION_BEGIN(bshell_line_ed_shell_integration) +FX_TYPE_CLASS_DECLARATION_END(bshell_line_ed_shell_integration) + +extern fx_type_id bshell_line_ed_shell_integration_get_type(void); + +extern bshell_line_ed_shell_integration *bshell_line_ed_shell_integration_create( + struct bshell_runtime *rt); + +FX_DECLS_END; + +#endif diff --git a/bshell.runtime/include/bshell/line-editor/syntax.h b/bshell.runtime/include/bshell/line-editor/syntax.h new file mode 100644 index 0000000..d321199 --- /dev/null +++ b/bshell.runtime/include/bshell/line-editor/syntax.h @@ -0,0 +1,23 @@ +#ifndef BSHELL_LINE_EDITOR_SYNTAX_H_ +#define BSHELL_LINE_EDITOR_SYNTAX_H_ + +#include + +FX_DECLS_BEGIN; + +#define BSHELL_TYPE_LINE_EDITOR_SYNTAX (bshell_line_ed_syntax_get_type()) + +FX_DECLARE_TYPE(bshell_line_ed_syntax); + +FX_TYPE_CLASS_DECLARATION_BEGIN(bshell_line_ed_syntax) +FX_TYPE_CLASS_DECLARATION_END(bshell_line_ed_syntax) + +extern fx_type_id bshell_line_ed_syntax_get_type(void); + +FX_TYPE_DEFAULT_CONSTRUCTOR( + bshell_line_ed_syntax, + BSHELL_TYPE_LINE_EDITOR_SYNTAX); + +FX_DECLS_END; + +#endif diff --git a/bshell.runtime/line-editor/core.c b/bshell.runtime/line-editor/core.c new file mode 100644 index 0000000..631fb87 --- /dev/null +++ b/bshell.runtime/line-editor/core.c @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include + +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) diff --git a/bshell.runtime/line-editor/cursor.c b/bshell.runtime/line-editor/cursor.c new file mode 100644 index 0000000..d668a33 --- /dev/null +++ b/bshell.runtime/line-editor/cursor.c @@ -0,0 +1,105 @@ +#include "line-editor.h" + +#include + +static void convert_offset_to_xy( + struct bshell_line_ed_p *ed, + unsigned long offset, + unsigned long *out_x, + unsigned long *out_y) +{ + unsigned long x = 0, y = 0; + unsigned int w = 0, h = 0; + fx_tty_get_dimensions(ed->ed_tty, &w, &h); + if (!w || !h) { + w = 80; + h = 25; + } + + fx_string *prompt = line_ed_get_prompt(ed); + fx_string *buffer = line_ed_get_buffer(ed); + const char *s = fx_string_get_cstr(prompt); + size_t len = fx_string_get_size(prompt, FX_STRLEN_CODEPOINTS); + fx_wchar prev = FX_WCHAR_INVALID; + + for (unsigned long i = 0; i < len; i++) { + fx_wchar c = fx_wchar_utf8_codepoint_decode(s); + if (prev != FX_WCHAR_INVALID) { + if (prev == '\n' || x + 1 >= w) { + x = 0; + y++; + } else { + x += line_ed_get_char_width(c); + } + } + + unsigned int stride = fx_wchar_utf8_codepoint_size(c); + if (stride == 0) { + break; + } + + s += stride; + prev = c; + } + + s = fx_string_get_cstr(buffer); + len = fx_string_get_size(buffer, FX_STRLEN_CODEPOINTS); + for (unsigned long i = 0; i < offset && i < len; i++) { + fx_wchar c = fx_wchar_utf8_codepoint_decode(s); + if (prev != FX_WCHAR_INVALID) { + if (prev == '\n' || x + 1 >= w) { + x = 0; + y++; + } else { + x += line_ed_get_char_width(c); + } + } + + unsigned int stride = fx_wchar_utf8_codepoint_size(c); + if (stride == 0) { + break; + } + + s += stride; + prev = c; + } + + if (offset > len) { + if (x + 1 >= w) { + x = 0; + y++; + } else { + x++; + } + } + + if (out_x) { + *out_x = x; + } + + if (out_y) { + *out_y = y; + } +} + +void line_ed_get_points(struct bshell_line_ed_p *ed, struct cursor_points *out) +{ + unsigned long cursor = line_ed_get_cursor(ed); + fx_string *buffer = line_ed_get_buffer(ed); + + convert_offset_to_xy( + ed, + cursor + 1, + &out->c_cursor_x, + &out->c_cursor_y); + convert_offset_to_xy( + ed, + fx_string_get_size(buffer, FX_STRLEN_CODEPOINTS), + &out->c_render_limit_x, + &out->c_render_limit_y); + convert_offset_to_xy( + ed, + fx_string_get_size(buffer, FX_STRLEN_CODEPOINTS) + 1, + &out->c_cursor_limit_x, + &out->c_cursor_limit_y); +} diff --git a/bshell.runtime/line-editor/display.c b/bshell.runtime/line-editor/display.c new file mode 100644 index 0000000..8571547 --- /dev/null +++ b/bshell.runtime/line-editor/display.c @@ -0,0 +1,98 @@ +#include "line-editor.h" + +void line_ed_hide(struct bshell_line_ed_p *ed) +{ + if (!ed->ed_render.r_vlines) { + return; + } + +#if 0 + fprintf(stderr, "--- hide ---\n"); + fprintf(stderr, + "vlines=%lu, cursor=%lu->(%ld,%ld)\n", + ed->ed_render.r_vlines, + ed->ed_cursor, + ed->ed_render.ed_points.c_cursor_x, + ed->ed_render.ed_points.c_cursor_y); +#endif + + if (ed->ed_render.ed_points.c_cursor_y > 0) { + fx_tty_move_cursor_y( + ed->ed_tty, + FX_TTY_POS_CURSOR, + -ed->ed_render.ed_points.c_cursor_y); + } + fx_tty_move_cursor_x(ed->ed_tty, FX_TTY_POS_START, 0); + fx_tty_clear( + ed->ed_tty, + FX_TTY_CLEAR_SCREEN | FX_TTY_CLEAR_FROM_CURSOR); + memset(&ed->ed_render, 0x0, sizeof ed->ed_render); +} + +static void render_prompt(struct bshell_line_ed_p *ed) +{ + if (ed->ed_render_prompt) { + bshell_line_ed_plugin_class *renderer = fx_object_get_interface( + ed->ed_render_prompt, + BSHELL_TYPE_LINE_EDITOR_PLUGIN); + renderer->p_render_prompt(ed->ed_render_prompt, ed->ed_self); + } +} + +static void render_buffer(struct bshell_line_ed_p *ed) +{ + if (ed->ed_render_buffer) { + bshell_line_ed_plugin_class *renderer = fx_object_get_interface( + ed->ed_render_buffer, + BSHELL_TYPE_LINE_EDITOR_PLUGIN); + renderer->p_render_buffer(ed->ed_render_buffer, ed->ed_self); + } +} + +void line_ed_show(struct bshell_line_ed_p *ed) +{ + render_prompt(ed); + render_buffer(ed); + line_ed_get_points(ed, &ed->ed_render.ed_points); + bool cursor_limit_newline + = ed->ed_render.ed_points.c_cursor_limit_x == 0 + && ed->ed_render.ed_points.c_cursor_limit_y > 0; + if (cursor_limit_newline) { + fx_tty_putc(ed->ed_tty, 0, '\r'); + fx_tty_putc(ed->ed_tty, 0, '\n'); + } + + long shift_x = 0, shift_y = 0; + shift_x = (long)ed->ed_render.ed_points.c_cursor_x + - (long)ed->ed_render.ed_points.c_cursor_limit_x; + shift_y = (long)ed->ed_render.ed_points.c_cursor_y + - (long)ed->ed_render.ed_points.c_cursor_limit_y; + + fx_tty_move_cursor_x(ed->ed_tty, FX_TTY_POS_CURSOR, shift_x); + fx_tty_move_cursor_y(ed->ed_tty, FX_TTY_POS_CURSOR, shift_y); + +#if 0 + fprintf(stderr, "--- show ---\n"); + fprintf(stderr, + "cursor=%lu->(%lu,%lu)\n", + ed->ed_cursor, + ed->ed_render.ed_points.c_cursor_x, + ed->ed_render.ed_points.c_cursor_y); + fprintf(stderr, + "cursor_limit=(%lu,%lu)\n", + ed->ed_render.ed_points.c_cursor_limit_x, + ed->ed_render.ed_points.c_cursor_limit_y); + fprintf(stderr, + "render_limit=(%lu,%lu)\n", + ed->ed_render.ed_points.c_render_limit_x, + ed->ed_render.ed_points.c_render_limit_y); +#endif + + ed->ed_render.r_vlines = ed->ed_render.ed_points.c_render_limit_y + 1; +} + +int line_ed_get_char_width(fx_wchar c) +{ + /* TODO support double-width and zero-width characters */ + return fx_wchar_is_graph(c) ? 1 : 0; +} diff --git a/bshell.runtime/line-editor/highlight.c b/bshell.runtime/line-editor/highlight.c new file mode 100644 index 0000000..135fa28 --- /dev/null +++ b/bshell.runtime/line-editor/highlight.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include + +struct bshell_line_ed_highlight_p { +}; + +static void init(fx_object *obj, void *priv) +{ +} + +static void fini(fx_object *obj, void *priv) +{ +} + +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; + } + + fx_tty_puts(tty, 0, "[reset]"); + return BSHELL_SUCCESS; +} + +FX_TYPE_CLASS_BEGIN(bshell_line_ed_highlight) + 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_render_buffer) = render_buffer; + FX_TYPE_VTABLE_INTERFACE_END(bshell_command, BSHELL_TYPE_COMMAND) +FX_TYPE_CLASS_END(bshell_line_ed_highlight) + +FX_TYPE_DEFINITION_BEGIN(bshell_line_ed_highlight) + FX_TYPE_ID(0x162823ef, 0x4893, 0x4089, 0xa4f6, 0xf015a1976881); + FX_TYPE_NAME("bshell.runtime.line_editor.plugins.highlight"); + FX_TYPE_EXTENDS(BSHELL_TYPE_LINE_EDITOR_PLUGIN); + FX_TYPE_CLASS(bshell_line_ed_highlight_class); + FX_TYPE_INSTANCE_INIT(init); + FX_TYPE_INSTANCE_FINI(fini); + FX_TYPE_INSTANCE_PRIVATE(struct bshell_line_ed_highlight_p); +FX_TYPE_DEFINITION_END(bshell_line_ed_highlight) diff --git a/bshell.runtime/line-editor/history.c b/bshell.runtime/line-editor/history.c new file mode 100644 index 0000000..74ec12e --- /dev/null +++ b/bshell.runtime/line-editor/history.c @@ -0,0 +1,34 @@ +#include +#include + +struct bshell_line_ed_history_p { +}; + +static void init(fx_object *obj, void *priv) +{ +} + +static void fini(fx_object *obj, void *priv) +{ +} + +FX_TYPE_CLASS_BEGIN(bshell_line_ed_history) + 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_TYPE_VTABLE_INTERFACE_END(bshell_command, BSHELL_TYPE_COMMAND) +FX_TYPE_CLASS_END(bshell_line_ed_history) + +FX_TYPE_DEFINITION_BEGIN(bshell_line_ed_history) + FX_TYPE_ID(0xa08c98d5, 0x9512, 0x4a65, 0xaf57, 0x0daa8a943e52); + FX_TYPE_NAME("bshell.runtime.line_editor.plugins.history"); + FX_TYPE_EXTENDS(BSHELL_TYPE_LINE_EDITOR_PLUGIN); + FX_TYPE_CLASS(bshell_line_ed_history_class); + FX_TYPE_INSTANCE_INIT(init); + FX_TYPE_INSTANCE_FINI(fini); + FX_TYPE_INSTANCE_PRIVATE(struct bshell_line_ed_history_p); +FX_TYPE_DEFINITION_END(bshell_line_ed_history) diff --git a/bshell.runtime/line-editor/keybind.c b/bshell.runtime/line-editor/keybind.c new file mode 100644 index 0000000..d080c9e --- /dev/null +++ b/bshell.runtime/line-editor/keybind.c @@ -0,0 +1,223 @@ +#include "keybind.h" + +#include "line-editor.h" + +#include + +#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) diff --git a/bshell.runtime/line-editor/keybind.h b/bshell.runtime/line-editor/keybind.h new file mode 100644 index 0000000..ba8d393 --- /dev/null +++ b/bshell.runtime/line-editor/keybind.h @@ -0,0 +1,22 @@ +#ifndef _KEYBIND_H_ +#define _KEYBIND_H_ + +#include +#include +#include + +struct keybind { + fx_bst_node b_node; + unsigned long b_priority; + bshell_line_ed_keybind_callback_t b_callback; +}; + +struct keymap_entry { + fx_bst_node e_node; + fx_keycode e_code; + fx_bst e_binds; +}; + +extern void line_ed_cleanup_keymap(fx_bst *keymap); + +#endif diff --git a/bshell.runtime/line-editor/line-editor.c b/bshell.runtime/line-editor/line-editor.c new file mode 100644 index 0000000..4ea3a93 --- /dev/null +++ b/bshell.runtime/line-editor/line-editor.c @@ -0,0 +1,507 @@ +#include "line-editor.h" + +#include "keybind.h" + +/*** PRIVATE FUNCTIONS ********************************************************/ + +static void line_ed_set_cursor( + struct bshell_line_ed_p *ed, + unsigned long cursor); +static fx_value line_ed_get_data( + const struct bshell_line_ed_p *ed, + const char *key); +static enum bshell_status line_ed_put_data( + struct bshell_line_ed_p *ed, + const char *key, + fx_value value); + +static void clear_cache(struct bshell_line_ed_p *ed) +{ + fx_string_unref(ed->ed_cache.c_prompt); + fx_string_unref(ed->ed_cache.c_buffer); + memset(&ed->ed_cache, 0x0, sizeof ed->ed_cache); +} + +static enum bshell_status line_ed_readline( + struct bshell_line_ed_p *ed, + fx_string *out) +{ + ed->ed_flags &= ~(LINE_ED_DONE | LINE_ED_EOF); + fx_tty_set_mode(ed->ed_tty, FX_TTY_RAW); + + clear_cache(ed); + fx_string *prompt = line_ed_get_prompt(ed); + fx_string *buffer = line_ed_get_buffer(ed); + + fx_string_clear(buffer); + line_ed_set_cursor(ed, 0); + +#define SHOULD_CONTINUE(ed) (!(ed->ed_flags & (LINE_ED_DONE | LINE_ED_EOF))) + while (SHOULD_CONTINUE(ed)) { + line_ed_hide(ed); + line_ed_show(ed); + fx_tty_flush(ed->ed_tty); + fx_keycode key = fx_tty_read_key(ed->ed_tty); + + line_ed_handle_key(ed, key); + } +#undef SHOULD_CONTINUE + + long shift_y = (long)ed->ed_render.ed_points.c_cursor_limit_y + - (long)ed->ed_render.ed_points.c_cursor_y + 1; +#if 0 + fprintf(stderr, + "cursor_y=%lu, cursor_limit_y=%lu, shift_y=%ld\n", + ed->ed_render.ed_points.c_cursor_limit_y, + ed->ed_render.ed_points.c_cursor_y, + shift_y); +#endif + + fx_tty_putc(ed->ed_tty, 0, '\r'); + for (long i = 0; i < shift_y; i++) { + fx_tty_putc(ed->ed_tty, 0, '\n'); + } + + fx_tty_flush(ed->ed_tty); + + fx_tty_set_mode(ed->ed_tty, FX_TTY_CANONICAL); + buffer = line_ed_get_buffer(ed); + + if (out) { + fx_string_append_s(out, buffer); + } + + return BSHELL_SUCCESS; +} + +static struct bshell_line_source *line_ed_get_line_source( + struct bshell_line_ed_p *ed) +{ + return &ed->ed_linesrc; +} + +unsigned long line_ed_get_cursor(const struct bshell_line_ed_p *ed) +{ + enum bshell_status status = BSHELL_SUCCESS; + fx_value v = line_ed_get_data(ed, "cursor"); + if (!fx_value_is_set(&v)) { + status = line_ed_put_data( + (struct bshell_line_ed_p *)ed, + "cursor", + FX_ULONG(0)); + return 0; + } + + unsigned long cursor = 0; + fx_value_get_ulong(&v, &cursor); + fx_value_unset(&v); + + return cursor; +} + +static void line_ed_set_cursor( + struct bshell_line_ed_p *ed, + unsigned long cursor) +{ + line_ed_put_data(ed, "cursor", FX_ULONG(cursor)); +} + +static unsigned long line_ed_move_cursor( + struct bshell_line_ed_p *ed, + long move_amount) +{ + unsigned long cursor = line_ed_get_cursor(ed); + fx_string *buffer = line_ed_get_buffer(ed); + + if (move_amount < 0) { + if (labs(move_amount) > cursor) { + cursor = 0; + } else { + cursor += move_amount; + } + } else { + unsigned long max + = fx_string_get_size(buffer, FX_STRLEN_CODEPOINTS); + if (cursor + move_amount > max) { + cursor = max; + } else { + cursor += move_amount; + } + } + + line_ed_put_data(ed, "cursor", FX_ULONG(cursor)); + return cursor; +} + +static enum bshell_status line_ed_set_done(struct bshell_line_ed_p *ed) +{ + ed->ed_flags |= LINE_ED_DONE; + return BSHELL_SUCCESS; +} + +static enum bshell_status line_ed_set_eof(struct bshell_line_ed_p *ed) +{ + ed->ed_flags |= LINE_ED_EOF; + return BSHELL_SUCCESS; +} + +static bool line_ed_is_done(const struct bshell_line_ed_p *ed) +{ + return (ed->ed_flags & LINE_ED_DONE) == LINE_ED_DONE; +} + +static bool line_ed_is_eof(const struct bshell_line_ed_p *ed) +{ + return (ed->ed_flags & LINE_ED_EOF) == LINE_ED_EOF; +} + +static fx_value line_ed_get_data( + const struct bshell_line_ed_p *ed, + const char *key) +{ + if (!ed->ed_data) { + return FX_VALUE_EMPTY; + } + + bshell_line_ed_plugin_class *ops = fx_object_get_interface( + ed->ed_data, + BSHELL_TYPE_LINE_EDITOR_PLUGIN); + if (!ops || !ops->p_get_data) { + return FX_VALUE_EMPTY; + } + + fx_value result = FX_VALUE_EMPTY; + enum bshell_status status + = ops->p_get_data(ed->ed_data, ed->ed_self, key, &result); + if (status != BSHELL_SUCCESS) { + return FX_VALUE_EMPTY; + } + + return result; +} + +static enum bshell_status line_ed_put_data( + struct bshell_line_ed_p *ed, + const char *key, + fx_value value) +{ + if (!ed->ed_data) { + return BSHELL_ERR_NOT_SUPPORTED; + } + + bshell_line_ed_plugin_class *ops = fx_object_get_interface( + ed->ed_data, + BSHELL_TYPE_LINE_EDITOR_PLUGIN); + if (!ops || !ops->p_put_data) { + return BSHELL_ERR_NOT_SUPPORTED; + } + + return ops->p_put_data(ed->ed_data, ed->ed_self, key, &value); +} + +fx_string *line_ed_get_prompt(struct bshell_line_ed_p *ed) +{ + if (ed->ed_cache.c_prompt) { + return ed->ed_cache.c_prompt; + } + + enum bshell_status status = BSHELL_SUCCESS; + fx_value v = line_ed_get_data(ed, "prompt"); + if (!fx_value_is_set(&v)) { + fx_string *prompt = fx_string_create(); + status = line_ed_put_data( + ed, + "prompt", + FX_VALUE_OBJECT(prompt)); + if (status != BSHELL_SUCCESS) { + fx_string_unref(prompt); + return NULL; + } + + ed->ed_cache.c_prompt = prompt; + return prompt; + } + + fx_value_get_object(&v, &ed->ed_cache.c_prompt); + return ed->ed_cache.c_prompt; +} + +fx_string *line_ed_get_buffer(struct bshell_line_ed_p *ed) +{ + if (ed->ed_cache.c_buffer) { + return ed->ed_cache.c_buffer; + } + + enum bshell_status status = BSHELL_SUCCESS; + fx_value v = line_ed_get_data(ed, "buffer"); + if (!fx_value_is_set(&v)) { + fx_string *buffer = fx_string_create(); + status = line_ed_put_data( + ed, + "buffer", + FX_VALUE_OBJECT(buffer)); + if (status != BSHELL_SUCCESS) { + fx_string_unref(buffer); + return NULL; + } + + ed->ed_cache.c_buffer = buffer; + return buffer; + } + + fx_value_get_object(&v, &ed->ed_cache.c_buffer); + return ed->ed_cache.c_buffer; +} + +static fx_tty *line_ed_get_tty(struct bshell_line_ed_p *ed) +{ + return ed->ed_tty; +} + +static enum bshell_status line_ed_add_plugin( + struct bshell_line_ed_p *ed, + bshell_line_ed_plugin *plugin) +{ + bshell_line_ed_plugin_class *ops = fx_object_get_interface( + plugin, + BSHELL_TYPE_LINE_EDITOR_PLUGIN); + if (!ops) { + return BSHELL_ERR_INVALID_ARGUMENT; + } + + if (ops->p_load) { + ops->p_load(plugin, ed->ed_self); + } + + fx_array_push_back(ed->ed_plugins, FX_VALUE_OBJECT(plugin)); + + if (ops->p_render_buffer) { + ed->ed_render_buffer = plugin; + } + + if (ops->p_render_prompt) { + ed->ed_render_prompt = plugin; + } + + if (ops->p_get_data && ops->p_put_data) { + ed->ed_data = plugin; + } + + return BSHELL_SUCCESS; +} + +static void line_ed_buffer_changed(struct bshell_line_ed_p *ed) +{ + const fx_iterator *it = fx_iterator_begin(ed->ed_plugins); + fx_foreach(v, it) + { + bshell_line_ed_plugin *plugin = NULL; + fx_value_get_object(v, &plugin); + bshell_line_ed_plugin_class *ops = fx_object_get_interface( + plugin, + BSHELL_TYPE_LINE_EDITOR_PLUGIN); + + if (ops && ops->p_buffer_changed) { + ops->p_buffer_changed(plugin, ed->ed_self); + } + } + fx_iterator_unref(it); +} + +static enum bshell_status readline( + struct bshell_line_source *src, + fx_stringstream *out) +{ + struct bshell_line_ed_p *ed + = fx_unbox(struct bshell_line_ed_p, src, ed_linesrc); + + long r = line_ed_readline(ed, NULL); + + if (line_ed_is_eof(ed)) { + return BSHELL_ERR_EOF; + } + + fx_string *buf = line_ed_get_buffer(ed); + if (buf) { + fx_stream_write_cstr(out, fx_string_get_cstr(buf), NULL); + } + + return BSHELL_SUCCESS; +} + +/*** PUBLIC FUNCTIONS + * *********************************************************/ + +enum bshell_status bshell_line_ed_readline(bshell_line_ed *ed, fx_string *out) +{ + FX_CLASS_DISPATCH_STATIC( + BSHELL_TYPE_LINE_EDITOR, + line_ed_readline, + ed, + out); +} + +struct bshell_line_source *bshell_line_ed_get_line_source(bshell_line_ed *ed) +{ + FX_CLASS_DISPATCH_STATIC_0( + BSHELL_TYPE_LINE_EDITOR, + line_ed_get_line_source, + ed); +} + +unsigned long bshell_line_ed_get_cursor(const bshell_line_ed *ed) +{ + FX_CLASS_DISPATCH_STATIC_0( + BSHELL_TYPE_LINE_EDITOR, + line_ed_get_cursor, + ed); +} + +void bshell_line_ed_set_cursor(bshell_line_ed *ed, unsigned long cursor) +{ + FX_CLASS_DISPATCH_STATIC( + BSHELL_TYPE_LINE_EDITOR, + line_ed_set_cursor, + ed, + cursor); +} + +unsigned long bshell_line_ed_move_cursor(bshell_line_ed *ed, long move_amount) +{ + FX_CLASS_DISPATCH_STATIC( + BSHELL_TYPE_LINE_EDITOR, + line_ed_move_cursor, + ed, + move_amount); +} + +enum bshell_status bshell_line_ed_set_done(bshell_line_ed *ed) +{ + FX_CLASS_DISPATCH_STATIC_0( + BSHELL_TYPE_LINE_EDITOR, + line_ed_set_done, + ed); +} + +enum bshell_status bshell_line_ed_set_eof(bshell_line_ed *ed) +{ + FX_CLASS_DISPATCH_STATIC_0( + BSHELL_TYPE_LINE_EDITOR, + line_ed_set_eof, + ed); +} + +bool bshell_line_ed_is_done(const bshell_line_ed *ed) +{ + FX_CLASS_DISPATCH_STATIC_0( + BSHELL_TYPE_LINE_EDITOR, + line_ed_is_done, + ed); +} + +bool bshell_line_ed_is_eof(const bshell_line_ed *ed) +{ + FX_CLASS_DISPATCH_STATIC_0(BSHELL_TYPE_LINE_EDITOR, line_ed_is_eof, ed); +} + +fx_string *bshell_line_ed_get_prompt(bshell_line_ed *ed) +{ + FX_CLASS_DISPATCH_STATIC_0( + BSHELL_TYPE_LINE_EDITOR, + line_ed_get_prompt, + ed); +} + +fx_string *bshell_line_ed_get_buffer(bshell_line_ed *ed) +{ + FX_CLASS_DISPATCH_STATIC_0( + BSHELL_TYPE_LINE_EDITOR, + line_ed_get_buffer, + ed); +} + +fx_tty *bshell_line_ed_get_tty(bshell_line_ed *ed) +{ + FX_CLASS_DISPATCH_STATIC_0( + BSHELL_TYPE_LINE_EDITOR, + line_ed_get_tty, + ed); +} + +fx_value bshell_line_ed_get_data(const bshell_line_ed *ed, const char *key) +{ + FX_CLASS_DISPATCH_STATIC( + BSHELL_TYPE_LINE_EDITOR, + line_ed_get_data, + ed, + key); +} + +void bshell_line_ed_put_data( + bshell_line_ed *ed, + const char *key, + fx_value value) +{ + FX_CLASS_DISPATCH_STATIC_V( + BSHELL_TYPE_LINE_EDITOR, + line_ed_put_data, + ed, + key, + value); +} + +enum bshell_status bshell_line_ed_add_plugin( + bshell_line_ed *ed, + bshell_line_ed_plugin *plugin) +{ + FX_CLASS_DISPATCH_STATIC( + BSHELL_TYPE_LINE_EDITOR, + line_ed_add_plugin, + ed, + plugin); +} + +void bshell_line_ed_buffer_changed(bshell_line_ed *ed) +{ + FX_CLASS_DISPATCH_STATIC_V0( + BSHELL_TYPE_LINE_EDITOR, + line_ed_buffer_changed, + ed); +} + +/*** VIRTUAL FUNCTIONS + * ********************************************************/ + +static void init(fx_object *obj, void *priv) +{ + struct bshell_line_ed_p *ed = priv; + + ed->ed_self = obj; + ed->ed_tty = fx_stdtty; + ed->ed_plugins = fx_array_create(); + ed->ed_linesrc.s_readline = readline; +} + +static void fini(fx_object *obj, void *priv) +{ + struct bshell_line_ed_p *ed = priv; + + clear_cache(ed); + line_ed_cleanup_keymap(&ed->ed_keymap); + fx_array_unref(ed->ed_plugins); +} + +FX_TYPE_CLASS_BEGIN(bshell_line_ed) + 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) + +FX_TYPE_DEFINITION_BEGIN(bshell_line_ed) + FX_TYPE_ID(0xc59de843, 0xea6a, 0x46d9, 0xb7fd, 0xa3777a40c4fa); + FX_TYPE_NAME("bshell.line_editor"); + FX_TYPE_CLASS(bshell_line_ed_class); + FX_TYPE_INSTANCE_PRIVATE(struct bshell_line_ed_p); + FX_TYPE_INSTANCE_INIT(init); + FX_TYPE_INSTANCE_FINI(fini); +FX_TYPE_DEFINITION_END(bshell_line_ed) diff --git a/bshell.runtime/line-editor/line-editor.h b/bshell.runtime/line-editor/line-editor.h new file mode 100644 index 0000000..60b857d --- /dev/null +++ b/bshell.runtime/line-editor/line-editor.h @@ -0,0 +1,76 @@ +#ifndef _LINE_EDITOR_H_ +#define _LINE_EDITOR_H_ + +#include +#include +#include +#include +#include + +enum line_ed_flags { + LINE_ED_DONE = 0x01u, + LINE_ED_EOF = 0x02u, +}; + +struct cursor_points { + /* visual co-ordinates of the current cursor position */ + unsigned long c_cursor_x, c_cursor_y; + /* visual co-ordinates of the last character in the rendered prompt + * string */ + unsigned long c_render_limit_x, c_render_limit_y; + /* visual co-ordinates of the last cell that could be occupied by the + * cursor in the rendered prompt string */ + unsigned long c_cursor_limit_x, c_cursor_limit_y; +}; + +struct line_render { + /* how many visual terminal rows are being used to display the current + * buffer. may be greater than the number of lines in ed_buffer if one + * or more of the lines exceeds the width of the terminal. will be + * less than or equal to the terminal height. */ + unsigned long r_vlines; + /* the logical offset that the cursor was at when the prompt was + * rendered */ + unsigned long r_cursor; + /* points of interest */ + struct cursor_points ed_points; +}; + +struct data_cache { + fx_string *c_prompt; + fx_string *c_buffer; +}; + +struct bshell_line_ed_p { + bshell_line_ed *ed_self; + enum line_ed_flags ed_flags; + struct bshell_line_source ed_linesrc; + fx_bst ed_keymap; + fx_array *ed_plugins; + fx_tty *ed_tty; + + struct data_cache ed_cache; + + bshell_line_ed_plugin *ed_render_prompt; + bshell_line_ed_plugin *ed_render_buffer; + bshell_line_ed_plugin *ed_data; + + struct line_render ed_render; +}; + +extern void line_ed_handle_key(struct bshell_line_ed_p *ed, fx_keycode code); + +extern void line_ed_get_points( + struct bshell_line_ed_p *ed, + struct cursor_points *out); + +extern void line_ed_hide(struct bshell_line_ed_p *ed); +extern void line_ed_show(struct bshell_line_ed_p *ed); + +extern fx_string *line_ed_get_prompt(struct bshell_line_ed_p *ed); +extern fx_string *line_ed_get_buffer(struct bshell_line_ed_p *ed); +extern unsigned long line_ed_get_cursor(const struct bshell_line_ed_p *ed); + +extern int line_ed_get_char_width(fx_wchar c); + +#endif diff --git a/bshell.runtime/line-editor/plugin.c b/bshell.runtime/line-editor/plugin.c new file mode 100644 index 0000000..8af2adc --- /dev/null +++ b/bshell.runtime/line-editor/plugin.c @@ -0,0 +1,27 @@ +#include + +struct bshell_line_ed_plugin_p { +}; + +static void init(fx_object *obj, void *priv) +{ +} + +static void fini(fx_object *obj, void *priv) +{ +} + +FX_TYPE_CLASS_BEGIN(bshell_line_ed_plugin) + 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_plugin) + +FX_TYPE_DEFINITION_BEGIN(bshell_line_ed_plugin) + FX_TYPE_ID(0x6311ff4e, 0x5eb7, 0x48f0, 0x982b, 0xf8ffbda74869); + FX_TYPE_NAME("bshell.runtime.line_editor.plugin"); + FX_TYPE_CLASS(bshell_line_ed_plugin_class); + FX_TYPE_INSTANCE_INIT(init); + FX_TYPE_INSTANCE_FINI(fini); + FX_TYPE_INSTANCE_PRIVATE(struct bshell_line_ed_plugin_p); +FX_TYPE_DEFINITION_END(bshell_line_ed_plugin) diff --git a/bshell.runtime/line-editor/shell-integration.c b/bshell.runtime/line-editor/shell-integration.c new file mode 100644 index 0000000..ee858e0 --- /dev/null +++ b/bshell.runtime/line-editor/shell-integration.c @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_PROMPT "Bsh> " + +struct bshell_line_ed_shell_integration_p { + struct bshell_runtime *si_rt; + fx_hashtable *si_data; + fx_string *si_default_prompt; + bshell_pipeline *si_pipeline; + bshell_cmdcall *si_prompt; +}; + +static void init(fx_object *obj, void *priv) +{ + struct bshell_line_ed_shell_integration_p *p = priv; + p->si_default_prompt = fx_string_create_from_cstr(DEFAULT_PROMPT); + p->si_pipeline = bshell_pipeline_create(); + p->si_prompt = bshell_cmdcall_create(); + bshell_cmdcall_push_arg(p->si_prompt, FX_CSTR("prompt")); + bshell_pipeline_add_cmdcall(p->si_pipeline, p->si_prompt); +} + +static void fini(fx_object *obj, void *priv) +{ + struct bshell_line_ed_shell_integration_p *p = priv; + bshell_pipeline_unref(p->si_pipeline); + bshell_cmdcall_unref(p->si_prompt); + fx_string_unref(p->si_default_prompt); +} + +bshell_line_ed_shell_integration *bshell_line_ed_shell_integration_create( + struct bshell_runtime *rt) +{ + bshell_line_ed_shell_integration *out + = fx_object_create(BSHELL_TYPE_LINE_EDITOR_SHELL_INTEGRATION); + if (!out) { + return NULL; + } + + struct bshell_line_ed_shell_integration_p *p = fx_object_get_private( + out, + BSHELL_TYPE_LINE_EDITOR_SHELL_INTEGRATION); + p->si_rt = rt; + + return out; +} + +static enum bshell_status load( + bshell_line_ed_plugin *plugin, + bshell_line_ed *ed) +{ + struct bshell_line_ed_shell_integration_p *shell + = fx_object_get_private( + plugin, + BSHELL_TYPE_LINE_EDITOR_SHELL_INTEGRATION); + shell->si_data = fx_hashtable_create(); + struct bshell_runtime_scope *global + = bshell_runtime_get_global_scope(shell->si_rt); + + bshell_variable *item + = bshell_runtime_scope_define_variable(global, "line_editor"); + bshell_variable_set_value(item, FX_VALUE_OBJECT(shell->si_data)); + printf("shell integration loaded\n"); + return BSHELL_SUCCESS; +} + +static enum bshell_status get_prompt( + struct bshell_line_ed_shell_integration_p *shell, + bshell_line_ed *ed, + fx_value *out) +{ + enum bshell_status status + = bshell_cmdcall_resolve(shell->si_prompt, shell->si_rt); + if (status != BSHELL_SUCCESS) { + *out = fx_value_copy_return( + FX_VALUE_OBJECT(shell->si_default_prompt)); + return BSHELL_SUCCESS; + } + + int eof = 0; + status = bshell_pipeline_pump_record( + shell->si_pipeline, + shell->si_rt, + out, + &eof); + return status; +} + +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_shell_integration_p *shell + = fx_object_get_private( + plugin, + BSHELL_TYPE_LINE_EDITOR_SHELL_INTEGRATION); + if (!strcmp(key, "prompt")) { + return get_prompt(shell, ed, out); + } + + const fx_value *v = fx_hashtable_get(shell->si_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) +{ + if (!strcmp(key, "prompt")) { + return BSHELL_SUCCESS; + } + + struct bshell_line_ed_shell_integration_p *shell + = fx_object_get_private( + plugin, + BSHELL_TYPE_LINE_EDITOR_SHELL_INTEGRATION); + fx_hashtable_put(shell->si_data, &FX_CSTR(key), (fx_value *)value); + return BSHELL_SUCCESS; +} + +FX_TYPE_CLASS_BEGIN(bshell_line_ed_shell_integration) + 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_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_shell_integration) + +FX_TYPE_DEFINITION_BEGIN(bshell_line_ed_shell_integration) + FX_TYPE_ID(0x8e89c8d0, 0xbc82, 0x4153, 0x9607, 0xa7a710486596); + FX_TYPE_NAME("bshell.runtime.line_editor.plugins.shell_integration"); + FX_TYPE_EXTENDS(BSHELL_TYPE_LINE_EDITOR_PLUGIN); + FX_TYPE_CLASS(bshell_line_ed_shell_integration_class); + FX_TYPE_INSTANCE_INIT(init); + FX_TYPE_INSTANCE_FINI(fini); + FX_TYPE_INSTANCE_PRIVATE(struct bshell_line_ed_shell_integration_p); +FX_TYPE_DEFINITION_END(bshell_line_ed_shell_integration) diff --git a/bshell.runtime/line-editor/syntax.c b/bshell.runtime/line-editor/syntax.c new file mode 100644 index 0000000..d70d157 --- /dev/null +++ b/bshell.runtime/line-editor/syntax.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +struct bshell_line_ed_syntax_p { +}; + +static void init(fx_object *obj, void *priv) +{ +} + +static void fini(fx_object *obj, void *priv) +{ +} + +static enum bshell_status buffer_changed( + bshell_line_ed_plugin *plugin, + bshell_line_ed *ed) +{ + // fprintf(stderr, "buffer changed!\n"); + return BSHELL_SUCCESS; +} + +FX_TYPE_CLASS_BEGIN(bshell_line_ed_syntax) + 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_buffer_changed) = buffer_changed; + FX_TYPE_VTABLE_INTERFACE_END(bshell_command, BSHELL_TYPE_COMMAND) +FX_TYPE_CLASS_END(bshell_line_ed_syntax) + +FX_TYPE_DEFINITION_BEGIN(bshell_line_ed_syntax) + FX_TYPE_ID(0x4d9dea6c, 0x2ed8, 0x40e0, 0x943d, 0xfd70a7a9f54a); + FX_TYPE_NAME("bshell.runtime.line_editor.plugins.syntax"); + FX_TYPE_EXTENDS(BSHELL_TYPE_LINE_EDITOR_PLUGIN); + FX_TYPE_CLASS(bshell_line_ed_syntax_class); + FX_TYPE_INSTANCE_INIT(init); + FX_TYPE_INSTANCE_FINI(fini); + FX_TYPE_INSTANCE_PRIVATE(struct bshell_line_ed_syntax_p); +FX_TYPE_DEFINITION_END(bshell_line_ed_syntax)