#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)