diff options
| author | Albert Cervin <albert@acervin.com> | 2023-11-26 23:08:06 +0100 |
|---|---|---|
| committer | Albert Cervin <albert@acervin.com> | 2024-01-15 10:39:56 +0100 |
| commit | 64d6816a36567274551dd4f067fe4d05b1445cc0 (patch) | |
| tree | 50f8dc895d363ab391d30226f665870d8ce263b5 | |
| parent | c87888f10dfb54590c5aae8311b7aff887193d9a (diff) | |
| download | dged-64d6816a36567274551dd4f067fe4d05b1445cc0.tar.gz dged-64d6816a36567274551dd4f067fe4d05b1445cc0.tar.xz dged-64d6816a36567274551dd4f067fe4d05b1445cc0.zip | |
Completion rework
- Add support for building with clang
Also fix some annoying bugs:
- Visual column was wrong when using tabs
- Add shift-tab for inserting an actual tab
- Fix minibuffer sometimes having dot above it
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | flake.nix | 1 | ||||
| -rw-r--r-- | src/dged/buffer.c | 209 | ||||
| -rw-r--r-- | src/dged/buffer.h | 91 | ||||
| -rw-r--r-- | src/dged/buffer_view.c | 35 | ||||
| -rw-r--r-- | src/dged/buffer_view.h | 1 | ||||
| -rw-r--r-- | src/dged/display.c | 88 | ||||
| -rw-r--r-- | src/dged/keyboard.c | 2 | ||||
| -rw-r--r-- | src/dged/minibuffer.c | 49 | ||||
| -rw-r--r-- | src/dged/minibuffer.h | 4 | ||||
| -rw-r--r-- | src/dged/reactor-epoll.c | 2 | ||||
| -rw-r--r-- | src/dged/settings-parse.c | 3 | ||||
| -rw-r--r-- | src/dged/settings.c | 16 | ||||
| -rw-r--r-- | src/dged/text.c | 2 | ||||
| -rw-r--r-- | src/dged/utf8.c | 2 | ||||
| -rw-r--r-- | src/dged/vec.h | 17 | ||||
| -rw-r--r-- | src/dged/window.c | 1 | ||||
| -rw-r--r-- | src/main/bindings.c | 129 | ||||
| -rw-r--r-- | src/main/bindings.h | 14 | ||||
| -rw-r--r-- | src/main/cmds.c | 366 | ||||
| -rw-r--r-- | src/main/completion.c | 541 | ||||
| -rw-r--r-- | src/main/completion.h | 83 | ||||
| -rw-r--r-- | src/main/main.c | 40 | ||||
| -rw-r--r-- | src/main/search-replace.c | 23 | ||||
| -rw-r--r-- | test/minibuffer.c | 2 |
25 files changed, 1140 insertions, 585 deletions
@@ -12,7 +12,7 @@ HEADERS = src/dged/settings.h src/dged/minibuffer.h src/dged/keyboard.h src/dged src/dged/buffer.h src/dged/btree.h src/dged/command.h src/dged/allocator.h src/dged/reactor.h \ src/dged/vec.h src/dged/window.h src/dged/hash.h src/dged/undo.h src/dged/lang.h \ src/dged/settings-parse.h src/dged/utf8.h src/main/cmds.h src/main/bindings.h \ - src/main/search-replace.h src/dged/location.h src/dged/buffer_view.h + src/main/search-replace.h src/dged/location.h src/dged/buffer_view.h src/main/completion.h SOURCES = src/dged/binding.c src/dged/buffer.c src/dged/command.c src/dged/display.c \ src/dged/keyboard.c src/dged/minibuffer.c src/dged/text.c \ @@ -20,7 +20,7 @@ SOURCES = src/dged/binding.c src/dged/buffer.c src/dged/command.c src/dged/displ src/dged/settings.c src/dged/lang.c src/dged/settings-parse.c src/dged/location.c \ src/dged/buffer_view.c -MAIN_SOURCES = src/main/main.c src/main/cmds.c src/main/bindings.c src/main/search-replace.c +MAIN_SOURCES = src/main/main.c src/main/cmds.c src/main/bindings.c src/main/search-replace.c src/main/completion.c TEST_SOURCES = test/assert.c test/buffer.c test/text.c test/utf8.c test/main.c \ test/command.c test/keyboard.c test/fake-reactor.c test/allocator.c \ @@ -11,6 +11,7 @@ in { packages.default = pkgs.callPackage ./dged.nix { }; + packages.clang = pkgs.callPackage ./dged.nix { stdenv = pkgs.clangStdenv; }; } ); } diff --git a/src/dged/buffer.c b/src/dged/buffer.c index c95da91..f1ce2ce 100644 --- a/src/dged/buffer.c +++ b/src/dged/buffer.c @@ -21,11 +21,6 @@ #include <unistd.h> #include <wchar.h> -struct modeline { - uint8_t *buffer; - uint32_t sz; -}; - #define KILL_RING_SZ 64 static struct kill_ring { struct text_chunk buffer[KILL_RING_SZ]; @@ -39,23 +34,78 @@ static struct kill_ring { .paste_idx = 0, .paste_up_to_date = false}; -#define MAX_CREATE_HOOKS 32 -static struct create_hook { - create_hook_cb callback; - void *userdata; -} g_create_hooks[MAX_CREATE_HOOKS]; -static uint32_t g_num_create_hooks = 0; +#define DECLARE_HOOK(name, callback_type, vec_type) \ + struct name##_hook { \ + uint32_t id; \ + callback_type callback; \ + void *userdata; \ + }; \ + \ + static uint32_t insert_##name##_hook( \ + vec_type *hooks, uint32_t *id, callback_type callback, void *userdata) { \ + uint32_t iid = ++(*id); \ + struct name##_hook hook = (struct name##_hook){ \ + .id = iid, \ + .callback = callback, \ + .userdata = userdata, \ + }; \ + VEC_PUSH(hooks, hook); \ + \ + return iid; \ + } \ + \ + static void remove_##name##_hook(vec_type *hooks, uint32_t id, \ + remove_hook_cb callback) { \ + uint64_t found_at = -1; \ + VEC_FOR_EACH_INDEXED(hooks, struct name##_hook *h, idx) { \ + if (h->id == id) { \ + callback(h->userdata); \ + found_at = idx; \ + break; \ + } \ + } \ + if (found_at != -1) { \ + if (found_at < VEC_SIZE(hooks) - 1) { \ + VEC_SWAP(hooks, found_at, VEC_SIZE(hooks) - 1); \ + } \ + VEC_POP(hooks, struct name##_hook removed); \ + } \ + } + +typedef VEC(struct create_hook) create_hook_vec; +typedef VEC(struct insert_hook) insert_hook_vec; +typedef VEC(struct update_hook) update_hook_vec; +typedef VEC(struct delete_hook) delete_hook_vec; + +DECLARE_HOOK(create, create_hook_cb, create_hook_vec); +DECLARE_HOOK(insert, insert_hook_cb, insert_hook_vec); +DECLARE_HOOK(update, update_hook_cb, update_hook_vec); +DECLARE_HOOK(delete, delete_hook_cb, delete_hook_vec); + +static create_hook_vec g_create_hooks; +uint32_t g_create_hook_id; + +struct hooks { + create_hook_vec create_hooks; + uint32_t create_hook_id; + + insert_hook_vec insert_hooks; + uint32_t insert_hook_id; + + update_hook_vec update_hooks; + uint32_t update_hook_id; + + delete_hook_vec delete_hooks; + uint32_t delete_hook_id; +}; -uint32_t buffer_add_create_hook(create_hook_cb hook, void *userdata) { - if (g_num_create_hooks < MAX_CREATE_HOOKS) { - g_create_hooks[g_num_create_hooks] = (struct create_hook){ - .callback = hook, - .userdata = userdata, - }; - ++g_num_create_hooks; - } +uint32_t buffer_add_create_hook(create_hook_cb callback, void *userdata) { + return insert_create_hook(&g_create_hooks, &g_create_hook_id, callback, + userdata); +} - return g_num_create_hooks - 1; +void buffer_remove_create_hook(uint32_t hook_id, remove_hook_cb callback) { + remove_create_hook(&g_create_hooks, hook_id, callback); } void buffer_static_init() { @@ -88,7 +138,10 @@ static struct buffer create_internal(const char *name, char *filename) { .last_write = {0}, }; - VEC_INIT(&b.update_hooks, 32); + b.hooks = calloc(1, sizeof(struct hooks)); + VEC_INIT(&b.hooks->insert_hooks, 8); + VEC_INIT(&b.hooks->update_hooks, 8); + VEC_INIT(&b.hooks->delete_hooks, 8); undo_init(&b.undo, 100); @@ -136,31 +189,9 @@ static bool moveh(struct buffer *buffer, int64_t coldelta, return true; } -static void delete_with_undo(struct buffer *buffer, struct location start, - struct location end) { - if (buffer->readonly) { - minibuffer_echo_timeout(4, "buffer is read-only"); - return; - } - - struct text_chunk txt = - text_get_region(buffer->text, start.line, start.col, end.line, end.col); - - undo_push_delete( - &buffer->undo, - (struct undo_delete){.data = txt.text, - .nbytes = txt.nbytes, - .pos = {.row = start.line, .col = start.col}}); - undo_push_boundary(&buffer->undo, - (struct undo_boundary){.save_point = false}); - - text_delete(buffer->text, start.line, start.col, end.line, end.col); - buffer->modified = true; -} - static void maybe_delete_region(struct buffer *buffer, struct region region) { if (region_has_size(region)) { - delete_with_undo(buffer, region.begin, region.end); + buffer_delete(buffer, region); } } @@ -254,8 +285,8 @@ struct buffer buffer_create(const char *name) { struct buffer b = create_internal(name, NULL); - for (uint32_t hooki = 0; hooki < g_num_create_hooks; ++hooki) { - g_create_hooks[hooki].callback(&b, g_create_hooks[hooki].userdata); + VEC_FOR_EACH(&g_create_hooks, struct create_hook * h) { + h->callback(&b, h->userdata); } return b; @@ -266,8 +297,8 @@ struct buffer buffer_from_file(const char *path) { struct buffer b = create_internal(basename((char *)path), full_path); buffer_read_from_file(&b); - for (uint32_t hooki = 0; hooki < g_num_create_hooks; ++hooki) { - g_create_hooks[hooki].callback(&b, g_create_hooks[hooki].userdata); + VEC_FOR_EACH(&g_create_hooks, struct create_hook * h) { + h->callback(&b, h->userdata); } return b; @@ -343,6 +374,11 @@ void buffer_destroy(struct buffer *buffer) { free(buffer->filename); buffer->filename = NULL; + VEC_DESTROY(&buffer->hooks->update_hooks); + VEC_DESTROY(&buffer->hooks->insert_hooks); + VEC_DESTROY(&buffer->hooks->delete_hooks); + free(buffer->hooks); + undo_destroy(&buffer->undo); } @@ -381,6 +417,10 @@ struct location buffer_add(struct buffer *buffer, struct location at, (struct undo_boundary){.save_point = false}); } + VEC_FOR_EACH(&buffer->hooks->insert_hooks, struct insert_hook * h) { + h->callback(buffer, region_new(initial, final), h->userdata); + } + buffer->modified = true; return final; } @@ -473,8 +513,8 @@ struct location buffer_newline(struct buffer *buffer, struct location at) { struct location buffer_indent(struct buffer *buffer, struct location at) { uint32_t tab_width = buffer->lang.tab_width; - buffer_add(buffer, at, (uint8_t *)" ", - tab_width > 16 ? 16 : tab_width); + return buffer_add(buffer, at, (uint8_t *)" ", + tab_width > 16 ? 16 : tab_width); } struct location buffer_undo(struct buffer *buffer, struct location dot) { @@ -557,7 +597,7 @@ static void search_line(struct text_chunk *chunk, void *userdata) { uint32_t pattern_nchars = utf8_nchars((uint8_t *)data->pattern, pattern_len); char *line = malloc(chunk->nbytes + 1); - strncpy(line, chunk->text, chunk->nbytes); + strncpy(line, (const char *)chunk->text, chunk->nbytes); line[chunk->nbytes] = '\0'; char *hit = NULL; uint32_t byteidx = 0; @@ -632,6 +672,10 @@ struct location buffer_delete(struct buffer *buffer, struct region region) { region.end.line, region.end.col); buffer->modified = true; + VEC_FOR_EACH(&buffer->hooks->delete_hooks, struct delete_hook * h) { + h->callback(buffer, region, h->userdata); + } + return region.begin; } @@ -660,7 +704,7 @@ struct location buffer_paste_older(struct buffer *buffer, struct location at) { // remove previous paste struct text_chunk *curr = &g_kill_ring.buffer[g_kill_ring.curr_idx]; - delete_with_undo(buffer, g_kill_ring.last_paste, at); + buffer_delete(buffer, region_new(g_kill_ring.last_paste, at)); // paste older if (g_kill_ring.paste_idx - 1 > 0) { @@ -669,10 +713,10 @@ struct location buffer_paste_older(struct buffer *buffer, struct location at) { g_kill_ring.paste_idx = g_kill_ring.curr_idx; } - paste(buffer, g_kill_ring.last_paste, g_kill_ring.paste_idx); + return paste(buffer, g_kill_ring.last_paste, g_kill_ring.paste_idx); } else { - buffer_paste(buffer, at); + return buffer_paste(buffer, at); } } @@ -680,15 +724,42 @@ struct text_chunk buffer_line(struct buffer *buffer, uint32_t line) { return text_get_line(buffer->text, line); } +struct text_chunk buffer_region(struct buffer *buffer, struct region region) { + return text_get_region(buffer->text, region.begin.line, region.begin.col, + region.end.line, region.end.col); +} + +uint32_t buffer_add_insert_hook(struct buffer *buffer, insert_hook_cb hook, + void *userdata) { + return insert_insert_hook(&buffer->hooks->insert_hooks, + &buffer->hooks->insert_hook_id, hook, userdata); +} + +void buffer_remove_insert_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback) { + remove_insert_hook(&buffer->hooks->insert_hooks, hook_id, callback); +} + +uint32_t buffer_add_delete_hook(struct buffer *buffer, delete_hook_cb hook, + void *userdata) { + return insert_delete_hook(&buffer->hooks->delete_hooks, + &buffer->hooks->delete_hook_id, hook, userdata); +} + +void buffer_remove_delete_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback) { + remove_delete_hook(&buffer->hooks->delete_hooks, hook_id, callback); +} + uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook, void *userdata) { - VEC_APPEND(&buffer->update_hooks, struct update_hook_entry * e); - struct update_hook *h = &e->hook; - h->callback = hook; - h->userdata = userdata; + return insert_update_hook(&buffer->hooks->update_hooks, + &buffer->hooks->update_hook_id, hook, userdata); +} - // TODO: cant really have this if we actually want to remove a hook - return VEC_SIZE(&buffer->update_hooks) - 1; +void buffer_remove_update_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback) { + remove_update_hook(&buffer->hooks->update_hooks, hook_id, callback); } struct cmdbuf { @@ -710,8 +781,8 @@ static uint32_t visual_char_width(uint8_t *byte, uint32_t maxlen) { } } -static uint32_t visual_string_width(uint8_t *txt, uint32_t len, - uint32_t start_col, uint32_t end_col) { +uint32_t visual_string_width(uint8_t *txt, uint32_t len, uint32_t start_col, + uint32_t end_col) { uint32_t start_byte = utf8_nbytes(txt, len, start_col); uint32_t end_byte = utf8_nbytes(txt, len, end_col); @@ -780,9 +851,10 @@ void render_line(struct text_chunk *line, void *userdata) { // apply new properties for (uint32_t propi = 0; propi < nnew_props; ++propi) { struct text_property *prop = new_props[propi]; + struct text_property_colors *colors = &prop->colors; + switch (prop->type) { case TextProperty_Colors: - struct text_property_colors *colors = &prop->colors; if (colors->set_bg) { command_list_set_index_color_bg(cmdbuf->cmds, colors->bg); } @@ -821,13 +893,14 @@ void render_line(struct text_chunk *line, void *userdata) { } void buffer_update(struct buffer *buffer, struct buffer_update_params *params) { - if (params->width == 0 || params->height == 0) { - return; + VEC_FOR_EACH(&buffer->hooks->update_hooks, struct update_hook * h) { + h->callback(buffer, h->userdata); } +} - VEC_FOR_EACH(&buffer->update_hooks, struct update_hook_entry * entry) { - struct update_hook *h = &entry->hook; - h->callback(buffer, params->width, params->height, h->userdata); +void buffer_render(struct buffer *buffer, struct buffer_render_params *params) { + if (params->width == 0 || params->height == 0) { + return; } struct setting *show_ws = settings_get("editor.show-whitespace"); diff --git a/src/dged/buffer.h b/src/dged/buffer.h index e29e3e1..7e4ef78 100644 --- a/src/dged/buffer.h +++ b/src/dged/buffer.h @@ -14,29 +14,7 @@ #include "window.h" struct command_list; - -/** Buffer update hook callback function */ -typedef void (*update_hook_cb)(struct buffer *buffer, uint32_t width, - uint32_t height, void *userdata); - -/** - * A buffer update hook. - * - * Can be used to implement custom behavior on top of a buffer. Used for - * minibuffer. - */ -struct update_hook { - /** Callback function */ - update_hook_cb callback; - - /** Optional userdata to pass to the callback function unmodified */ - void *userdata; -}; - -struct update_hook_entry { - uint32_t id; - struct update_hook hook; -}; +struct hooks; /** * A buffer of text that can be modified, read from and written to disk. @@ -55,12 +33,12 @@ struct buffer { /** Time when buffer was last written to disk */ struct timespec last_write; + /** Hooks for this buffer */ + struct hooks *hooks; + /** Text data structure */ struct text *text; - /** Buffer update hooks */ - VEC(struct update_hook_entry) update_hooks; - /** Buffer undo stack */ struct undo_stack undo; @@ -396,6 +374,16 @@ struct location buffer_paste_older(struct buffer *buffer, struct location at); */ struct text_chunk buffer_line(struct buffer *buffer, uint32_t line); +/** + * Get a region of text from the buffer. + * + * @param buffer The buffer to get text from. + * @param region A region representing the buffer area to get text from. + * + * @returns A text chunk describing the region. + */ +struct text_chunk buffer_region(struct buffer *buffer, struct region region); + void buffer_add_text_property(struct buffer *buffer, struct location start, struct location end, struct text_property property); @@ -407,6 +395,12 @@ void buffer_get_text_properties(struct buffer *buffer, struct location location, void buffer_clear_text_properties(struct buffer *buffer); +/** Callback when removing hooks to clean up userdata */ +typedef void (*remove_hook_cb)(void *userdata); + +/** Buffer update hook callback function */ +typedef void (*update_hook_cb)(struct buffer *buffer, void *userdata); + /** * Add a buffer update hook. * @@ -418,23 +412,51 @@ void buffer_clear_text_properties(struct buffer *buffer); uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook, void *userdata); +void buffer_remove_update_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback); + +/** Buffer insert hook callback function */ +typedef void (*insert_hook_cb)(struct buffer *buffer, struct region inserted, + void *userdata); + +uint32_t buffer_add_insert_hook(struct buffer *buffer, insert_hook_cb callback, + void *userdata); +void buffer_remove_insert_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback); + +/** Buffer delete hook callback function */ +typedef void (*delete_hook_cb)(struct buffer *buffer, struct region removed, + void *userdata); + +uint32_t buffer_add_delete_hook(struct buffer *buffer, delete_hook_cb callback, + void *userdata); +void buffer_remove_delete_hook(struct buffer *buffer, uint32_t hook_id, + remove_hook_cb callback); + /** Buffer create hook callback function */ typedef void (*create_hook_cb)(struct buffer *buffer, void *userdata); /** * Add a buffer create hook. * - * @param [in] hook Create hook callback. + * @param [in] callback Create hook callback. * @param [in] userdata Pointer to data that is passed unmodified to the update * hook. * @returns The hook id. */ -uint32_t buffer_add_create_hook(create_hook_cb hook, void *userdata); +uint32_t buffer_add_create_hook(create_hook_cb callback, void *userdata); + +void buffer_remove_create_hook(uint32_t hook_id, remove_hook_cb callback); /** * Parameters for updating a buffer. */ -struct buffer_update_params { +struct buffer_update_params {}; + +/** + * Parameters for rendering a buffer. + */ +struct buffer_render_params { /** Command list to add rendering commands for the buffer */ struct command_list *commands; @@ -459,4 +481,15 @@ struct buffer_update_params { */ void buffer_update(struct buffer *buffer, struct buffer_update_params *params); +/** + * Render a buffer. + * @param [in] buffer The buffer to render. + * @param [inout] params The parameters for the rendering. + */ +void buffer_render(struct buffer *buffer, struct buffer_render_params *params); + +// TODO: move this to where it makes sense +uint32_t visual_string_width(uint8_t *txt, uint32_t len, uint32_t start_col, + uint32_t end_col); + #endif diff --git a/src/dged/buffer_view.c b/src/dged/buffer_view.c index a95e3b0..05d3caa 100644 --- a/src/dged/buffer_view.c +++ b/src/dged/buffer_view.c @@ -3,6 +3,7 @@ #include "buffer.h" #include "buffer_view.h" #include "display.h" +#include "utf8.h" struct modeline { uint8_t *buffer; @@ -240,6 +241,24 @@ struct location buffer_view_dot_to_relative(struct buffer_view *view) { }; } +struct location buffer_view_dot_to_visual(struct buffer_view *view) { + // calculate visual column index for dot column + struct text_chunk c = buffer_line(view->buffer, view->dot.line); + uint32_t width = visual_string_width(c.text, c.nbytes, 0, view->dot.col); + if (view->scroll.col > 0) { + width -= visual_string_width(c.text, c.nbytes, 0, view->scroll.col); + } + + struct location l = buffer_view_dot_to_relative(view); + l.col = width + view->fringe_width; + + if (c.allocated) { + free(c.text); + } + + return l; +} + void buffer_view_undo(struct buffer_view *view) { view->dot = buffer_undo(view->buffer, view->dot); } @@ -345,6 +364,9 @@ static void render_modeline(struct modeline *modeline, struct buffer_view *view, void buffer_view_update(struct buffer_view *view, struct buffer_view_update_params *params) { + struct buffer_update_params update_params = {}; + buffer_update(view->buffer, &update_params); + uint32_t height = params->height; uint32_t width = params->width; @@ -409,22 +431,13 @@ void buffer_view_update(struct buffer_view *view, struct command_list *buf_cmds = command_list_create( width * height, params->frame_alloc, params->window_x + linum_width, params->window_y, view->buffer->name); - struct buffer_update_params bufparams = { + struct buffer_render_params render_params = { .commands = buf_cmds, .origin = view->scroll, .width = width, .height = height, }; - buffer_update(view->buffer, &bufparams); - - /* Make sure the dot is always inside buffer limits. - * Updating the buffer above could have removed text. - * TODO: this is not really correct, since it may have caused - * changes that would need a re-eval of scroll and redraw. - * Hooks should prob not get width and height and be ran before rendering. - */ - view->dot = buffer_clamp(view->buffer, (int64_t)view->dot.line, - (int64_t)view->dot.col); + buffer_render(view->buffer, &render_params); // draw buffer commands nested inside this command list command_list_draw_command_list(params->commands, buf_cmds); diff --git a/src/dged/buffer_view.h b/src/dged/buffer_view.h index 0002b95..1c8fa6a 100644 --- a/src/dged/buffer_view.h +++ b/src/dged/buffer_view.h @@ -80,6 +80,7 @@ void buffer_view_clear_mark(struct buffer_view *view); void buffer_view_set_mark_at(struct buffer_view *view, struct location mark); struct location buffer_view_dot_to_relative(struct buffer_view *view); +struct location buffer_view_dot_to_visual(struct buffer_view *view); void buffer_view_undo(struct buffer_view *view); diff --git a/src/dged/display.c b/src/dged/display.c index b779d5f..85a6e0b 100644 --- a/src/dged/display.c +++ b/src/dged/display.c @@ -5,6 +5,7 @@ #include "utf8.h" #include <assert.h> +#include <ctype.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> @@ -130,24 +131,49 @@ uint32_t display_width(struct display *display) { return display->width; } uint32_t display_height(struct display *display) { return display->height; } void putch(uint8_t c) { - if (c != '\r') { + // TODO: move this to buffer rendering + if (c < ' ') { + fprintf(stdout, "^%c", c + 0x40); + } else if (c == 0x7f) { + fprintf(stdout, "^?"); + } else if (utf8_byte_is_unicode_start(c) || + utf8_byte_is_unicode_continuation(c)) { putc(c, stdout); + } else if (c >= ' ' && c < 0x7f) { + putc(c, stdout); + } else { + fprintf(stdout, "|0x%02x|", c); + } +} + +static void apply_fmt(uint8_t *fmt_stack, uint32_t fmt_stack_len) { + if (fmt_stack == NULL || fmt_stack_len == 0) { + return; + } + + for (uint32_t i = 0; i < fmt_stack_len; ++i) { + putc(fmt_stack[i], stdout); } + putc('m', stdout); } -void putch_ws(uint8_t c, bool show_whitespace) { +void putch_ws(uint8_t c, bool show_whitespace, uint8_t *fmt_stack, + uint32_t fmt_stack_len) { if (show_whitespace && c == '\t') { fputs("\x1b[90m → \x1b[39m", stdout); + apply_fmt(fmt_stack, fmt_stack_len); } else if (show_whitespace && c == ' ') { fputs("\x1b[90m·\x1b[39m", stdout); + apply_fmt(fmt_stack, fmt_stack_len); } else { putch(c); } } -void putbytes(uint8_t *line_bytes, uint32_t line_length, bool show_whitespace) { +void putbytes(uint8_t *line_bytes, uint32_t line_length, bool show_whitespace, + uint8_t *fmt_stack, uint32_t fmt_stack_len) { for (uint32_t bytei = 0; bytei < line_length; ++bytei) { - putch_ws(line_bytes[bytei], show_whitespace); + putch_ws(line_bytes[bytei], show_whitespace, fmt_stack, fmt_stack_len); } } @@ -156,26 +182,28 @@ void put_ansiparm(int n) { if (q != 0) { int r = q / 10; if (r != 0) { - putch((r % 10) + '0'); + putc((r % 10) + '0', stdout); } - putch((q % 10) + '0'); + putc((q % 10) + '0', stdout); } - putch((n % 10) + '0'); + putc((n % 10) + '0', stdout); } void display_move_cursor(struct display *display, uint32_t row, uint32_t col) { - putch(ESC); - putch('['); + putc(ESC, stdout); + putc('[', stdout); put_ansiparm(row + 1); - putch(';'); + putc(';', stdout); put_ansiparm(col + 1); - putch('H'); + putc('H', stdout); } void display_clear(struct display *display) { display_move_cursor(display, 0, 0); uint8_t bytes[] = {ESC, '[', 'J'}; - putbytes(bytes, 3, false); + putc(ESC, stdout); + putc('[', stdout); + putc('J', stdout); } struct command_list *command_list_create(uint32_t initial_capacity, @@ -346,9 +374,9 @@ void display_render(struct display *display, struct draw_text_cmd *txt_cmd = cmd->draw_txt; display_move_cursor(display, txt_cmd->row + cl->yoffset, txt_cmd->col + cl->xoffset); - putbytes(fmt_stack, fmt_stack_len, false); - putch('m'); - putbytes(txt_cmd->data, txt_cmd->len, show_whitespace_state); + apply_fmt(fmt_stack, fmt_stack_len); + putbytes(txt_cmd->data, txt_cmd->len, show_whitespace_state, fmt_stack, + fmt_stack_len); break; } @@ -356,11 +384,11 @@ void display_render(struct display *display, struct repeat_cmd *repeat_cmd = cmd->repeat; display_move_cursor(display, repeat_cmd->row + cl->yoffset, repeat_cmd->col + cl->xoffset); - putbytes(fmt_stack, fmt_stack_len, false); - putch('m'); + apply_fmt(fmt_stack, fmt_stack_len); uint32_t nbytes = utf8_nbytes((uint8_t *)&repeat_cmd->c, 4, 1); for (uint32_t i = 0; i < repeat_cmd->nrepeat; ++i) { - putbytes((uint8_t *)&repeat_cmd->c, nbytes, show_whitespace_state); + putbytes((uint8_t *)&repeat_cmd->c, nbytes, show_whitespace_state, + fmt_stack, fmt_stack_len); } break; } @@ -394,21 +422,21 @@ void display_render(struct display *display, } void hide_cursor() { - putch(ESC); - putch('['); - putch('?'); - putch('2'); - putch('5'); - putch('l'); + putc(ESC, stdout); + putc('[', stdout); + putc('?', stdout); + putc('2', stdout); + putc('5', stdout); + putc('l', stdout); } void show_cursor() { - putch(ESC); - putch('['); - putch('?'); - putch('2'); - putch('5'); - putch('h'); + putc(ESC, stdout); + putc('[', stdout); + putc('?', stdout); + putc('2', stdout); + putc('5', stdout); + putc('h', stdout); } void display_begin_render(struct display *display) { hide_cursor(); } diff --git a/src/dged/keyboard.c b/src/dged/keyboard.c index 63b7b6e..4d4526e 100644 --- a/src/dged/keyboard.c +++ b/src/dged/keyboard.c @@ -163,4 +163,6 @@ uint32_t key_name(struct key *key, char *buf, size_t capacity) { } snprintf(buf, capacity, "%s%c", mod, tolower(key->key)); + + return 0; } diff --git a/src/dged/minibuffer.c b/src/dged/minibuffer.c index d31d634..2105c52 100644 --- a/src/dged/minibuffer.c +++ b/src/dged/minibuffer.c @@ -1,6 +1,7 @@ #include "minibuffer.h" #include "binding.h" #include "buffer.h" +#include "buffer_view.h" #include "command.h" #include "display.h" @@ -19,9 +20,6 @@ static struct minibuffer { bool clear; struct window *prev_window; - void (*update_callback)(void *); - void *update_callback_userdata; - } g_minibuffer = {0}; uint32_t minibuffer_draw_prompt(struct command_list *commands) { @@ -76,8 +74,7 @@ int32_t minibuffer_execute() { } } -void update(struct buffer *buffer, uint32_t width, uint32_t height, - void *userdata) { +void update(struct buffer *buffer, void *userdata) { struct timespec current; struct minibuffer *mb = (struct minibuffer *)userdata; clock_gettime(CLOCK_MONOTONIC, ¤t); @@ -86,10 +83,6 @@ void update(struct buffer *buffer, uint32_t width, uint32_t height, buffer_clear(buffer); mb->clear = false; } - - if (mb->update_callback != NULL) { - mb->update_callback(mb->update_callback_userdata); - } } void minibuffer_init(struct buffer *buffer) { @@ -150,14 +143,15 @@ void minibuffer_set_prompt_internal(const char *fmt, va_list args) { vsnprintf(g_minibuffer.prompt, sizeof(g_minibuffer.prompt), fmt, args); } -int32_t minibuffer_prompt_internal(struct command_ctx command_ctx, - void (*update_callback)(void *), - void *userdata, const char *fmt, - va_list args) { +int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, + ...) { if (g_minibuffer.buffer == NULL) { return 1; } + va_list args; + va_start(args, fmt); + minibuffer_clear(); g_minibuffer.prompt_active = true; @@ -166,39 +160,13 @@ int32_t minibuffer_prompt_internal(struct command_ctx command_ctx, minibuffer_set_prompt_internal(fmt, args); - if (update_callback != NULL) { - g_minibuffer.update_callback = update_callback; - g_minibuffer.update_callback_userdata = userdata; - } - if (windows_get_active() != minibuffer_window()) { g_minibuffer.prev_window = windows_get_active(); windows_set_active(minibuffer_window()); } - - return 0; -} - -int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, - ...) { - va_list args; - va_start(args, fmt); - int32_t r = minibuffer_prompt_internal(command_ctx, NULL, NULL, fmt, args); - va_end(args); - - return r; -} - -int32_t minibuffer_prompt_interactive(struct command_ctx command_ctx, - void (*update_callback)(void *), - void *userdata, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - int32_t r = minibuffer_prompt_internal(command_ctx, update_callback, userdata, - fmt, args); va_end(args); - return r; + return 0; } void minibuffer_set_prompt(const char *fmt, ...) { @@ -210,7 +178,6 @@ void minibuffer_set_prompt(const char *fmt, ...) { void minibuffer_abort_prompt() { minibuffer_clear(); - g_minibuffer.update_callback = NULL; g_minibuffer.prompt_active = false; if (g_minibuffer.prev_window != NULL) { diff --git a/src/dged/minibuffer.h b/src/dged/minibuffer.h index fb6eae4..727aac5 100644 --- a/src/dged/minibuffer.h +++ b/src/dged/minibuffer.h @@ -59,10 +59,6 @@ void minibuffer_echo_timeout(uint32_t timeout, const char *fmt, ...); */ int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, ...); -int32_t minibuffer_prompt_interactive(struct command_ctx command_ctx, - void (*update_callback)(void *), - void *userdata, const char *fmt, ...); - void minibuffer_set_prompt(const char *fmt, ...); uint32_t minibuffer_draw_prompt(struct command_list *commands); diff --git a/src/dged/reactor-epoll.c b/src/dged/reactor-epoll.c index 96c6da4..22888f6 100644 --- a/src/dged/reactor-epoll.c +++ b/src/dged/reactor-epoll.c @@ -109,7 +109,7 @@ bool reactor_next_file_event(struct reactor *reactor, struct file_event *out) { struct inotify_event *ev = (struct inotify_event *)buf; // TODO: change when adding more of these out->mask = FileWritten; - if (ev->mask & IN_IGNORED != 0) { + if ((ev->mask & IN_IGNORED) != 0) { out->mask |= LastEvent; } out->id = (uint32_t)ev->wd; diff --git a/src/dged/settings-parse.c b/src/dged/settings-parse.c index 6c1c482..c63a0cf 100644 --- a/src/dged/settings-parse.c +++ b/src/dged/settings-parse.c @@ -115,6 +115,7 @@ bool parser_next_token(struct parser *state, struct token *token_out) { memset(token_out, 0, sizeof(struct token)); while (state->reader.getbytes(1, &byte, state->reader.userdata) > 0) { + bool multiline = false; switch (classify(byte)) { case Byte_Alphanumeric: // unquoted key / value if (!parse_value) { @@ -223,7 +224,7 @@ bool parser_next_token(struct parser *state, struct token *token_out) { return true; case '"': // quoted key or string value - bool multiline = false; + multiline = false; if (parse_value) { token_out->type = Token_StringValue; } else { diff --git a/src/dged/settings.c b/src/dged/settings.c index 4370aa9..7b5e4dc 100644 --- a/src/dged/settings.c +++ b/src/dged/settings.c @@ -116,6 +116,9 @@ static int32_t parse_toml(struct parser *state, char **errmsgs[]) { VEC_INIT(&errs, 16); struct token t = {0}; + int64_t i = 0; + bool b = false; + char *v = NULL, *err = NULL; while (parser_next_token(state, &t)) { switch (t.type) { case Token_Table: @@ -145,26 +148,26 @@ static int32_t parse_toml(struct parser *state, char **errmsgs[]) { curkey = calloc(len, 1); if (curtbl != NULL) { strcpy(curkey, curtbl); - strncat(curkey, ".", 1); + curkey[strlen(curtbl)] = '.'; } strncat(curkey, (char *)t.data, t.len); break; case Token_IntValue: - int64_t i = *((int64_t *)t.data); + i = *((int64_t *)t.data); settings_upsert(curkey, (struct setting_value){.type = Setting_Number, .number_value = i}); break; case Token_BoolValue: - bool b = *((bool *)t.data); + b = *((bool *)t.data); settings_upsert(curkey, (struct setting_value){.type = Setting_Bool, .bool_value = b}); break; case Token_StringValue: - char *v = calloc(t.len + 1, 1); + v = calloc(t.len + 1, 1); strncpy(v, (char *)t.data, t.len); settings_upsert(curkey, (struct setting_value){.type = Setting_String, .string_value = v}); @@ -172,11 +175,14 @@ static int32_t parse_toml(struct parser *state, char **errmsgs[]) { break; case Token_Error: - char *err = malloc(t.len + 128); + err = malloc(t.len + 128); snprintf(err, t.len + 128, "error (%d:%d): %.*s\n", t.row, t.col, t.len, (char *)t.data); VEC_PUSH(&errs, err); break; + + case Token_Comment: + break; } } diff --git a/src/dged/text.c b/src/dged/text.c index b3eb4ad..4d9a073 100644 --- a/src/dged/text.c +++ b/src/dged/text.c @@ -47,6 +47,8 @@ struct text *text_create(uint32_t initial_capacity) { } void text_destroy(struct text *text) { + VEC_DESTROY(&text->properties); + for (uint32_t li = 0; li < text->nlines; ++li) { free(text->lines[li].data); text->lines[li].data = NULL; diff --git a/src/dged/utf8.c b/src/dged/utf8.c index 30344be..b71a7e1 100644 --- a/src/dged/utf8.c +++ b/src/dged/utf8.c @@ -71,7 +71,7 @@ uint32_t utf8_visual_char_width(uint8_t *bytes, uint32_t len) { if (utf8_byte_is_unicode_start(*bytes)) { wchar_t wc; size_t nbytes = 0; - if (nbytes = mbrtowc(&wc, (char *)bytes, len, NULL) > 0) { + if ((nbytes = mbrtowc(&wc, (char *)bytes, len, NULL)) > 0) { return wcwidth(wc); } else { return 0; diff --git a/src/dged/vec.h b/src/dged/vec.h index df5cd0e..8b17fa5 100644 --- a/src/dged/vec.h +++ b/src/dged/vec.h @@ -5,6 +5,7 @@ #define VEC(entry) \ struct { \ + entry *temp; \ entry *entries; \ uint32_t nentries; \ uint32_t capacity; \ @@ -12,10 +13,12 @@ #define VEC_INIT(vec, initial_capacity) \ (vec)->entries = malloc(sizeof((vec)->entries[0]) * initial_capacity); \ + (vec)->temp = calloc(1, sizeof((vec)->entries[0])); \ (vec)->capacity = initial_capacity; \ (vec)->nentries = 0; #define VEC_DESTROY(vec) \ + free((vec)->temp); \ free((vec)->entries); \ (vec)->entries = NULL; \ (vec)->capacity = 0; \ @@ -36,6 +39,20 @@ (vec)->entries[(vec)->nentries] = entry; \ ++(vec)->nentries; +#define VEC_POP(vec, var) \ + var = (vec)->entries[(vec)->nentries - 1]; \ + if ((vec)->nentries > 0) { \ + --(vec)->nentries; \ + } + +#define VEC_SWAP(vec, idx1, idx2) \ + if (idx1 < (vec)->nentries && idx2 < (vec)->nentries && idx1 >= 0 && \ + idx2 >= 0) { \ + *((vec)->temp) = (vec)->entries[idx1]; \ + (vec)->entries[idx1] = (vec)->entries[idx2]; \ + (vec)->entries[idx2] = *((vec)->temp); \ + } + #define VEC_APPEND(vec, var) \ if ((vec)->nentries + 1 >= (vec)->capacity) { \ VEC_GROW(vec, (vec)->capacity * 2); \ diff --git a/src/dged/window.c b/src/dged/window.c index a25154a..da2cebe 100644 --- a/src/dged/window.c +++ b/src/dged/window.c @@ -178,6 +178,7 @@ void windows_update(void *(*frame_alloc)(size_t), uint64_t frame_time) { .window_y = w->y, .frame_alloc = frame_alloc, }; + buffer_view_update(&w->buffer_view, &p); command_list_draw_command_list(w->commands, inner_commands); diff --git a/src/main/bindings.c b/src/main/bindings.c index 0e01306..32dcbdb 100644 --- a/src/main/bindings.c +++ b/src/main/bindings.c @@ -3,16 +3,19 @@ #include "dged/minibuffer.h" #include "dged/vec.h" +#include "bindings.h" + static struct keymap g_global_keymap, g_ctrlx_map, g_windows_keymap, g_buffer_default_keymap; struct buffer_keymap { + buffer_keymap_id id; struct buffer *buffer; - bool active; struct keymap keymap; }; static VEC(struct buffer_keymap) g_buffer_keymaps; +static buffer_keymap_id g_current_keymap_id; void set_default_buffer_bindings(struct keymap *keymap) { struct binding buffer_bindings[] = { @@ -46,6 +49,7 @@ void set_default_buffer_bindings(struct keymap *keymap) { BINDING(ENTER, "newline"), BINDING(TAB, "indent"), + BINDING(Spec, 'Z', "insert-tab"), BINDING(Ctrl, 'K', "kill-line"), BINDING(DELETE, "delete-char"), @@ -68,7 +72,18 @@ void set_default_buffer_bindings(struct keymap *keymap) { sizeof(buffer_bindings) / sizeof(buffer_bindings[0])); } -struct keymap *register_bindings() { +int32_t execute(struct command_ctx ctx, int argc, const char *argv[]) { + // TODO: this should be more lib-like + return minibuffer_execute(); +} + +static struct command execute_minibuffer_command = { + .fn = execute, + .name = "minibuffer-execute", + .userdata = NULL, +}; + +void init_bindings() { g_global_keymap = keymap_create("global", 32); g_ctrlx_map = keymap_create("c-x", 32); g_windows_keymap = keymap_create("c-x w", 32); @@ -122,93 +137,79 @@ struct keymap *register_bindings() { sizeof(ctrlx_bindings) / sizeof(ctrlx_bindings[0])); VEC_INIT(&g_buffer_keymaps, 32); + g_current_keymap_id = 0; - return &g_global_keymap; + /* Minibuffer binds. + * This map is actually never removed so forget about the id. + */ + struct binding minibuffer_binds[] = { + ANONYMOUS_BINDING(ENTER, &execute_minibuffer_command), + }; + struct keymap minibuffer_map = keymap_create("minibuffer", 8); + keymap_bind_keys(&minibuffer_map, minibuffer_binds, + sizeof(minibuffer_binds) / sizeof(minibuffer_binds[0])); + buffer_add_keymap(minibuffer_buffer(), minibuffer_map); } -struct keymap *buffer_default_bindings() { - return &g_buffer_default_keymap; -} +buffer_keymap_id buffer_add_keymap(struct buffer *buffer, + struct keymap keymap) { + buffer_keymap_id id = ++g_current_keymap_id; + VEC_PUSH(&g_buffer_keymaps, ((struct buffer_keymap){ + .id = id, + .buffer = buffer, + .keymap = keymap, + })); -int32_t execute(struct command_ctx ctx, int argc, const char *argv[]) { - // TODO: this should be more lib-like - return minibuffer_execute(); + return id; } -static struct command execute_minibuffer_command = { - .fn = execute, - .name = "minibuffer-execute", - .userdata = NULL, -}; - -void buffer_bind_keys(struct buffer *buffer, struct binding *bindings, - uint32_t nbindings) { - struct buffer_keymap *target = NULL; - VEC_FOR_EACH(&g_buffer_keymaps, struct buffer_keymap * km) { - if (buffer == km->buffer) { - target = km; +void buffer_remove_keymap(buffer_keymap_id id) { + VEC_FOR_EACH_INDEXED(&g_buffer_keymaps, struct buffer_keymap * km, i) { + if (km->id == id) { + VEC_SWAP(&g_buffer_keymaps, i, VEC_SIZE(&g_buffer_keymaps) - 1); + VEC_POP(&g_buffer_keymaps, struct buffer_keymap removed); + keymap_destroy(&removed.keymap); } } +} - if (target == NULL) { - struct buffer_keymap new = (struct buffer_keymap){ - .buffer = buffer, - .active = false, - }; - VEC_PUSH(&g_buffer_keymaps, new); - target = VEC_BACK(&g_buffer_keymaps); - } +uint32_t buffer_keymaps(struct buffer *buffer, struct keymap *keymaps[], + uint32_t max_nkeymaps) { + uint32_t nkeymaps = 0; - if (!target->active) { - target->keymap = keymap_create("buffer-overlay-keys", 32); - target->active = true; - set_default_buffer_bindings(&target->keymap); + // global keybinds + if (nkeymaps < max_nkeymaps) { + keymaps[nkeymaps] = &g_global_keymap; + ++nkeymaps; } - keymap_bind_keys(&target->keymap, bindings, nbindings); -} - -// TODO: do something better -void reset_buffer_keys(struct buffer *buffer) { - VEC_FOR_EACH(&g_buffer_keymaps, struct buffer_keymap * km) { - if (buffer == km->buffer) { - keymap_destroy(&km->keymap); - km->active = false; - } + // buffer keybinds + if (nkeymaps < max_nkeymaps) { + keymaps[nkeymaps] = &g_buffer_default_keymap; + ++nkeymaps; } -} -struct keymap *buffer_keymap(struct buffer *buffer) { - VEC_FOR_EACH(&g_buffer_keymaps, struct buffer_keymap * km) { - if (buffer == km->buffer && km->active) { - return &km->keymap; + // keybinds specific to this buffer + if (nkeymaps < max_nkeymaps) { + VEC_FOR_EACH(&g_buffer_keymaps, struct buffer_keymap * km) { + if (buffer == km->buffer && nkeymaps < max_nkeymaps) { + keymaps[nkeymaps] = &km->keymap; + ++nkeymaps; + } } } - return &g_buffer_default_keymap; + return nkeymaps; } -void reset_minibuffer_keys(struct buffer *minibuffer) { - reset_buffer_keys(minibuffer); - struct binding bindings[] = { - ANONYMOUS_BINDING(ENTER, &execute_minibuffer_command), - }; - - buffer_bind_keys(minibuffer, bindings, - sizeof(bindings) / sizeof(bindings[0])); -} - -void destroy_keymaps() { +void destroy_bindings() { keymap_destroy(&g_windows_keymap); keymap_destroy(&g_global_keymap); keymap_destroy(&g_ctrlx_map); keymap_destroy(&g_buffer_default_keymap); VEC_FOR_EACH(&g_buffer_keymaps, struct buffer_keymap * km) { - if (km->active) { - keymap_destroy(&km->keymap); - km->active = false; - } + keymap_destroy(&km->keymap); } VEC_DESTROY(&g_buffer_keymaps); diff --git a/src/main/bindings.h b/src/main/bindings.h index d0ba27c..4fd760a 100644 --- a/src/main/bindings.h +++ b/src/main/bindings.h @@ -4,12 +4,12 @@ struct keymap; struct buffer; struct binding; -struct keymap *register_bindings(); +void init_bindings(); -void buffer_bind_keys(struct buffer *buffer, struct binding *bindings, - uint32_t nbindings); -void reset_buffer_keys(struct buffer *buffer); -void reset_minibuffer_keys(struct buffer *minibuffer); -struct keymap *buffer_keymap(struct buffer *buffer); +typedef uint64_t buffer_keymap_id; +buffer_keymap_id buffer_add_keymap(struct buffer *buffer, struct keymap keymap); +void buffer_remove_keymap(buffer_keymap_id id); +uint32_t buffer_keymaps(struct buffer *buffer, struct keymap *keymaps[], + uint32_t max_nkeymaps); -void destroy_keymaps(); +void destroy_bindings(); diff --git a/src/main/cmds.c b/src/main/cmds.c index 5991f4a..bf465ed 100644 --- a/src/main/cmds.c +++ b/src/main/cmds.c @@ -1,11 +1,7 @@ -#define _DEFAULT_SOURCE -#include <dirent.h> #include <errno.h> -#include <libgen.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> -#include <sys/types.h> #include "dged/binding.h" #include "dged/buffer.h" @@ -18,16 +14,15 @@ #include "dged/settings.h" #include "bindings.h" +#include "completion.h" #include "search-replace.h" -static void abort_completion(); - int32_t _abort(struct command_ctx ctx, int argc, const char *argv[]) { abort_replace(); abort_completion(); + disable_completion(minibuffer_buffer()); minibuffer_abort_prompt(); buffer_view_clear_mark(window_buffer_view(ctx.active_window)); - reset_minibuffer_keys(minibuffer_buffer()); minibuffer_echo_timeout(4, "💣 aborted"); return 0; } @@ -43,291 +38,6 @@ int32_t exit_editor(struct command_ctx ctx, int argc, const char *argv[]) { return 0; } -struct completion { - const char *display; - const char *insert; - bool complete; -}; - -uint32_t g_ncompletions = 0; -struct completion g_completions[50] = {0}; - -static void abort_completion() { - reset_minibuffer_keys(minibuffer_buffer()); - windows_close_popup(); - - for (uint32_t compi = 0; compi < g_ncompletions; ++compi) { - free((void *)g_completions[compi].display); - free((void *)g_completions[compi].insert); - } - g_ncompletions = 0; -} - -int cmp_completions(const void *comp_a, const void *comp_b) { - struct completion *a = (struct completion *)comp_a; - struct completion *b = (struct completion *)comp_b; - return strcmp(a->display, b->display); -} - -static bool is_hidden(const char *filename) { - return filename[0] == '.' && filename[1] != '\0' && filename[1] != '.'; -} - -static void complete_path(const char *path, struct completion results[], - uint32_t nresults_max, uint32_t *nresults) { - uint32_t n = 0; - char *p1 = to_abspath(path); - size_t len = strlen(p1); - char *p2 = strdup(p1); - - size_t inlen = strlen(path); - - if (len == 0) { - goto done; - } - - if (nresults_max == 0) { - goto done; - } - - const char *dir = p1; - const char *file = ""; - - // check the input path here since - // to_abspath removes trailing slashes - if (path[inlen - 1] != '/') { - dir = dirname(p1); - file = basename(p2); - } - - DIR *d = opendir(dir); - if (d == NULL) { - goto done; - } - - errno = 0; - while (n < nresults_max) { - struct dirent *de = readdir(d); - if (de == NULL && errno != 0) { - // skip the erroring entry - errno = 0; - continue; - } else if (de == NULL && errno == 0) { - break; - } - - switch (de->d_type) { - case DT_DIR: - case DT_REG: - case DT_LNK: - if (!is_hidden(de->d_name) && - (strncmp(file, de->d_name, strlen(file)) == 0 || strlen(file) == 0)) { - const char *disp = strdup(de->d_name); - results[n] = (struct completion){ - .display = disp, - .insert = strdup(disp + strlen(file)), - .complete = de->d_type == DT_REG, - }; - ++n; - } - break; - } - } - - closedir(d); - -done: - free(p1); - free(p2); - - qsort(results, n, sizeof(struct completion), cmp_completions); - *nresults = n; -} - -void update_completion_buffer(struct buffer *buffer, uint32_t width, - uint32_t height, void *userdata) { - struct buffer_view *view = (struct buffer_view *)userdata; - struct text_chunk line = buffer_line(buffer, view->dot.line); - buffer_add_text_property( - view->buffer, (struct location){.line = view->dot.line, .col = 0}, - (struct location){.line = view->dot.line, .col = line.nchars}, - (struct text_property){.type = TextProperty_Colors, - .colors = (struct text_property_colors){ - .set_bg = false, - .bg = 0, - .set_fg = true, - .fg = 4, - }}); - - if (line.allocated) { - free(line.text); - } -} - -static int32_t goto_completion(struct command_ctx ctx, int argc, - const char *argv[]) { - void (*movement_fn)(struct buffer_view *) = - (void (*)(struct buffer_view *))ctx.userdata; - struct buffer *b = buffers_find(ctx.buffers, "*completions*"); - - // is it in the popup? - if (b != NULL && window_buffer(popup_window()) == b) { - struct buffer_view *v = window_buffer_view(popup_window()); - movement_fn(v); - - if (v->dot.line >= text_num_lines(b->text)) { - buffer_view_backward_line(v); - } - } - - return 0; -} - -static int32_t insert_completion(struct command_ctx ctx, int argc, - const char *argv[]) { - struct buffer *b = buffers_find(ctx.buffers, "*completions*"); - // is it in the popup? - if (b != NULL && window_buffer(popup_window()) == b) { - struct buffer_view *cv = window_buffer_view(popup_window()); - - if (cv->dot.line < g_ncompletions) { - char *ins = (char *)g_completions[cv->dot.line].insert; - bool complete = g_completions[cv->dot.line].complete; - size_t inslen = strlen(ins); - buffer_view_add(window_buffer_view(windows_get_active()), ins, inslen); - - if (minibuffer_focused() && complete) { - minibuffer_execute(); - } - - abort_completion(); - } - } - - return 0; -} - -COMMAND_FN("next-completion", next_completion, goto_completion, - buffer_view_forward_line); -COMMAND_FN("prev-completion", prev_completion, goto_completion, - buffer_view_backward_line); -COMMAND_FN("insert-completion", insert_completion, insert_completion, NULL); - -static void on_find_file_input(void *userdata) { - struct buffers *buffers = (struct buffers *)userdata; - struct text_chunk txt = minibuffer_content(); - - struct window *mb = minibuffer_window(); - struct location mb_dot = window_buffer_view(mb)->dot; - struct window_position mbpos = window_position(mb); - - struct buffer *b = buffers_find(buffers, "*completions*"); - if (b == NULL) { - b = buffers_add(buffers, buffer_create("*completions*")); - buffer_add_update_hook(b, update_completion_buffer, - (void *)window_buffer_view(popup_window())); - window_set_buffer_e(popup_window(), b, false, false); - } - - struct buffer_view *v = window_buffer_view(popup_window()); - - char path[1024]; - strncpy(path, txt.text, txt.nbytes); - path[(txt.nbytes >= 1024 ? 1023 : txt.nbytes)] = '\0'; - - for (uint32_t compi = 0; compi < g_ncompletions; ++compi) { - free((void *)g_completions[compi].display); - free((void *)g_completions[compi].insert); - } - - g_ncompletions = 0; - complete_path(path, g_completions, 50, &g_ncompletions); - - size_t max_width = 0; - struct location prev_dot = v->dot; - - buffer_clear(v->buffer); - buffer_view_goto(v, (struct location){.line = 0, .col = 0}); - if (g_ncompletions > 0) { - for (uint32_t compi = 0; compi < g_ncompletions; ++compi) { - const char *disp = g_completions[compi].display; - size_t width = strlen(disp); - if (width > max_width) { - max_width = width; - } - buffer_view_add(v, (uint8_t *)disp, width); - - // the extra newline feels weird in navigation - if (compi != g_ncompletions - 1) { - buffer_view_add(v, (uint8_t *)"\n", 1); - } - } - - buffer_view_goto( - v, (struct location){.line = prev_dot.line, .col = prev_dot.col}); - if (prev_dot.line >= text_num_lines(b->text)) { - buffer_view_backward_line(v); - } - - if (!popup_window_visible()) { - struct binding bindings[] = { - ANONYMOUS_BINDING(Ctrl, 'N', &next_completion_command), - ANONYMOUS_BINDING(Ctrl, 'P', &prev_completion_command), - ANONYMOUS_BINDING(ENTER, &insert_completion_command), - }; - buffer_bind_keys(minibuffer_buffer(), bindings, - sizeof(bindings) / sizeof(bindings[0])); - } - - uint32_t width = max_width > 2 ? max_width : 4, - height = g_ncompletions > 10 ? 10 : g_ncompletions; - windows_show_popup(mbpos.y + mb_dot.line - height, mbpos.x + mb_dot.col, - width, height); - } else { - abort_completion(); - } - - if (txt.allocated) { - free(txt.text); - } -} - -int32_t find_file(struct command_ctx ctx, int argc, const char *argv[]) { - const char *pth = NULL; - if (argc == 0) { - return minibuffer_prompt_interactive(ctx, on_find_file_input, ctx.buffers, - "find file: "); - } - - pth = argv[0]; - struct stat sb = {0}; - if (stat(pth, &sb) < 0 && errno != ENOENT) { - minibuffer_echo("stat on %s failed: %s", pth, strerror(errno)); - return 1; - } - - if (S_ISDIR(sb.st_mode) && errno != ENOENT) { - minibuffer_echo("TODO: implement dired!"); - return 1; - } - - const char *filename = to_abspath(pth); - struct buffer *b = buffers_find_by_filename(ctx.buffers, filename); - free((char *)filename); - - if (b == NULL) { - b = buffers_add(ctx.buffers, buffer_from_file((char *)pth)); - } else { - buffer_reload(b); - } - - window_set_buffer(ctx.active_window, b); - minibuffer_echo_timeout(4, "buffer \"%s\" loaded", - window_buffer(ctx.active_window)->name); - - return 0; -} - int32_t write_file(struct command_ctx ctx, int argc, const char *argv[]) { const char *pth = NULL; if (argc == 0) { @@ -380,8 +90,16 @@ int32_t do_switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) { COMMAND_FN("do-switch-buffer", do_switch_buffer, do_switch_buffer, NULL); +static void switch_buffer_comp_inserted() { minibuffer_execute(); } + int32_t switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) { if (argc == 0) { + struct completion_provider providers[] = {buffer_provider()}; + enable_completion(minibuffer_buffer(), + ((struct completion_trigger){ + .kind = CompletionTrigger_Input, .nchars = 0}), + providers, 1, switch_buffer_comp_inserted); + ctx.self = &do_switch_buffer_command; if (window_has_prev_buffer(ctx.active_window)) { return minibuffer_prompt(ctx, "buffer (default %s): ", @@ -391,6 +109,8 @@ int32_t switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) { } } + disable_completion(minibuffer_buffer()); + return execute_command(&do_switch_buffer_command, ctx.commands, ctx.active_window, ctx.buffers, argc, argv); } @@ -433,7 +153,7 @@ int32_t buflist_visit_cmd(struct command_ctx ctx, int argc, if (end != NULL) { uint32_t len = end - (char *)text.text; char *bufname = (char *)malloc(len + 1); - strncpy(bufname, text.text, len); + strncpy(bufname, (const char *)text.text, len); bufname[len] = '\0'; struct buffer *target = buffers_find(ctx.buffers, bufname); @@ -454,8 +174,8 @@ int32_t buflist_close_cmd(struct command_ctx ctx, int argc, void buflist_refresh(struct buffers *buffers, struct buffer_view *target) { buffer_set_readonly(target->buffer, false); buffer_clear(target->buffer); - buffers_for_each(buffers, buffer_to_list_line, target); buffer_view_goto_beginning(target); + buffers_for_each(buffers, buffer_to_list_line, target); buffer_set_readonly(target->buffer, true); } @@ -496,12 +216,58 @@ int32_t buffer_list(struct command_ctx ctx, int argc, const char *argv[]) { ANONYMOUS_BINDING(None, 'q', &buflist_close), ANONYMOUS_BINDING(None, 'g', &buflist_refresh), }; - buffer_bind_keys(b, bindings, sizeof(bindings) / sizeof(bindings[0])); + struct keymap km = keymap_create("buflist", 8); + keymap_bind_keys(&km, bindings, sizeof(bindings) / sizeof(bindings[0])); + buffer_add_keymap(b, km); windows_set_active(w); return 0; } +static void find_file_comp_inserted() { minibuffer_execute(); } + +int32_t find_file(struct command_ctx ctx, int argc, const char *argv[]) { + const char *pth = NULL; + if (argc == 0) { + struct completion_provider providers[] = {path_provider()}; + enable_completion(minibuffer_buffer(), + ((struct completion_trigger){ + .kind = CompletionTrigger_Input, .nchars = 0}), + providers, 1, find_file_comp_inserted); + return minibuffer_prompt(ctx, "find file: "); + } + + disable_completion(minibuffer_buffer()); + + pth = argv[0]; + struct stat sb = {0}; + if (stat(pth, &sb) < 0 && errno != ENOENT) { + minibuffer_echo("stat on %s failed: %s", pth, strerror(errno)); + return 1; + } + + if (S_ISDIR(sb.st_mode) && errno != ENOENT) { + minibuffer_echo("TODO: implement dired!"); + return 1; + } + + const char *filename = to_abspath(pth); + struct buffer *b = buffers_find_by_filename(ctx.buffers, filename); + free((char *)filename); + + if (b == NULL) { + b = buffers_add(ctx.buffers, buffer_from_file((char *)pth)); + } else { + buffer_reload(b); + } + + window_set_buffer(ctx.active_window, b); + minibuffer_echo_timeout(4, "buffer \"%s\" loaded", + window_buffer(ctx.active_window)->name); + + return 0; +} + void register_global_commands(struct commands *commands, void (*terminate_cb)()) { struct command global_commands[] = { @@ -591,6 +357,15 @@ static int32_t goto_line(struct command_ctx ctx, int argc, const char *argv[]) { line = line - 1; } buffer_view_goto(v, (struct location){.line = line, .col = 0}); + + return 0; +} + +static int32_t insert_tab(struct command_ctx ctx, int argc, + const char *argv[]) { + struct buffer_view *v = window_buffer_view(ctx.active_window); + buffer_view_add(v, (uint8_t *)"\t", 1); + return 0; } void register_buffer_commands(struct commands *commands) { @@ -610,6 +385,7 @@ void register_buffer_commands(struct commands *commands) { {.name = "beginning-of-line", .fn = goto_beginning_of_line_cmd}, {.name = "newline", .fn = newline_cmd}, {.name = "indent", .fn = indent_cmd}, + {.name = "insert-tab", .fn = insert_tab}, {.name = "buffer-write-to-file", .fn = to_file_cmd}, {.name = "set-mark", .fn = set_mark_cmd}, {.name = "clear-mark", .fn = clear_mark_cmd}, diff --git a/src/main/completion.c b/src/main/completion.c new file mode 100644 index 0000000..7382805 --- /dev/null +++ b/src/main/completion.c @@ -0,0 +1,541 @@ +#define _DEFAULT_SOURCE +#include "completion.h" + +#include <dirent.h> +#include <errno.h> +#include <libgen.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "dged/binding.h" +#include "dged/buffer.h" +#include "dged/buffer_view.h" +#include "dged/buffers.h" +#include "dged/minibuffer.h" +#include "dged/path.h" +#include "dged/window.h" + +#include "bindings.h" + +struct active_completion_ctx { + struct completion_trigger trigger; + uint32_t trigger_current_nchars; + struct completion_provider *providers; + uint32_t nproviders; + insert_cb on_completion_inserted; +}; + +struct completion_state { + struct completion completions[50]; + uint32_t ncompletions; + uint32_t current_completion; + bool active; + buffer_keymap_id keymap_id; + bool keymap_active; + struct active_completion_ctx *ctx; +} g_state = {0}; + +static struct buffer *g_target_buffer = NULL; + +static void hide_completion(); + +static uint32_t complete_path(struct completion_context ctx, void *userdata); +static struct completion_provider g_path_provider = { + .name = "path", + .complete = complete_path, + .userdata = NULL, +}; + +static uint32_t complete_buffers(struct completion_context ctx, void *userdata); +static struct completion_provider g_buffer_provider = { + .name = "buffers", + .complete = complete_buffers, + .userdata = NULL, +}; + +struct completion_provider path_provider() { + return g_path_provider; +} + +struct completion_provider buffer_provider() { + return g_buffer_provider; +} + +struct active_completion { + struct buffer *buffer; + uint32_t insert_hook_id; + uint32_t remove_hook_id; +}; + +VEC(struct active_completion) g_active_completions; + +static int32_t goto_next_completion(struct command_ctx ctx, int argc, + const char *argv[]) { + if (g_state.current_completion < g_state.ncompletions - 1) { + ++g_state.current_completion; + } + + if (completion_active()) { + buffer_view_goto( + window_buffer_view(popup_window()), + ((struct location){.line = g_state.current_completion, .col = 0})); + } + + return 0; +} + +static int32_t goto_prev_completion(struct command_ctx ctx, int argc, + const char *argv[]) { + if (g_state.current_completion > 0) { + --g_state.current_completion; + } + + if (completion_active()) { + buffer_view_goto( + window_buffer_view(popup_window()), + ((struct location){.line = g_state.current_completion, .col = 0})); + } + + return 0; +} + +static int32_t insert_completion(struct command_ctx ctx, int argc, + const char *argv[]) { + // is it in the popup? + struct completion *comp = &g_state.completions[g_state.current_completion]; + bool done = comp->complete; + const char *ins = comp->insert; + size_t inslen = strlen(ins); + buffer_view_add(window_buffer_view(windows_get_active()), (uint8_t *)ins, + inslen); + + if (done) { + g_state.ctx->on_completion_inserted(); + abort_completion(); + } + + return 0; +} + +static void clear_completions() { + for (uint32_t ci = 0; ci < g_state.ncompletions; ++ci) { + free((void *)g_state.completions[ci].display); + free((void *)g_state.completions[ci].insert); + g_state.completions[ci].display = NULL; + g_state.completions[ci].insert = NULL; + g_state.completions[ci].complete = false; + } + g_state.ncompletions = 0; +} + +COMMAND_FN("next-completion", next_completion, goto_next_completion, NULL); +COMMAND_FN("prev-completion", prev_completion, goto_prev_completion, NULL); +COMMAND_FN("insert-completion", insert_completion, insert_completion, NULL); + +static void update_completions(struct buffer *buffer, + struct active_completion_ctx *ctx, + struct location location) { + clear_completions(); + for (uint32_t pi = 0; pi < ctx->nproviders; ++pi) { + struct completion_provider *provider = &ctx->providers[pi]; + + struct completion_context comp_ctx = (struct completion_context){ + .buffer = buffer, + .location = location, + .max_ncompletions = 50 - g_state.ncompletions, + .completions = g_state.completions, + }; + + g_state.ncompletions += provider->complete(comp_ctx, provider->userdata); + } + + window_set_buffer_e(popup_window(), g_target_buffer, false, false); + struct buffer_view *v = window_buffer_view(popup_window()); + + size_t max_width = 0; + uint32_t prev_selection = g_state.current_completion; + + buffer_clear(v->buffer); + buffer_view_goto(v, (struct location){.line = 0, .col = 0}); + if (g_state.ncompletions > 0) { + for (uint32_t compi = 0; compi < g_state.ncompletions; ++compi) { + const char *disp = g_state.completions[compi].display; + size_t width = strlen(disp); + if (width > max_width) { + max_width = width; + } + buffer_view_add(v, (uint8_t *)disp, width); + buffer_view_add(v, (uint8_t *)"\n", 1); + } + + // select the closest one to previous selection + g_state.current_completion = prev_selection < g_state.ncompletions + ? prev_selection + : g_state.ncompletions - 1; + + buffer_view_goto( + v, (struct location){.line = g_state.current_completion, .col = 0}); + + struct window *target_window = window_find_by_buffer(buffer); + struct window_position winpos = window_position(target_window); + struct buffer_view *view = window_buffer_view(target_window); + uint32_t height = g_state.ncompletions > 10 ? 10 : g_state.ncompletions; + windows_show_popup(winpos.y + location.line - height - 1, + winpos.x + view->fringe_width + location.col + 1, + max_width + 2, height); + + if (!g_state.keymap_active) { + struct keymap km = keymap_create("completion", 8); + struct binding comp_bindings[] = { + ANONYMOUS_BINDING(Ctrl, 'N', &next_completion_command), + ANONYMOUS_BINDING(Ctrl, 'P', &prev_completion_command), + ANONYMOUS_BINDING(ENTER, &insert_completion_command), + }; + keymap_bind_keys(&km, comp_bindings, + sizeof(comp_bindings) / sizeof(comp_bindings[0])); + g_state.keymap_id = buffer_add_keymap(buffer, km); + g_state.keymap_active = true; + } + } else { + hide_completion(); + } +} + +static void on_buffer_delete(struct buffer *buffer, struct region deleted, + void *userdata) { + struct active_completion_ctx *ctx = (struct active_completion_ctx *)userdata; + + if (g_state.active) { + update_completions(buffer, ctx, deleted.begin); + } +} + +static void on_buffer_insert(struct buffer *buffer, struct region inserted, + void *userdata) { + struct active_completion_ctx *ctx = (struct active_completion_ctx *)userdata; + + if (!g_state.active) { + uint32_t nchars = 0; + switch (ctx->trigger.kind) { + case CompletionTrigger_Input: + for (uint32_t line = inserted.begin.line; line <= inserted.end.line; + ++line) { + nchars += buffer_num_chars(buffer, line); + } + nchars -= + inserted.begin.col + + (buffer_num_chars(buffer, inserted.end.line) - inserted.end.col); + + ctx->trigger_current_nchars += nchars; + + if (ctx->trigger_current_nchars < ctx->trigger.nchars) { + return; + } + + ctx->trigger_current_nchars = 0; + break; + + case CompletionTrigger_Char: + // TODO + break; + } + + // activate completion + g_state.active = true; + g_state.ctx = ctx; + } + + update_completions(buffer, ctx, inserted.end); +} + +static void update_completion_buffer(struct buffer *buffer, void *userdata) { + buffer_add_text_property( + g_target_buffer, + (struct location){.line = g_state.current_completion, .col = 0}, + (struct location){ + .line = g_state.current_completion, + .col = buffer_num_chars(g_target_buffer, g_state.current_completion)}, + (struct text_property){.type = TextProperty_Colors, + .colors = (struct text_property_colors){ + .set_bg = false, + .bg = 0, + .set_fg = true, + .fg = 4, + }}); +} + +void init_completion(struct buffers *buffers) { + if (g_target_buffer == NULL) { + g_target_buffer = buffers_add(buffers, buffer_create("*completions*")); + buffer_add_update_hook(g_target_buffer, update_completion_buffer, NULL); + } + + g_buffer_provider.userdata = buffers; + VEC_INIT(&g_active_completions, 32); +} + +void enable_completion(struct buffer *source, struct completion_trigger trigger, + struct completion_provider *providers, + uint32_t nproviders, insert_cb on_completion_inserted) { + // check if we are already active + VEC_FOR_EACH(&g_active_completions, struct active_completion * c) { + if (c->buffer == source) { + disable_completion(source); + } + } + + struct active_completion_ctx *ctx = + calloc(1, sizeof(struct active_completion_ctx)); + ctx->trigger = trigger; + ctx->on_completion_inserted = on_completion_inserted; + ctx->nproviders = nproviders; + ctx->providers = calloc(nproviders, sizeof(struct completion_provider)); + memcpy(ctx->providers, providers, + sizeof(struct completion_provider) * nproviders); + + uint32_t insert_hook_id = + buffer_add_insert_hook(source, on_buffer_insert, ctx); + uint32_t remove_hook_id = + buffer_add_delete_hook(source, on_buffer_delete, ctx); + + VEC_PUSH(&g_active_completions, ((struct active_completion){ + .buffer = source, + .insert_hook_id = insert_hook_id, + .remove_hook_id = remove_hook_id, + })); +} + +static void hide_completion() { + windows_close_popup(); + if (g_state.active) { + buffer_remove_keymap(g_state.keymap_id); + g_state.keymap_active = false; + } +} + +void abort_completion() { + hide_completion(); + g_state.active = false; + clear_completions(); +} + +bool completion_active() { + return popup_window_visible() && + window_buffer(popup_window()) == g_target_buffer && g_state.active; +} + +static void cleanup_active_comp_ctx(void *userdata) { + struct active_completion_ctx *ctx = (struct active_completion_ctx *)userdata; + free(ctx->providers); + free(ctx); +} + +static void do_nothing(void *userdata) {} + +static void cleanup_active_completion(struct active_completion *comp) { + buffer_remove_delete_hook(comp->buffer, comp->remove_hook_id, do_nothing); + buffer_remove_insert_hook(comp->buffer, comp->insert_hook_id, + cleanup_active_comp_ctx); +} + +void disable_completion(struct buffer *buffer) { + VEC_FOR_EACH_INDEXED(&g_active_completions, struct active_completion * comp, + i) { + if (buffer == comp->buffer) { + VEC_SWAP(&g_active_completions, i, VEC_SIZE(&g_active_completions) - 1); + VEC_POP(&g_active_completions, struct active_completion removed); + cleanup_active_completion(&removed); + } + } +} + +void destroy_completion() { + // clean up any active completions we might have + VEC_FOR_EACH(&g_active_completions, struct active_completion * comp) { + cleanup_active_completion(comp); + } + VEC_DESTROY(&g_active_completions); +} + +static bool is_hidden(const char *filename) { + return filename[0] == '.' && filename[1] != '\0' && filename[1] != '.'; +} + +static int cmp_completions(const void *comp_a, const void *comp_b) { + struct completion *a = (struct completion *)comp_a; + struct completion *b = (struct completion *)comp_b; + return strcmp(a->display, b->display); +} + +static uint32_t complete_path(struct completion_context ctx, void *userdata) { + + // obtain path from the buffer + struct text_chunk txt = {0}; + uint32_t start_idx = 0; + if (ctx.buffer == minibuffer_buffer()) { + txt = minibuffer_content(); + } else { + txt = buffer_line(ctx.buffer, ctx.location.line); + uint32_t end_idx = text_col_to_byteindex( + ctx.buffer->text, ctx.location.line, ctx.location.col); + + for (uint32_t bytei = end_idx; bytei > 0; --bytei) { + if (txt.text[bytei] == ' ') { + start_idx = bytei + 1; + break; + } + } + + if (start_idx >= end_idx) { + return 0; + } + + txt.nbytes = end_idx - start_idx; + } + + char *path = calloc(txt.nbytes + 1, sizeof(uint8_t)); + memcpy(path, txt.text + start_idx, txt.nbytes); + + if (txt.allocated) { + free(txt.text); + } + + uint32_t n = 0; + char *p1 = to_abspath(path); + size_t len = strlen(p1); + char *p2 = strdup(p1); + + size_t inlen = strlen(path); + + if (len == 0) { + goto done; + } + + if (ctx.max_ncompletions == 0) { + goto done; + } + + const char *dir = p1; + const char *file = ""; + + // check the input path here since + // to_abspath removes trailing slashes + if (path[inlen - 1] != '/') { + dir = dirname(p1); + file = basename(p2); + } + + DIR *d = opendir(dir); + if (d == NULL) { + goto done; + } + + errno = 0; + while (n < ctx.max_ncompletions) { + struct dirent *de = readdir(d); + if (de == NULL && errno != 0) { + // skip the erroring entry + errno = 0; + continue; + } else if (de == NULL && errno == 0) { + break; + } + + switch (de->d_type) { + case DT_DIR: + case DT_REG: + case DT_LNK: + if (!is_hidden(de->d_name) && + (strncmp(file, de->d_name, strlen(file)) == 0 || strlen(file) == 0)) { + const char *disp = strdup(de->d_name); + ctx.completions[n] = (struct completion){ + .display = disp, + .insert = strdup(disp + strlen(file)), + .complete = de->d_type == DT_REG, + }; + ++n; + } + break; + } + } + + closedir(d); + +done: + free(path); + free(p1); + free(p2); + + qsort(ctx.completions, n, sizeof(struct completion), cmp_completions); + return n; +} + +struct buffer_match_ctx { + const char *needle; + struct completion *completions; + uint32_t max_ncompletions; + uint32_t ncompletions; +}; + +static void buffer_matches(struct buffer *buffer, void *userdata) { + struct buffer_match_ctx *ctx = (struct buffer_match_ctx *)userdata; + + if (strncmp(ctx->needle, buffer->name, strlen(ctx->needle)) == 0 && + ctx->ncompletions < ctx->max_ncompletions) { + ctx->completions[ctx->ncompletions] = (struct completion){ + .display = strdup(buffer->name), + .insert = strdup(buffer->name + strlen(ctx->needle)), + .complete = true, + }; + ++ctx->ncompletions; + } +} + +static uint32_t complete_buffers(struct completion_context ctx, + void *userdata) { + struct buffers *buffers = (struct buffers *)userdata; + if (buffers == NULL) { + return 0; + } + + struct text_chunk txt = {0}; + uint32_t start_idx = 0; + if (ctx.buffer == minibuffer_buffer()) { + txt = minibuffer_content(); + } else { + txt = buffer_line(ctx.buffer, ctx.location.line); + uint32_t end_idx = text_col_to_byteindex( + ctx.buffer->text, ctx.location.line, ctx.location.col); + for (uint32_t bytei = end_idx; bytei > 0; --bytei) { + if (txt.text[bytei] == ' ') { + start_idx = bytei + 1; + break; + } + } + + if (start_idx >= end_idx) { + return 0; + } + + txt.nbytes = end_idx - start_idx; + } + + char *needle = calloc(txt.nbytes + 1, sizeof(uint8_t)); + memcpy(needle, txt.text + start_idx, txt.nbytes); + + if (txt.allocated) { + free(txt.text); + } + + struct buffer_match_ctx match_ctx = (struct buffer_match_ctx){ + .needle = needle, + .max_ncompletions = ctx.max_ncompletions, + .completions = ctx.completions, + .ncompletions = 0, + }; + buffers_for_each(buffers, buffer_matches, &match_ctx); + + free(needle); + return match_ctx.ncompletions; +} diff --git a/src/main/completion.h b/src/main/completion.h new file mode 100644 index 0000000..751d152 --- /dev/null +++ b/src/main/completion.h @@ -0,0 +1,83 @@ +#ifndef _COMPLETION_H +#define _COMPLETION_H + +#include "dged/location.h" + +struct buffer; +struct buffers; + +struct completion { + const char *display; + const char *insert; + bool complete; +}; + +struct completion_context { + struct buffer *buffer; + const struct location location; + const uint32_t max_ncompletions; + struct completion *completions; +}; + +typedef uint32_t (*completion_fn)(struct completion_context ctx, + void *userdata); + +struct completion_provider { + char name[16]; + completion_fn complete; + void *userdata; +}; + +enum completion_trigger_kind { + CompletionTrigger_Input = 0, + CompletionTrigger_Char = 1, +}; + +struct completion_trigger { + enum completion_trigger_kind kind; + union { + uint32_t c; + uint32_t nchars; + }; +}; + +void init_completion(struct buffers *buffers); +void destroy_completion(); + +typedef void (*insert_cb)(); + +/** + * Enable completions in the buffer @ref source. + * + * @param source [in] The buffer to provide completions for. + * @param trigger [in] The completion trigger to use for this completion. + * @param providers [in] The completion providers to use. + * @param nproviders [in] The number of providers in @ref providers. + */ +void enable_completion(struct buffer *source, struct completion_trigger trigger, + struct completion_provider *providers, + uint32_t nproviders, insert_cb on_completion_inserted); + +struct completion_provider path_provider(); +struct completion_provider buffer_provider(); + +/** + * Abort any active completion. + */ +void abort_completion(); + +/** + * Is a completion currently showing? + * + * @returns True if the completion window is showing completions. + */ +bool completion_active(); + +/** + * Disable completion for @ref buffer. + * + * @param buffer [in] Buffer to disable completions for. + */ +void disable_completion(struct buffer *buffer); + +#endif diff --git a/src/main/main.c b/src/main/main.c index fd69cff..1bb3003 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -22,6 +22,7 @@ #include "bindings.h" #include "cmds.h" +#include "completion.h" static struct frame_allocator frame_allocator; @@ -103,7 +104,7 @@ void update_file_watches(struct reactor *reactor) { // find the buffer we need to reload VEC_FOR_EACH(&g_watched_files, struct watched_file * w) { if (w->watch_id == ev.id) { - if (ev.mask & LastEvent != 0) { + if ((ev.mask & LastEvent) != 0) { w->watch_id = -1; continue; } @@ -206,16 +207,8 @@ int main(int argc, char *argv[]) { display_clear(display); signal(SIGWINCH, resized); - register_global_commands(&commands, terminate); - register_buffer_commands(&commands); - register_window_commands(&commands); - register_settings_commands(&commands); - struct keyboard kbd = keyboard_create(reactor); - struct keymap *current_keymap = NULL; - struct keymap *global_keymap = register_bindings(); - VEC_INIT(&g_watched_files, 32); struct buffers buflist = {0}; @@ -234,7 +227,6 @@ int main(int argc, char *argv[]) { struct buffer *ib = buffers_add(&buflist, initial_buffer); struct buffer minibuffer = buffer_create("minibuffer"); minibuffer_init(&minibuffer); - reset_minibuffer_keys(&minibuffer); windows_init(display_height(display), display_width(display), ib, &minibuffer); @@ -249,6 +241,16 @@ int main(int argc, char *argv[]) { buffer_view_goto(window_buffer_view(active), to); } + register_global_commands(&commands, terminate); + register_buffer_commands(&commands); + register_window_commands(&commands); + register_settings_commands(&commands); + + struct keymap *current_keymap = NULL; + init_bindings(); + + init_completion(&buflist); + DECLARE_TIMER(buffer); DECLARE_TIMER(display); DECLARE_TIMER(keyboard); @@ -277,7 +279,7 @@ int main(int argc, char *argv[]) { display_begin_render(display); windows_render(display); struct buffer_view *view = window_buffer_view(active_window); - struct location cursor = buffer_view_dot_to_relative(view); + struct location cursor = buffer_view_dot_to_visual(view); struct window_position winpos = window_position(active_window); display_move_cursor(display, winpos.y + cursor.line, winpos.x + cursor.col); display_end_render(display); @@ -302,11 +304,14 @@ int main(int argc, char *argv[]) { if (current_keymap != NULL) { res = lookup_key(current_keymap, 1, k, &commands); } else { - // check the global keymap first, then the buffer one - res = lookup_key(global_keymap, 1, k, &commands); - if (!res.found) { - res = lookup_key(buffer_keymap(window_buffer(active_window)), 1, k, - &commands); + struct keymap *buffer_maps[128]; + uint32_t nkeymaps = + buffer_keymaps(window_buffer(active_window), buffer_maps, 128); + for (uint32_t kmi = nkeymaps; kmi > 0; --kmi) { + res = lookup_key(buffer_maps[kmi - 1], 1, k, &commands); + if (res.found) { + break; + } } } @@ -372,13 +377,14 @@ int main(int argc, char *argv[]) { frame_allocator_clear(&frame_allocator); } + destroy_completion(); windows_destroy(); minibuffer_destroy(); buffer_destroy(&minibuffer); buffers_destroy(&buflist); display_clear(display); display_destroy(display); - destroy_keymaps(); + destroy_bindings(); command_registry_destroy(&commands); reactor_destroy(reactor); frame_allocator_destroy(&frame_allocator); diff --git a/src/main/search-replace.c b/src/main/search-replace.c index c1b812f..6467ee1 100644 --- a/src/main/search-replace.c +++ b/src/main/search-replace.c @@ -17,10 +17,11 @@ static struct replace { struct region *matches; uint32_t nmatches; uint32_t current_match; + buffer_keymap_id keymap_id; } g_current_replace = {0}; void abort_replace() { - reset_minibuffer_keys(minibuffer_buffer()); + buffer_remove_keymap(g_current_replace.keymap_id); free(g_current_replace.matches); free(g_current_replace.replace); g_current_replace.matches = NULL; @@ -84,7 +85,8 @@ static int32_t replace_next(struct command_ctx ctx, int argc, .col = m->begin.col}); buffer_view_goto(buffer_view, (struct location){.line = m->end.line, .col = m->end.col + 1}); - buffer_view_add(buffer_view, state->replace, strlen(state->replace)); + buffer_view_add(buffer_view, (uint8_t *)state->replace, + strlen(state->replace)); ++state->current_match; @@ -193,14 +195,16 @@ static int32_t replace(struct command_ctx ctx, int argc, const char *argv[]) { ANONYMOUS_BINDING(None, 'n', &skip_next_command), ANONYMOUS_BINDING(Ctrl, 'M', &replace_next_command), }; - buffer_bind_keys(minibuffer_buffer(), bindings, - sizeof(bindings) / sizeof(bindings[0])); + struct keymap km = keymap_create("replace", 8); + keymap_bind_keys(&km, bindings, sizeof(bindings) / sizeof(bindings[0])); + g_current_replace.keymap_id = buffer_add_keymap(minibuffer_buffer(), km); return minibuffer_prompt(ctx, "replace? [yn] "); } static char *g_last_search = NULL; static bool g_last_search_interactive = false; +static buffer_keymap_id g_search_keymap; const char *search_prompt(bool reverse) { const char *txt = "search (down): "; @@ -303,7 +307,7 @@ int32_t search_interactive(struct command_ctx ctx, int argc, } else { struct text_chunk content = minibuffer_content(); char *p = malloc(content.nbytes + 1); - strncpy(p, content.text, content.nbytes); + strncpy(p, (const char *)content.text, content.nbytes); p[content.nbytes] = '\0'; pattern = p; } @@ -334,12 +338,13 @@ int32_t find(struct command_ctx ctx, int argc, const char *argv[]) { ANONYMOUS_BINDING(Ctrl, 'S', &search_forward_command), ANONYMOUS_BINDING(Ctrl, 'R', &search_backward_command), }; - buffer_bind_keys(minibuffer_buffer(), bindings, - sizeof(bindings) / sizeof(bindings[0])); + struct keymap m = keymap_create("search", 8); + keymap_bind_keys(&m, bindings, sizeof(bindings) / sizeof(bindings[0])); + g_search_keymap = buffer_add_keymap(minibuffer_buffer(), m); return minibuffer_prompt(ctx, search_prompt(reverse)); } - reset_minibuffer_keys(minibuffer_buffer()); + buffer_remove_keymap(g_search_keymap); if (g_last_search_interactive) { g_last_search_interactive = false; return 0; @@ -347,7 +352,7 @@ int32_t find(struct command_ctx ctx, int argc, const char *argv[]) { struct text_chunk content = minibuffer_content(); char *pattern = malloc(content.nbytes + 1); - strncpy(pattern, content.text, content.nbytes); + strncpy(pattern, (const char *)content.text, content.nbytes); pattern[content.nbytes] = '\0'; do_search(window_buffer_view(ctx.active_window), pattern, reverse); diff --git a/test/minibuffer.c b/test/minibuffer.c index 7ac6c9b..93bb90f 100644 --- a/test/minibuffer.c +++ b/test/minibuffer.c @@ -23,12 +23,14 @@ void init() { } minibuffer_init(&b); + windows_init(100, 100, &b, &b); } void destroy() { if (b.name != NULL) { buffer_destroy(&b); settings_destroy(); + windows_destroy(); } } |
