diff options
| author | Albert Cervin <albert@acervin.com> | 2023-04-06 23:23:46 +0200 |
|---|---|---|
| committer | Albert Cervin <albert@acervin.com> | 2023-05-01 22:19:14 +0200 |
| commit | a123725a12e948d78badb2cb686d38548f1c633b (patch) | |
| tree | c92c46134ef5536fbbf3bf08983c4f0dea1aaf58 /src | |
| parent | b5ed4cf757afc50afb6ac499eee7b87a2648fa4c (diff) | |
| download | dged-a123725a12e948d78badb2cb686d38548f1c633b.tar.gz dged-a123725a12e948d78badb2cb686d38548f1c633b.tar.xz dged-a123725a12e948d78badb2cb686d38548f1c633b.zip | |
Implement window handling
Also implement searching.
fix undo boundaries
when it checked for other save point, it used && instead of == which
caused it to overwrite other types.
Fix bytes vs chars bug in text_get_region
Diffstat (limited to 'src')
| -rw-r--r-- | src/command.c | 175 | ||||
| -rw-r--r-- | src/dged/allocator.c (renamed from src/allocator.c) | 0 | ||||
| -rw-r--r-- | src/dged/allocator.h (renamed from src/allocator.h) | 0 | ||||
| -rw-r--r-- | src/dged/binding.c (renamed from src/binding.c) | 4 | ||||
| -rw-r--r-- | src/dged/binding.h (renamed from src/binding.h) | 0 | ||||
| -rw-r--r-- | src/dged/btree.h | 113 | ||||
| -rw-r--r-- | src/dged/buffer.c (renamed from src/buffer.c) | 725 | ||||
| -rw-r--r-- | src/dged/buffer.h (renamed from src/buffer.h) | 140 | ||||
| -rw-r--r-- | src/dged/buffers.c (renamed from src/buffers.c) | 0 | ||||
| -rw-r--r-- | src/dged/buffers.h (renamed from src/buffers.h) | 0 | ||||
| -rw-r--r-- | src/dged/command.c | 79 | ||||
| -rw-r--r-- | src/dged/command.h (renamed from src/command.h) | 26 | ||||
| -rw-r--r-- | src/dged/display.c (renamed from src/display.c) | 4 | ||||
| -rw-r--r-- | src/dged/display.h (renamed from src/display.h) | 0 | ||||
| -rw-r--r-- | src/dged/hash.h (renamed from src/hash.h) | 0 | ||||
| -rw-r--r-- | src/dged/hashmap.h (renamed from src/hashmap.h) | 0 | ||||
| -rw-r--r-- | src/dged/keyboard.c (renamed from src/keyboard.c) | 2 | ||||
| -rw-r--r-- | src/dged/keyboard.h (renamed from src/keyboard.h) | 4 | ||||
| -rw-r--r-- | src/dged/lang.c (renamed from src/lang.c) | 0 | ||||
| -rw-r--r-- | src/dged/lang.h (renamed from src/lang.h) | 0 | ||||
| -rw-r--r-- | src/dged/minibuffer.c (renamed from src/minibuffer.c) | 99 | ||||
| -rw-r--r-- | src/dged/minibuffer.h (renamed from src/minibuffer.h) | 23 | ||||
| -rw-r--r-- | src/dged/reactor-epoll.c (renamed from src/reactor-epoll.c) | 0 | ||||
| -rw-r--r-- | src/dged/reactor.h (renamed from src/reactor.h) | 0 | ||||
| -rw-r--r-- | src/dged/settings.c (renamed from src/settings.c) | 72 | ||||
| -rw-r--r-- | src/dged/settings.h (renamed from src/settings.h) | 20 | ||||
| -rw-r--r-- | src/dged/text.c (renamed from src/text.c) | 6 | ||||
| -rw-r--r-- | src/dged/text.h (renamed from src/text.h) | 0 | ||||
| -rw-r--r-- | src/dged/undo.c (renamed from src/undo.c) | 10 | ||||
| -rw-r--r-- | src/dged/undo.h (renamed from src/undo.h) | 0 | ||||
| -rw-r--r-- | src/dged/utf8.c (renamed from src/utf8.c) | 0 | ||||
| -rw-r--r-- | src/dged/utf8.h (renamed from src/utf8.h) | 0 | ||||
| -rw-r--r-- | src/dged/vec.h (renamed from src/vec.h) | 2 | ||||
| -rw-r--r-- | src/dged/window.c | 445 | ||||
| -rw-r--r-- | src/dged/window.h | 48 | ||||
| -rw-r--r-- | src/main.c | 364 | ||||
| -rw-r--r-- | src/main/bindings.c | 207 | ||||
| -rw-r--r-- | src/main/bindings.h | 15 | ||||
| -rw-r--r-- | src/main/cmds.c | 519 | ||||
| -rw-r--r-- | src/main/cmds.h | 10 | ||||
| -rw-r--r-- | src/main/main.c | 294 | ||||
| -rw-r--r-- | src/window.c | 14 | ||||
| -rw-r--r-- | src/window.h | 18 |
43 files changed, 2294 insertions, 1144 deletions
diff --git a/src/command.c b/src/command.c deleted file mode 100644 index 65543a0..0000000 --- a/src/command.c +++ /dev/null @@ -1,175 +0,0 @@ -#include "command.h" -#include "buffer.h" -#include "buffers.h" -#include "hash.h" -#include "hashmap.h" -#include "minibuffer.h" - -#include <errno.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> - -struct commands command_registry_create(uint32_t capacity) { - - struct commands cmds = {0}; - HASHMAP_INIT(&cmds.commands, capacity, hash_name); - return cmds; -} - -void command_registry_destroy(struct commands *commands) { - HASHMAP_DESTROY(&commands->commands); -} - -uint32_t register_command(struct commands *commands, struct command command) { - uint32_t hash = 0; - HASHMAP_INSERT(&commands->commands, struct command_entry, command.name, - command, hash); - return hash; -} - -void register_commands(struct commands *command_list, struct command *commands, - uint32_t ncommands) { - for (uint32_t ci = 0; ci < ncommands; ++ci) { - register_command(command_list, commands[ci]); - } -} - -struct command *lookup_command(struct commands *command_list, - const char *name) { - HASHMAP_GET(&command_list->commands, struct command_entry, name, - struct command * command); - return command; -} - -struct command *lookup_command_by_hash(struct commands *commands, - uint32_t hash) { - HASHMAP_GET_BY_HASH(&commands->commands, struct command_entry, hash, - struct command * command); - return command; -} - -int32_t execute_command(struct command *command, struct commands *commands, - struct window *active_window, struct buffers *buffers, - int argc, const char *argv[]) { - - return command->fn( - (struct command_ctx){ - .buffers = buffers, - .active_window = active_window, - .userdata = command->userdata, - .commands = commands, - .self = command, - .saved_argv = {0}, - .saved_argc = 0, - }, - argc, argv); -} - -void command_ctx_push_arg(struct command_ctx *ctx, const char *argv) { - if (ctx->saved_argc < 64) { - ctx->saved_argv[ctx->saved_argc] = strdup(argv); - ++ctx->saved_argc; - } -} - -void command_ctx_free(struct command_ctx *ctx) { - for (uint32_t i = 0; i < ctx->saved_argc; ++i) { - free((char *)ctx->saved_argv[i]); - } - - ctx->saved_argc = 0; -} - -int32_t find_file(struct command_ctx ctx, int argc, const char *argv[]) { - const char *pth = NULL; - if (argc == 0) { - return minibuffer_prompt(ctx, "find file: "); - } - - pth = argv[0]; - struct stat sb; - 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; - } - - window_set_buffer(ctx.active_window, - buffers_add(ctx.buffers, buffer_from_file((char *)pth))); - minibuffer_echo_timeout(4, "buffer \"%s\" loaded", - ctx.active_window->buffer->name); - - return 0; -} - -int32_t write_file(struct command_ctx ctx, int argc, const char *argv[]) { - const char *pth = NULL; - if (argc == 0) { - return minibuffer_prompt(ctx, "write to file: "); - } - - pth = argv[0]; - buffer_write_to(ctx.active_window->buffer, pth); - - return 0; -} - -int32_t run_interactive(struct command_ctx ctx, int argc, const char *argv[]) { - if (argc == 0) { - return minibuffer_prompt(ctx, "execute: "); - } - - struct command *cmd = lookup_command(ctx.commands, argv[0]); - if (cmd != NULL) { - return execute_command(cmd, ctx.commands, ctx.active_window, ctx.buffers, - argc - 1, argv + 1); - } else { - minibuffer_echo_timeout(4, "command %s not found", argv[0]); - return 11; - } -} - -int32_t do_switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) { - const char *bufname = argv[0]; - if (argc == 0) { - // switch back to prev buffer - if (ctx.active_window->prev_buffer != NULL) { - bufname = ctx.active_window->prev_buffer->name; - } else { - return 0; - } - } - - struct buffer *buf = buffers_find(ctx.buffers, bufname); - - if (buf == NULL) { - minibuffer_echo_timeout(4, "buffer %s not found", bufname); - return 1; - } else { - window_set_buffer(ctx.active_window, buf); - return 0; - } -} - -static struct command do_switch_buffer_cmd = {.fn = do_switch_buffer, - .name = "do-switch-buffer"}; - -int32_t switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) { - if (argc == 0) { - ctx.self = &do_switch_buffer_cmd; - if (ctx.active_window->prev_buffer != NULL) { - return minibuffer_prompt( - ctx, "buffer (default %s): ", ctx.active_window->prev_buffer->name); - } else { - return minibuffer_prompt(ctx, "buffer: "); - } - } - - return execute_command(&do_switch_buffer_cmd, ctx.commands, ctx.active_window, - ctx.buffers, argc, argv); -} diff --git a/src/allocator.c b/src/dged/allocator.c index 308b97c..308b97c 100644 --- a/src/allocator.c +++ b/src/dged/allocator.c diff --git a/src/allocator.h b/src/dged/allocator.h index 49e3aec..49e3aec 100644 --- a/src/allocator.h +++ b/src/dged/allocator.h diff --git a/src/binding.c b/src/dged/binding.c index a0946ee..5111548 100644 --- a/src/binding.c +++ b/src/dged/binding.c @@ -42,8 +42,8 @@ struct lookup_result lookup_key(struct keymap *keymaps, uint32_t nkeymaps, --kmi; struct keymap *keymap = &keymaps[kmi]; - for (uint32_t bi = 0; bi < keymap->nbindings; ++bi) { - struct binding *binding = &keymap->bindings[bi]; + for (uint32_t bi = keymap->nbindings; bi > 0; --bi) { + struct binding *binding = &keymap->bindings[bi - 1]; if (key_equal(key, &binding->key)) { switch (binding->type) { case BindingType_Command: { diff --git a/src/binding.h b/src/dged/binding.h index f2a531d..f2a531d 100644 --- a/src/binding.h +++ b/src/dged/binding.h diff --git a/src/dged/btree.h b/src/dged/btree.h new file mode 100644 index 0000000..8743b32 --- /dev/null +++ b/src/dged/btree.h @@ -0,0 +1,113 @@ +#ifndef _BTREE_H +#define _BTREE_H + +#include "vec.h" + +#include <stdlib.h> + +#define BINTREE_ENTRY_TYPE(name, entry) \ + struct name { \ + struct name *parent; \ + struct name *left; \ + struct name *right; \ + entry value; \ + } + +#define BINTREE(entry) \ + struct { \ + struct entry *root; \ + } + +#define BINTREE_INIT(tree) ((tree)->root = NULL) +#define BINTREE_DESTROY(tree, entry_type) BINTREE_INIT(tree) + +#define BINTREE_ROOT(tree) (tree)->root + +#define BINTREE_LEFT(node) (node)->left +#define BINTREE_RIGHT(node) (node)->right +#define BINTREE_PARENT(node) (node)->parent +#define BINTREE_VALUE(node) (node)->value +#define BINTREE_HAS_PARENT(node) ((node)->parent != NULL) +#define BINTREE_HAS_LEFT(node) ((node)->left != NULL) +#define BINTREE_HAS_RIGHT(node) ((node)->right != NULL) + +#define BINTREE_FREE_NODE(node) free(node) +#define BINTREE_FREE_NODES(root, entry_type) \ + { \ + BINTREE_FIRST(root); \ + VEC(struct entry_type *) to_delete; \ + VEC_INIT(&to_delete, 10); \ + while (root != NULL) { \ + VEC_PUSH(&to_delete, root); \ + BINTREE_NEXT(root); \ + } \ + VEC_FOR_EACH(&to_delete, struct entry_type **e) { BINTREE_FREE_NODE(*e); } \ + VEC_DESTROY(&to_delete); \ + } + +#define BINTREE_FIRST(res) \ + if (res == NULL) { \ + res = NULL; \ + } else { \ + while (BINTREE_HAS_LEFT(res)) { \ + res = BINTREE_LEFT(res); \ + } \ + } + +#define BINTREE_NEXT(res) \ + if (res == NULL) { \ + res = NULL; \ + } else { \ + if (BINTREE_HAS_RIGHT(res)) { \ + res = BINTREE_RIGHT(res); \ + BINTREE_FIRST(res) \ + } else { \ + while (BINTREE_HAS_PARENT(res) && \ + res == BINTREE_RIGHT(BINTREE_PARENT(res))) \ + res = BINTREE_PARENT(res); \ + res = BINTREE_PARENT(res); \ + } \ + } + +#define BINTREE_INSERT(parent, entry) \ + if (parent != NULL) { \ + if (!BINTREE_HAS_LEFT(parent)) { \ + BINTREE_LEFT(parent) = calloc(1, sizeof(*(parent))); \ + BINTREE_PARENT(BINTREE_LEFT(parent)) = parent; \ + BINTREE_VALUE(BINTREE_LEFT(parent)) = entry; \ + } else { \ + BINTREE_RIGHT(parent) = calloc(1, sizeof(*(parent))); \ + BINTREE_PARENT(BINTREE_RIGHT(parent)) = parent; \ + BINTREE_VALUE(BINTREE_RIGHT(parent)) = entry; \ + } \ + } + +#define BINTREE_REMOVE(node) \ + if (BINTREE_HAS_PARENT(node)) { \ + if (BINTREE_LEFT(BINTREE_PARENT(node)) == node) { \ + BINTREE_LEFT(BINTREE_PARENT(node)) = NULL; \ + } else { \ + BINTREE_RIGHT(BINTREE_PARENT(node)) = NULL; \ + } \ + BINTREE_PARENT(node) = NULL; \ + } + +#define BINTREE_SET_ROOT(tree, value) \ + (tree)->root = calloc(1, sizeof(*(tree)->root)); \ + BINTREE_VALUE((tree)->root) = value; + +#define BINTREE_FIND(tree, needle, res) \ + { \ + res = BINTREE_ROOT(tree); \ + BINTREE_FIRST(res); \ + bool found = false; \ + while (res != NULL) { \ + if (BINTREE_VALUE(res) == needle) { \ + found = true; \ + break; \ + } \ + BINTREE_NEXT(res); \ + } \ + res = found ? res : NULL; \ + } +#endif diff --git a/src/buffer.c b/src/dged/buffer.c index 23a8ab1..25a8a4a 100644 --- a/src/buffer.c +++ b/src/dged/buffer.c @@ -1,5 +1,7 @@ #include "buffer.h" #include "binding.h" +#include "bits/stdint-uintn.h" +#include "dged/vec.h" #include "display.h" #include "errno.h" #include "lang.h" @@ -20,6 +22,7 @@ struct modeline { uint8_t *buffer; + uint32_t sz; }; #define KILL_RING_SZ 64 @@ -35,89 +38,100 @@ static struct kill_ring { .paste_idx = 0, .paste_up_to_date = false}; -struct update_hook_result buffer_linenum_hook(struct buffer *buffer, +#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; + +struct update_hook_result buffer_linenum_hook(struct buffer_view *view, struct command_list *commands, uint32_t width, uint32_t height, uint64_t frame_time, void *userdata); -struct update_hook_result buffer_modeline_hook(struct buffer *buffer, +struct update_hook_result buffer_modeline_hook(struct buffer_view *view, struct command_list *commands, uint32_t width, uint32_t height, uint64_t frame_time, void *userdata); -struct buffer buffer_create(char *name, bool modeline) { - struct buffer b = (struct buffer){ - .filename = NULL, - .name = strdup(name), - .text = text_create(10), +struct buffer_view buffer_view_create(struct buffer *buffer, bool modeline, + bool line_numbers) { + struct buffer_view view = { .dot = {0}, .mark = {0}, .mark_set = false, - .modified = false, - .readonly = false, - .modeline = NULL, - .keymaps = calloc(10, sizeof(struct keymap)), - .nkeymaps = 1, .scroll = {0}, - .update_hooks = {0}, - .nkeymaps_max = 10, - .lang = lang_from_id("fnd"), + .buffer = buffer, + .modeline = NULL, + .line_numbers = line_numbers, }; - undo_init(&b.undo, 100); - - b.keymaps[0] = keymap_create("buffer-default", 128); - struct binding bindings[] = { - BINDING(Ctrl, 'B', "backward-char"), - BINDING(LEFT, "backward-char"), - BINDING(Ctrl, 'F', "forward-char"), - BINDING(RIGHT, "forward-char"), - - BINDING(Ctrl, 'P', "backward-line"), - BINDING(UP, "backward-line"), - BINDING(Ctrl, 'N', "forward-line"), - BINDING(DOWN, "forward-line"), + if (modeline) { + view.modeline = calloc(1, sizeof(struct modeline)); + view.modeline->buffer = malloc(1024); + view.modeline->sz = 1024; + view.modeline->buffer[0] = '\0'; + } - BINDING(Meta, 'f', "forward-word"), - BINDING(Meta, 'b', "backward-word"), + return view; +} - BINDING(Ctrl, 'A', "beginning-of-line"), - BINDING(Ctrl, 'E', "end-of-line"), +struct buffer_view buffer_view_clone(struct buffer_view *view) { + struct buffer_view c = { + .dot = view->dot, + .mark = view->mark, + .mark_set = view->mark_set, + .scroll = view->scroll, + .buffer = view->buffer, + .modeline = NULL, + .line_numbers = view->line_numbers, + }; - BINDING(Meta, '<', "goto-beginning"), - BINDING(Meta, '>', "goto-end"), + if (view->modeline) { + c.modeline = calloc(1, sizeof(struct modeline)); + c.modeline->buffer = malloc(view->modeline->sz); + memcpy(c.modeline->buffer, view->modeline->buffer, view->modeline->sz); + } - BINDING(ENTER, "newline"), - BINDING(TAB, "indent"), + return c; +} - BINDING(Ctrl, 'K', "kill-line"), - BINDING(DELETE, "delete-char"), - BINDING(Ctrl, 'D', "delete-char"), - BINDING(BACKSPACE, "backward-delete-char"), +void buffer_view_destroy(struct buffer_view *view) { + if (view->modeline != NULL) { + free(view->modeline->buffer); + free(view->modeline); + } +} - BINDING(Ctrl, '@', "set-mark"), +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; + } - BINDING(Ctrl, 'W', "cut"), - BINDING(Ctrl, 'Y', "paste"), - BINDING(Meta, 'y', "paste-older"), - BINDING(Meta, 'w', "copy"), + return g_num_create_hooks - 1; +} - BINDING(Ctrl, '_', "undo"), +struct buffer buffer_create(char *name) { + struct buffer b = (struct buffer){ + .filename = NULL, + .name = strdup(name), + .text = text_create(10), + .modified = false, + .readonly = false, + .lang = lang_from_id("fnd"), }; - keymap_bind_keys(&b.keymaps[0], bindings, - sizeof(bindings) / sizeof(bindings[0])); - if (modeline) { - b.modeline = calloc(1, sizeof(struct modeline)); - b.modeline->buffer = malloc(1024); - b.modeline->buffer[0] = '\0'; - buffer_add_update_hook(&b, buffer_modeline_hook, b.modeline); - } + undo_init(&b.undo, 100); - if (modeline) { - buffer_add_update_hook(&b, buffer_linenum_hook, NULL); + for (uint32_t hooki = 0; hooki < g_num_create_hooks; ++hooki) { + g_create_hooks[hooki].callback(&b, g_create_hooks[hooki].userdata); } return b; @@ -133,58 +147,15 @@ void buffer_destroy(struct buffer *buffer) { free(buffer->filename); buffer->filename = NULL; - for (uint32_t keymapi = 0; keymapi < buffer->nkeymaps; ++keymapi) { - keymap_destroy(&buffer->keymaps[keymapi]); - } - free(buffer->keymaps); - buffer->keymaps = NULL; - - if (buffer->modeline != NULL) { - free(buffer->modeline->buffer); - free(buffer->modeline); - } - undo_destroy(&buffer->undo); } -void buffer_clear(struct buffer *buffer) { - text_clear(buffer->text); - buffer->dot.col = buffer->dot.line = 0; +void buffer_clear(struct buffer_view *view) { + text_clear(view->buffer->text); + view->dot.col = view->dot.line = 0; } -#define BUFFER_WRAPCMD(fn) \ - static int32_t fn##_cmd(struct command_ctx ctx, int argc, \ - const char *argv[]) { \ - fn(ctx.active_window->buffer); \ - return 0; \ - } - -// commands -BUFFER_WRAPCMD(buffer_kill_line); -BUFFER_WRAPCMD(buffer_forward_delete_char); -BUFFER_WRAPCMD(buffer_backward_delete_char); -BUFFER_WRAPCMD(buffer_backward_char); -BUFFER_WRAPCMD(buffer_backward_word); -BUFFER_WRAPCMD(buffer_forward_char); -BUFFER_WRAPCMD(buffer_forward_word); -BUFFER_WRAPCMD(buffer_backward_line); -BUFFER_WRAPCMD(buffer_forward_line); -BUFFER_WRAPCMD(buffer_end_of_line); -BUFFER_WRAPCMD(buffer_beginning_of_line); -BUFFER_WRAPCMD(buffer_newline); -BUFFER_WRAPCMD(buffer_indent); -BUFFER_WRAPCMD(buffer_to_file); -BUFFER_WRAPCMD(buffer_set_mark); -BUFFER_WRAPCMD(buffer_clear_mark); -BUFFER_WRAPCMD(buffer_copy); -BUFFER_WRAPCMD(buffer_cut); -BUFFER_WRAPCMD(buffer_paste); -BUFFER_WRAPCMD(buffer_paste_older); -BUFFER_WRAPCMD(buffer_goto_beginning); -BUFFER_WRAPCMD(buffer_goto_end); -BUFFER_WRAPCMD(buffer_undo); - -void buffer_static_init(struct commands *commands) { +void buffer_static_init() { settings_register_setting( "editor.tab-width", (struct setting_value){.type = Setting_Number, .number_value = 4}); @@ -192,35 +163,6 @@ void buffer_static_init(struct commands *commands) { settings_register_setting( "editor.show-whitespace", (struct setting_value){.type = Setting_Bool, .bool_value = true}); - - static struct command buffer_commands[] = { - {.name = "kill-line", .fn = buffer_kill_line_cmd}, - {.name = "delete-char", .fn = buffer_forward_delete_char_cmd}, - {.name = "backward-delete-char", .fn = buffer_backward_delete_char_cmd}, - {.name = "backward-char", .fn = buffer_backward_char_cmd}, - {.name = "backward-word", .fn = buffer_backward_word_cmd}, - {.name = "forward-char", .fn = buffer_forward_char_cmd}, - {.name = "forward-word", .fn = buffer_forward_word_cmd}, - {.name = "backward-line", .fn = buffer_backward_line_cmd}, - {.name = "forward-line", .fn = buffer_forward_line_cmd}, - {.name = "end-of-line", .fn = buffer_end_of_line_cmd}, - {.name = "beginning-of-line", .fn = buffer_beginning_of_line_cmd}, - {.name = "newline", .fn = buffer_newline_cmd}, - {.name = "indent", .fn = buffer_indent_cmd}, - {.name = "buffer-write-to-file", .fn = buffer_to_file_cmd}, - {.name = "set-mark", .fn = buffer_set_mark_cmd}, - {.name = "clear-mark", .fn = buffer_clear_mark_cmd}, - {.name = "copy", .fn = buffer_copy_cmd}, - {.name = "cut", .fn = buffer_cut_cmd}, - {.name = "paste", .fn = buffer_paste_cmd}, - {.name = "paste-older", .fn = buffer_paste_older_cmd}, - {.name = "goto-beginning", .fn = buffer_goto_beginning_cmd}, - {.name = "goto-end", .fn = buffer_goto_end_cmd}, - {.name = "undo", .fn = buffer_undo_cmd}, - }; - - register_commands(commands, buffer_commands, - sizeof(buffer_commands) / sizeof(buffer_commands[0])); } void buffer_static_teardown() { @@ -264,77 +206,62 @@ void delete_with_undo(struct buffer *buffer, struct buffer_location start, buffer->modified = true; } -uint32_t buffer_keymaps(struct buffer *buffer, struct keymap **keymaps_out) { - *keymaps_out = buffer->keymaps; - return buffer->nkeymaps; +void buffer_goto_beginning(struct buffer_view *view) { + view->dot.col = 0; + view->dot.line = 0; } -void buffer_add_keymap(struct buffer *buffer, struct keymap *keymap) { - if (buffer->nkeymaps == buffer->nkeymaps_max) { - buffer->nkeymaps_max *= 2; - buffer->keymaps = - realloc(buffer->keymaps, sizeof(struct keymap) * buffer->nkeymaps_max); - } - buffer->keymaps[buffer->nkeymaps] = *keymap; - ++buffer->nkeymaps; -} - -void buffer_goto_beginning(struct buffer *buffer) { - buffer->dot.col = 0; - buffer->dot.line = 0; +void buffer_goto_end(struct buffer_view *view) { + view->dot.line = text_num_lines(view->buffer->text); + view->dot.col = 0; } -void buffer_goto_end(struct buffer *buffer) { - buffer->dot.line = text_num_lines(buffer->text); - buffer->dot.col = 0; -} - -bool movev(struct buffer *buffer, int rowdelta) { - int64_t new_line = (int64_t)buffer->dot.line + rowdelta; +bool movev(struct buffer_view *view, int rowdelta) { + int64_t new_line = (int64_t)view->dot.line + rowdelta; if (new_line < 0) { - buffer->dot.line = 0; + view->dot.line = 0; return false; - } else if (new_line > text_num_lines(buffer->text)) { - buffer->dot.line = text_num_lines(buffer->text); + } else if (new_line > text_num_lines(view->buffer->text)) { + view->dot.line = text_num_lines(view->buffer->text); return false; } else { - buffer->dot.line = (uint32_t)new_line; + view->dot.line = (uint32_t)new_line; // make sure column stays on the line - uint32_t linelen = text_line_length(buffer->text, buffer->dot.line); - buffer->dot.col = buffer->dot.col > linelen ? linelen : buffer->dot.col; + uint32_t linelen = text_line_length(view->buffer->text, view->dot.line); + view->dot.col = view->dot.col > linelen ? linelen : view->dot.col; return true; } } // move dot `coldelta` chars -bool moveh(struct buffer *buffer, int coldelta) { - int64_t new_col = (int64_t)buffer->dot.col + coldelta; +bool moveh(struct buffer_view *view, int coldelta) { + int64_t new_col = (int64_t)view->dot.col + coldelta; - if (new_col > (int64_t)text_line_length(buffer->text, buffer->dot.line)) { - if (movev(buffer, 1)) { - buffer->dot.col = 0; + if (new_col > (int64_t)text_line_length(view->buffer->text, view->dot.line)) { + if (movev(view, 1)) { + view->dot.col = 0; } } else if (new_col < 0) { - if (movev(buffer, -1)) { - buffer->dot.col = text_line_length(buffer->text, buffer->dot.line); + if (movev(view, -1)) { + view->dot.col = text_line_length(view->buffer->text, view->dot.line); } else { return false; } } else { - buffer->dot.col = new_col; + view->dot.col = new_col; } return true; } -void buffer_goto(struct buffer *buffer, uint32_t line, uint32_t col) { - int64_t linedelta = (int64_t)line - (int64_t)buffer->dot.line; - movev(buffer, linedelta); +void buffer_goto(struct buffer_view *view, uint32_t line, uint32_t col) { + int64_t linedelta = (int64_t)line - (int64_t)view->dot.line; + movev(view, linedelta); - int64_t coldelta = (int64_t)col - (int64_t)buffer->dot.col; - moveh(buffer, coldelta); + int64_t coldelta = (int64_t)col - (int64_t)view->dot.col; + moveh(view, coldelta); } struct region { @@ -354,14 +281,14 @@ struct region to_region(struct buffer_location dot, return reg; } -struct region buffer_get_region(struct buffer *buffer) { - return to_region(buffer->dot, buffer->mark); +struct region buffer_get_region(struct buffer_view *view) { + return to_region(view->dot, view->mark); } -bool buffer_region_has_size(struct buffer *buffer) { - return buffer->mark_set && - (labs((int64_t)buffer->mark.line - (int64_t)buffer->dot.line) + - labs((int64_t)buffer->mark.col - (int64_t)buffer->dot.col)) > 0; +bool buffer_region_has_size(struct buffer_view *view) { + return view->mark_set && + (labs((int64_t)view->mark.line - (int64_t)view->dot.line) + + labs((int64_t)view->mark.col - (int64_t)view->dot.col)) > 0; } struct text_chunk *copy_region(struct buffer *buffer, struct region region) { @@ -379,39 +306,39 @@ struct text_chunk *copy_region(struct buffer *buffer, struct region region) { return curr; } -void buffer_copy(struct buffer *buffer) { - if (buffer_region_has_size(buffer)) { - struct region reg = buffer_get_region(buffer); - struct text_chunk *curr = copy_region(buffer, reg); - buffer_clear_mark(buffer); +void buffer_copy(struct buffer_view *view) { + if (buffer_region_has_size(view)) { + struct region reg = buffer_get_region(view); + struct text_chunk *curr = copy_region(view->buffer, reg); + buffer_clear_mark(view); } } -void paste(struct buffer *buffer, uint32_t ring_idx) { +void paste(struct buffer_view *view, uint32_t ring_idx) { if (ring_idx > 0) { struct text_chunk *curr = &g_kill_ring.buffer[ring_idx - 1]; if (curr->text != NULL) { - g_kill_ring.last_paste = buffer->mark_set ? buffer->mark : buffer->dot; - buffer_add_text(buffer, curr->text, curr->nbytes); + g_kill_ring.last_paste = view->mark_set ? view->mark : view->dot; + buffer_add_text(view, curr->text, curr->nbytes); g_kill_ring.paste_up_to_date = true; } } } -void buffer_paste(struct buffer *buffer) { +void buffer_paste(struct buffer_view *view) { g_kill_ring.paste_idx = g_kill_ring.curr_idx; - paste(buffer, g_kill_ring.curr_idx); + paste(view, g_kill_ring.curr_idx); } -void buffer_paste_older(struct buffer *buffer) { +void buffer_paste_older(struct buffer_view *view) { if (g_kill_ring.paste_up_to_date) { // 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, buffer->dot); + delete_with_undo(view->buffer, g_kill_ring.last_paste, view->dot); // place ourselves right - buffer->dot = g_kill_ring.last_paste; + view->dot = g_kill_ring.last_paste; // paste older if (g_kill_ring.paste_idx - 1 > 0) { @@ -420,92 +347,88 @@ void buffer_paste_older(struct buffer *buffer) { g_kill_ring.paste_idx = g_kill_ring.curr_idx; } - paste(buffer, g_kill_ring.paste_idx); + paste(view, g_kill_ring.paste_idx); } else { - buffer_paste(buffer); + buffer_paste(view); } } -void buffer_cut(struct buffer *buffer) { - if (buffer_region_has_size(buffer)) { - struct region reg = buffer_get_region(buffer); - copy_region(buffer, reg); - delete_with_undo(buffer, reg.begin, reg.end); - buffer_clear_mark(buffer); - buffer->dot = reg.begin; +void buffer_cut(struct buffer_view *view) { + if (buffer_region_has_size(view)) { + struct region reg = buffer_get_region(view); + copy_region(view->buffer, reg); + delete_with_undo(view->buffer, reg.begin, reg.end); + buffer_clear_mark(view); + view->dot = reg.begin; } } -bool maybe_delete_region(struct buffer *buffer) { - if (buffer_region_has_size(buffer)) { - struct region reg = buffer_get_region(buffer); - delete_with_undo(buffer, reg.begin, reg.end); - buffer_clear_mark(buffer); - buffer->dot = reg.begin; +bool maybe_delete_region(struct buffer_view *view) { + if (buffer_region_has_size(view)) { + struct region reg = buffer_get_region(view); + delete_with_undo(view->buffer, reg.begin, reg.end); + buffer_clear_mark(view); + view->dot = reg.begin; return true; } return false; } -void buffer_kill_line(struct buffer *buffer) { - if (text_num_lines(buffer->text) == 0) { - return; - } - +void buffer_kill_line(struct buffer_view *view) { uint32_t nchars = - text_line_length(buffer->text, buffer->dot.line) - buffer->dot.col; + text_line_length(view->buffer->text, view->dot.line) - view->dot.col; if (nchars == 0) { nchars = 1; } struct region reg = { - .begin = buffer->dot, + .begin = view->dot, .end = { - .line = buffer->dot.line, - .col = buffer->dot.col + nchars, + .line = view->dot.line, + .col = view->dot.col + nchars, }, }; - copy_region(buffer, reg); - delete_with_undo(buffer, buffer->dot, + copy_region(view->buffer, reg); + delete_with_undo(view->buffer, view->dot, (struct buffer_location){ - .line = buffer->dot.line, - .col = buffer->dot.col + nchars, + .line = view->dot.line, + .col = view->dot.col + nchars, }); } -void buffer_forward_delete_char(struct buffer *buffer) { - if (maybe_delete_region(buffer)) { +void buffer_forward_delete_char(struct buffer_view *view) { + if (maybe_delete_region(view)) { return; } - delete_with_undo(buffer, buffer->dot, + delete_with_undo(view->buffer, view->dot, (struct buffer_location){ - .line = buffer->dot.line, - .col = buffer->dot.col + 1, + .line = view->dot.line, + .col = view->dot.col + 1, }); } -void buffer_backward_delete_char(struct buffer *buffer) { - if (maybe_delete_region(buffer)) { +void buffer_backward_delete_char(struct buffer_view *view) { + if (maybe_delete_region(view)) { return; } - if (moveh(buffer, -1)) { - buffer_forward_delete_char(buffer); + if (moveh(view, -1)) { + buffer_forward_delete_char(view); } } -void buffer_backward_char(struct buffer *buffer) { moveh(buffer, -1); } -void buffer_forward_char(struct buffer *buffer) { moveh(buffer, 1); } +void buffer_backward_char(struct buffer_view *view) { moveh(view, -1); } +void buffer_forward_char(struct buffer_view *view) { moveh(view, 1); } -struct buffer_location find_next(struct buffer *buffer, uint8_t chars[], +struct buffer_location find_next(struct buffer_view *view, uint8_t chars[], uint32_t nchars, int direction) { - struct text_chunk line = text_get_line(buffer->text, buffer->dot.line); + struct text_chunk line = text_get_line(view->buffer->text, view->dot.line); int64_t bytei = - text_col_to_byteindex(buffer->text, buffer->dot.line, buffer->dot.col); + text_col_to_byteindex(view->buffer->text, view->dot.line, view->dot.col); while (bytei < line.nbytes && bytei > 0 && (line.text[bytei] == ' ' || line.text[bytei] == '.')) { bytei += direction; @@ -519,33 +442,33 @@ struct buffer_location find_next(struct buffer *buffer, uint8_t chars[], } uint32_t target_col = - text_byteindex_to_col(buffer->text, buffer->dot.line, bytei); - return (struct buffer_location){.line = buffer->dot.line, .col = target_col}; + text_byteindex_to_col(view->buffer->text, view->dot.line, bytei); + return (struct buffer_location){.line = view->dot.line, .col = target_col}; } -void buffer_forward_word(struct buffer *buffer) { - moveh(buffer, 1); +void buffer_forward_word(struct buffer_view *view) { + moveh(view, 1); uint8_t chars[] = {' ', '.'}; - buffer->dot = find_next(buffer, chars, 2, 1); + view->dot = find_next(view, chars, 2, 1); } -void buffer_backward_word(struct buffer *buffer) { - moveh(buffer, -1); +void buffer_backward_word(struct buffer_view *view) { + moveh(view, -1); uint8_t chars[] = {' ', '.'}; - buffer->dot = find_next(buffer, chars, 2, -1); + view->dot = find_next(view, chars, 2, -1); } -void buffer_backward_line(struct buffer *buffer) { movev(buffer, -1); } -void buffer_forward_line(struct buffer *buffer) { movev(buffer, 1); } +void buffer_backward_line(struct buffer_view *view) { movev(view, -1); } +void buffer_forward_line(struct buffer_view *view) { movev(view, 1); } -void buffer_end_of_line(struct buffer *buffer) { - buffer->dot.col = text_line_length(buffer->text, buffer->dot.line); +void buffer_end_of_line(struct buffer_view *view) { + view->dot.col = text_line_length(view->buffer->text, view->dot.line); } -void buffer_beginning_of_line(struct buffer *buffer) { buffer->dot.col = 0; } +void buffer_beginning_of_line(struct buffer_view *view) { view->dot.col = 0; } struct buffer buffer_from_file(char *filename) { - struct buffer b = buffer_create(basename((char *)filename), true); + struct buffer b = buffer_create(basename((char *)filename)); b.filename = strdup(filename); if (access(b.filename, F_OK) == 0) { FILE *file = fopen(filename, "r"); @@ -626,8 +549,57 @@ void buffer_write_to(struct buffer *buffer, const char *filename) { buffer_to_file(buffer); } -int buffer_add_text(struct buffer *buffer, uint8_t *text, uint32_t nbytes) { - if (buffer->readonly) { +struct search_data { + VEC(struct match) matches; + const char *pattern; +}; + +// TODO: maybe should live in text +void search_line(struct text_chunk *chunk, void *userdata) { + struct search_data *data = (struct search_data *)userdata; + size_t pattern_len = strlen(data->pattern); + uint32_t pattern_nchars = utf8_nchars((uint8_t *)data->pattern, pattern_len); + + char *line = malloc(chunk->nbytes + 1); + memcpy(line, chunk->text, chunk->nbytes); + line[chunk->nbytes] = '\0'; + char *hit = NULL; + uint32_t byteidx = 0; + while ((hit = strstr(line + byteidx, data->pattern)) != NULL) { + byteidx = hit - line; + uint32_t begin = utf8_nchars(chunk->text, byteidx); + struct match match = (struct match){ + .begin = {.col = begin, .line = chunk->line}, + .end = {.col = begin + pattern_nchars, .line = chunk->line}, + }; + + VEC_PUSH(&data->matches, match); + + // proceed to after match + byteidx += pattern_len; + } +} + +void buffer_find(struct buffer *buffer, const char *pattern, + struct match **matches, uint32_t *nmatches) { + + struct search_data data = (struct search_data){.pattern = pattern}; + VEC_INIT(&data.matches, 16); + text_for_each_line(buffer->text, 0, text_num_lines(buffer->text), search_line, + &data); + + *matches = VEC_ENTRIES(&data.matches); + *nmatches = VEC_SIZE(&data.matches); +} + +void buffer_set_text(struct buffer *buffer, uint8_t *text, uint32_t nbytes) { + text_clear(buffer->text); + uint32_t lines, cols; + text_append(buffer->text, text, nbytes, &lines, &cols); +} + +int buffer_add_text(struct buffer_view *view, uint8_t *text, uint32_t nbytes) { + if (view->buffer->readonly) { minibuffer_echo_timeout(4, "buffer is read-only"); return 0; } @@ -637,44 +609,44 @@ int buffer_add_text(struct buffer *buffer, uint8_t *text, uint32_t nbytes) { /* If we currently have a selection active, * replace it with the text to insert. */ - maybe_delete_region(buffer); + maybe_delete_region(view); - struct buffer_location initial = buffer->dot; + struct buffer_location initial = view->dot; uint32_t lines_added, cols_added; - text_insert_at(buffer->text, initial.line, initial.col, text, nbytes, + text_insert_at(view->buffer->text, initial.line, initial.col, text, nbytes, &lines_added, &cols_added); // move to after inserted text - movev(buffer, lines_added); + movev(view, lines_added); if (lines_added > 0) { // does not make sense to use position from another line - buffer->dot.col = 0; + view->dot.col = 0; } - moveh(buffer, cols_added); + moveh(view, cols_added); - struct buffer_location final = buffer->dot; + struct buffer_location final = view->dot; undo_push_add( - &buffer->undo, + &view->buffer->undo, (struct undo_add){.begin = {.row = initial.line, .col = initial.col}, .end = {.row = final.line, .col = final.col}}); if (lines_added > 0) { - undo_push_boundary(&buffer->undo, + undo_push_boundary(&view->buffer->undo, (struct undo_boundary){.save_point = false}); } - buffer->modified = true; + view->buffer->modified = true; return lines_added; } -void buffer_newline(struct buffer *buffer) { - buffer_add_text(buffer, (uint8_t *)"\n", 1); +void buffer_newline(struct buffer_view *view) { + buffer_add_text(view, (uint8_t *)"\n", 1); } -void buffer_indent(struct buffer *buffer) { - uint32_t tab_width = buffer->lang.tab_width; - buffer_add_text(buffer, (uint8_t *)" ", +void buffer_indent(struct buffer_view *view) { + uint32_t tab_width = view->buffer->lang.tab_width; + buffer_add_text(view, (uint8_t *)" ", tab_width > 16 ? 16 : tab_width); } @@ -691,26 +663,25 @@ uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook, return buffer->update_hooks.nhooks - 1; } -void buffer_set_mark(struct buffer *buffer) { - buffer->mark_set - ? buffer_clear_mark(buffer) - : buffer_set_mark_at(buffer, buffer->dot.line, buffer->dot.col); +void buffer_set_mark(struct buffer_view *view) { + view->mark_set ? buffer_clear_mark(view) + : buffer_set_mark_at(view, view->dot.line, view->dot.col); } -void buffer_clear_mark(struct buffer *buffer) { - buffer->mark_set = false; +void buffer_clear_mark(struct buffer_view *view) { + view->mark_set = false; minibuffer_echo_timeout(2, "mark cleared"); } -void buffer_set_mark_at(struct buffer *buffer, uint32_t line, uint32_t col) { - buffer->mark_set = true; - buffer->mark.line = line; - buffer->mark.col = col; +void buffer_set_mark_at(struct buffer_view *view, uint32_t line, uint32_t col) { + view->mark_set = true; + view->mark.line = line; + view->mark.col = col; minibuffer_echo_timeout(2, "mark set"); } -void buffer_undo(struct buffer *buffer) { - struct undo_stack *undo = &buffer->undo; +void buffer_undo(struct buffer_view *view) { + struct undo_stack *undo = &view->buffer->undo; undo_begin(undo); // fetch and handle records @@ -731,14 +702,14 @@ void buffer_undo(struct buffer *buffer) { case Undo_Boundary: { struct undo_boundary *b = &rec->boundary; if (b->save_point) { - buffer->modified = false; + view->buffer->modified = false; } break; } case Undo_Add: { struct undo_add *add = &rec->add; - delete_with_undo(buffer, + delete_with_undo(view->buffer, (struct buffer_location){ .line = add->begin.row, .col = add->begin.col, @@ -748,13 +719,13 @@ void buffer_undo(struct buffer *buffer) { .col = add->end.col, }); - buffer_goto(buffer, add->begin.row, add->begin.col); + buffer_goto(view, add->begin.row, add->begin.col); break; } case Undo_Delete: { struct undo_delete *del = &rec->delete; - buffer_goto(buffer, del->pos.row, del->pos.col); - buffer_add_text(buffer, del->data, del->nbytes); + buffer_goto(view, del->pos.row, del->pos.col); + buffer_add_text(view, del->data, del->nbytes); break; } } @@ -792,17 +763,34 @@ void render_line(struct text_chunk *line, void *userdata) { uint32_t scroll_bytes = utf8_nbytes(line->text, line->nbytes, cmdbuf->scroll.col); - uint8_t *text = line->text + scroll_bytes; - uint32_t text_nbytes = + uint32_t text_nbytes_scroll = scroll_bytes > line->nbytes ? 0 : line->nbytes - scroll_bytes; - uint32_t text_nchars = - scroll_bytes > line->nchars ? 0 : line->nchars - cmdbuf->scroll.col; + uint8_t *text = line->text + scroll_bytes; + + // calculate how many chars we can fit in 'width' + uint32_t linewidth = cmdbuf->left_margin; + uint32_t text_nbytes = 0; + for (uint32_t bytei = 0; + bytei < text_nbytes_scroll && linewidth < cmdbuf->width; ++bytei) { + uint8_t *txt = &text[bytei]; + if (*txt == '\t') { + linewidth += 3; + } else if (utf8_byte_is_unicode_start(*txt)) { + wchar_t wc; + if (mbrtowc(&wc, (char *)txt, 6, NULL) >= 0) { + linewidth += wcwidth(wc) - 1; + } + } + + ++linewidth; + ++text_nbytes; + } command_list_set_show_whitespace(cmdbuf->cmds, cmdbuf->show_ws); struct buffer_location *begin = &cmdbuf->region.begin, *end = &cmdbuf->region.end; - // should we draw region + // should we draw region? if (cmdbuf->mark_set && line->line >= begin->line && line->line <= end->line) { uint32_t byte_offset = 0; @@ -857,54 +845,42 @@ void render_line(struct text_chunk *line, void *userdata) { command_list_set_show_whitespace(cmdbuf->cmds, false); - uint32_t col = text_nchars + cmdbuf->left_margin; - for (uint32_t bytei = scroll_bytes; bytei < line->nbytes; ++bytei) { - if (line->text[bytei] == '\t') { - col += 3; - } else if (utf8_byte_is_unicode_start(line->text[bytei])) { - wchar_t wc; - if (mbrtowc(&wc, (char *)line->text + bytei, 6, NULL) >= 0) { - col += wcwidth(wc) - 1; - } - } - } - - if (col < cmdbuf->width) { - command_list_draw_repeated(cmdbuf->cmds, col, visual_line, ' ', - cmdbuf->width - col); + if (linewidth < cmdbuf->width) { + command_list_draw_repeated(cmdbuf->cmds, linewidth, visual_line, ' ', + cmdbuf->width - linewidth); } } -void scroll(struct buffer *buffer, int line_delta, int col_delta) { - uint32_t nlines = text_num_lines(buffer->text); - int64_t new_line = (int64_t)buffer->scroll.line + line_delta; +void scroll(struct buffer_view *view, int line_delta, int col_delta) { + uint32_t nlines = text_num_lines(view->buffer->text); + int64_t new_line = (int64_t)view->scroll.line + line_delta; if (new_line >= 0 && new_line < nlines) { - buffer->scroll.line = (uint32_t)new_line; + view->scroll.line = (uint32_t)new_line; } else if (new_line < 0) { - buffer->scroll.line = 0; + view->scroll.line = 0; } - int64_t new_col = (int64_t)buffer->scroll.col + col_delta; + int64_t new_col = (int64_t)view->scroll.col + col_delta; if (new_col >= 0 && - new_col < text_line_length(buffer->text, buffer->dot.line)) { - buffer->scroll.col = (uint32_t)new_col; + new_col < text_line_length(view->buffer->text, view->dot.line)) { + view->scroll.col = (uint32_t)new_col; } else if (new_col < 0) { - buffer->scroll.col = 0; + view->scroll.col = 0; } } -void to_relative(struct buffer *buffer, uint32_t line, uint32_t col, +void to_relative(struct buffer_view *view, uint32_t line, uint32_t col, int64_t *rel_line, int64_t *rel_col) { - *rel_col = (int64_t)col - (int64_t)buffer->scroll.col; - *rel_line = (int64_t)line - (int64_t)buffer->scroll.line; + *rel_col = (int64_t)col - (int64_t)view->scroll.col; + *rel_line = (int64_t)line - (int64_t)view->scroll.line; } -uint32_t visual_dot_col(struct buffer *buffer, uint32_t dot_col) { +uint32_t visual_dot_col(struct buffer_view *view, uint32_t dot_col) { uint32_t visual_dot_col = dot_col; - struct text_chunk line = text_get_line(buffer->text, buffer->dot.line); + struct text_chunk line = text_get_line(view->buffer->text, view->dot.line); for (uint32_t bytei = 0; bytei < - text_col_to_byteindex(buffer->text, buffer->dot.line, buffer->dot.col); + text_col_to_byteindex(view->buffer->text, view->dot.line, view->dot.col); ++bytei) { if (line.text[bytei] == '\t') { visual_dot_col += 3; @@ -919,11 +895,9 @@ uint32_t visual_dot_col(struct buffer *buffer, uint32_t dot_col) { return visual_dot_col; } -struct update_hook_result buffer_modeline_hook(struct buffer *buffer, - struct command_list *commands, - uint32_t width, uint32_t height, - uint64_t frame_time, - void *userdata) { +void render_modeline(struct modeline *modeline, struct buffer_view *view, + struct command_list *commands, uint32_t width, + uint32_t height, uint64_t frame_time) { char buf[width * 4]; static uint64_t samples[10] = {0}; @@ -941,18 +915,19 @@ struct update_hook_result buffer_modeline_hook(struct buffer *buffer, char left[128], right[128]; snprintf(left, 128, " %c%c %-16s (%d, %d) (%s)", - buffer->modified ? '*' : '-', buffer->readonly ? '%' : '-', - buffer->name, buffer->dot.line + 1, - visual_dot_col(buffer, buffer->dot.col), buffer->lang.name); + view->buffer->modified ? '*' : '-', + view->buffer->readonly ? '%' : '-', view->buffer->name, + view->dot.line + 1, visual_dot_col(view, view->dot.col), + view->buffer->lang.name); snprintf(right, 128, "(%.2f ms) %02d:%02d", frame_time / 1e6, lt->tm_hour, lt->tm_min); snprintf(buf, width * 4, "%s%*s%s", left, (int)(width - (strlen(left) + strlen(right))), "", right); - struct modeline *modeline = (struct modeline *)userdata; if (strcmp(buf, (char *)modeline->buffer) != 0) { modeline->buffer = realloc(modeline->buffer, width * 4); + modeline->sz = width * 4; strcpy((char *)modeline->buffer, buf); } @@ -960,10 +935,6 @@ struct update_hook_result buffer_modeline_hook(struct buffer *buffer, command_list_draw_text(commands, 0, height - 1, modeline->buffer, strlen((char *)modeline->buffer)); command_list_reset_color(commands); - - struct update_hook_result res = {0}; - res.margins.bottom = 1; - return res; } struct linenumdata { @@ -993,11 +964,7 @@ void clear_empty_linenum_lines(uint32_t line, struct command_list *commands, command_list_draw_repeated(commands, 0, line, ' ', longest_nchars + 2); } -struct update_hook_result buffer_linenum_hook(struct buffer *buffer, - struct command_list *commands, - uint32_t width, uint32_t height, - uint64_t frame_time, - void *userdata) { +uint32_t longest_linenum(struct buffer *buffer) { uint32_t total_lines = text_num_lines(buffer->text); uint32_t longest_nchars = 10; if (total_lines < 10) { @@ -1020,18 +987,10 @@ struct update_hook_result buffer_linenum_hook(struct buffer *buffer, longest_nchars = 9; } - linenum_data.longest_nchars = longest_nchars; - linenum_data.dot_line = buffer->dot.line; - struct update_hook_result res = {0}; - res.margins.left = longest_nchars + 2; - res.line_render_hook.callback = linenum_render_hook; - res.line_render_hook.empty_callback = clear_empty_linenum_lines; - res.line_render_hook.userdata = &linenum_data; - - return res; + return longest_nchars; } -void buffer_update(struct buffer *buffer, uint32_t width, uint32_t height, +void buffer_update(struct buffer_view *view, uint32_t width, uint32_t height, struct command_list *commands, uint64_t frame_time, uint32_t *relline, uint32_t *relcol) { if (width == 0 || height == 0) { @@ -1040,31 +999,48 @@ void buffer_update(struct buffer *buffer, uint32_t width, uint32_t height, uint32_t total_width = width, total_height = height; struct margin total_margins = {0}; - struct line_render_hook line_hooks[16]; + struct line_render_hook line_hooks[16 + 1]; uint32_t nlinehooks = 0; - for (uint32_t hooki = 0; hooki < buffer->update_hooks.nhooks; ++hooki) { - struct update_hook *h = &buffer->update_hooks.hooks[hooki]; + for (uint32_t hooki = 0; hooki < view->buffer->update_hooks.nhooks; ++hooki) { + struct update_hook *h = &view->buffer->update_hooks.hooks[hooki]; struct update_hook_result res = - h->callback(buffer, commands, width, height, frame_time, h->userdata); - - struct margin margins = res.margins; + h->callback(view, commands, width, height, frame_time, h->userdata); if (res.line_render_hook.callback != NULL) { line_hooks[nlinehooks] = res.line_render_hook; ++nlinehooks; } - total_margins.left += margins.left; - total_margins.right += margins.right; - total_margins.top += margins.top; - total_margins.bottom += margins.bottom; + total_margins.left += res.margins.left; + total_margins.right += res.margins.right; + total_margins.bottom += res.margins.bottom; + total_margins.top += res.margins.top; + + height -= total_margins.top + total_margins.bottom; + width -= total_margins.left + total_margins.right; + } + + if (view->line_numbers) { + linenum_data.longest_nchars = longest_linenum(view->buffer); + linenum_data.dot_line = view->dot.line; + line_hooks[nlinehooks].callback = linenum_render_hook; + line_hooks[nlinehooks].empty_callback = clear_empty_linenum_lines; + line_hooks[nlinehooks].userdata = &linenum_data; + ++nlinehooks; + + total_margins.left += linenum_data.longest_nchars + 2; + } - height -= margins.top + margins.bottom; - width -= margins.left + margins.right; + if (view->modeline != NULL) { + render_modeline(view->modeline, view, commands, width, height, frame_time); + total_margins.bottom += 1; } + height -= total_margins.top + total_margins.bottom; + width -= total_margins.left + total_margins.right; + int64_t rel_line, rel_col; - to_relative(buffer, buffer->dot.line, buffer->dot.col, &rel_line, &rel_col); + to_relative(view, view->dot.line, view->dot.col, &rel_line, &rel_col); int line_delta = 0, col_delta = 0; if (rel_line < 0) { line_delta = rel_line - ((int)height / 2); @@ -1074,32 +1050,32 @@ void buffer_update(struct buffer *buffer, uint32_t width, uint32_t height, if (rel_col < 0) { col_delta = rel_col - ((int)width / 2); - } else if (rel_col > width) { + } else if (rel_col >= width) { col_delta = (rel_col - width) + width / 2; } - scroll(buffer, line_delta, col_delta); + scroll(view, line_delta, col_delta); struct setting *show_ws = settings_get("editor.show-whitespace"); struct cmdbuf cmdbuf = (struct cmdbuf){ .cmds = commands, - .scroll = buffer->scroll, + .scroll = view->scroll, .left_margin = total_margins.left, .width = total_width, .line_offset = total_margins.top, .line_render_hooks = line_hooks, .nlinerender_hooks = nlinehooks, - .mark_set = buffer->mark_set, - .region = to_region(buffer->dot, buffer->mark), + .mark_set = view->mark_set, + .region = to_region(view->dot, view->mark), .show_ws = show_ws != NULL ? show_ws->value.bool_value : true, }; - text_for_each_line(buffer->text, buffer->scroll.line, height, render_line, + text_for_each_line(view->buffer->text, view->scroll.line, height, render_line, &cmdbuf); // draw empty lines - uint32_t nlines = text_num_lines(buffer->text); - for (uint32_t linei = nlines - buffer->scroll.line + total_margins.top; + uint32_t nlines = text_num_lines(view->buffer->text); + for (uint32_t linei = nlines - view->scroll.line + total_margins.top; linei < height; ++linei) { for (uint32_t hooki = 0; hooki < nlinehooks; ++hooki) { @@ -1112,9 +1088,9 @@ void buffer_update(struct buffer *buffer, uint32_t width, uint32_t height, } // update the visual cursor position - to_relative(buffer, buffer->dot.line, buffer->dot.col, &rel_line, &rel_col); - uint32_t visual_col = visual_dot_col(buffer, buffer->dot.col); - to_relative(buffer, buffer->dot.line, visual_col, &rel_line, &rel_col); + to_relative(view, view->dot.line, view->dot.col, &rel_line, &rel_col); + uint32_t visual_col = visual_dot_col(view, view->dot.col); + to_relative(view, view->dot.line, visual_col, &rel_line, &rel_col); *relline = rel_line < 0 ? 0 : (uint32_t)rel_line + total_margins.top; *relcol = rel_col < 0 ? 0 : (uint32_t)rel_col + total_margins.left; @@ -1123,3 +1099,12 @@ void buffer_update(struct buffer *buffer, uint32_t width, uint32_t height, struct text_chunk buffer_get_line(struct buffer *buffer, uint32_t line) { return text_get_line(buffer->text, line); } + +void buffer_view_scroll_down(struct buffer_view *view, uint32_t height) { + buffer_goto(view, view->dot.line + height, view->dot.col); + scroll(view, height, 0); +} +void buffer_view_scroll_up(struct buffer_view *view, uint32_t height) { + buffer_goto(view, view->dot.line - height, view->dot.col); + scroll(view, -height, 0); +} diff --git a/src/buffer.h b/src/dged/buffer.h index 85518f5..539c427 100644 --- a/src/buffer.h +++ b/src/dged/buffer.h @@ -2,6 +2,7 @@ #include <stdint.h> #include <stdio.h> +#include "bits/stdint-uintn.h" #include "command.h" #include "lang.h" #include "text.h" @@ -53,7 +54,7 @@ struct update_hook_result { /** Buffer update hook callback function */ typedef struct update_hook_result (*update_hook_cb)( - struct buffer *buffer, struct command_list *commands, uint32_t width, + struct buffer_view *view, struct command_list *commands, uint32_t width, uint32_t height, uint64_t frame_time, void *userdata); /** @@ -70,6 +71,8 @@ struct update_hook { void *userdata; }; +typedef void (*create_hook_cb)(struct buffer *buffer, void *userdata); + /** * A set of update hooks */ @@ -86,6 +89,41 @@ struct buffer_location { uint32_t col; }; +struct match { + struct buffer_location begin; + struct buffer_location end; +}; + +struct buffer_view { + /** Location of dot (cursor) */ + struct buffer_location dot; + + /** Location of mark (where a selection starts) */ + struct buffer_location mark; + + /** Current buffer scroll position */ + struct buffer_location scroll; + + /** True if the start of a selection has been set */ + bool mark_set; + + /** Modeline buffer (may be NULL) */ + struct modeline *modeline; + + bool line_numbers; + + struct buffer *buffer; +}; + +struct buffer_view buffer_view_create(struct buffer *buffer, bool modeline, + bool line_numbers); +struct buffer_view buffer_view_clone(struct buffer_view *view); + +void buffer_view_scroll_down(struct buffer_view *view, uint32_t height); +void buffer_view_scroll_up(struct buffer_view *view, uint32_t height); + +void buffer_view_destroy(struct buffer_view *view); + /** * A buffer of text that can be modified, read from and written to disk. * @@ -96,33 +134,13 @@ struct buffer { /** Buffer name */ char *name; + /** Associated filename, this is where the buffer will be saved to */ char *filename; /** Text data structure */ struct text *text; - /** Location of dot (cursor) */ - struct buffer_location dot; - - /** Location of mark (where a selection starts) */ - struct buffer_location mark; - - /** True if the start of a selection has been set */ - bool mark_set; - - /** Buffer-local keymaps in reverse priority order */ - struct keymap *keymaps; - - /** Number of buffer-local keymaps */ - uint32_t nkeymaps; - - /** Maximum number of keymaps */ - uint32_t nkeymaps_max; - - /** Current buffer scroll position */ - struct buffer_location scroll; - /** Buffer update hooks */ struct update_hooks update_hooks; @@ -135,67 +153,67 @@ struct buffer { /** Can this buffer be changed */ bool readonly; - /** Modeline buffer (may be NULL) */ - struct modeline *modeline; - /** Buffer programming language */ struct language lang; }; -struct buffer buffer_create(char *name, bool modeline); +struct buffer buffer_create(char *name); void buffer_destroy(struct buffer *buffer); -void buffer_static_init(struct commands *commands); +void buffer_static_init(); void buffer_static_teardown(); -uint32_t buffer_keymaps(struct buffer *buffer, struct keymap **keymaps_out); -void buffer_add_keymap(struct buffer *buffer, struct keymap *keymap); - -int buffer_add_text(struct buffer *buffer, uint8_t *text, uint32_t nbytes); -void buffer_clear(struct buffer *buffer); +int buffer_add_text(struct buffer_view *view, uint8_t *text, uint32_t nbytes); +void buffer_set_text(struct buffer *buffer, uint8_t *text, uint32_t nbytes); +void buffer_clear(struct buffer_view *view); bool buffer_is_empty(struct buffer *buffer); bool buffer_is_modified(struct buffer *buffer); bool buffer_is_readonly(struct buffer *buffer); void buffer_set_readonly(struct buffer *buffer, bool readonly); -void buffer_kill_line(struct buffer *buffer); -void buffer_forward_delete_char(struct buffer *buffer); -void buffer_backward_delete_char(struct buffer *buffer); -void buffer_backward_char(struct buffer *buffer); -void buffer_backward_word(struct buffer *buffer); -void buffer_forward_char(struct buffer *buffer); -void buffer_forward_word(struct buffer *buffer); -void buffer_backward_line(struct buffer *buffer); -void buffer_forward_line(struct buffer *buffer); -void buffer_end_of_line(struct buffer *buffer); -void buffer_beginning_of_line(struct buffer *buffer); -void buffer_newline(struct buffer *buffer); -void buffer_indent(struct buffer *buffer); - -void buffer_undo(struct buffer *buffer); - -void buffer_goto_beginning(struct buffer *buffer); -void buffer_goto_end(struct buffer *buffer); -void buffer_goto(struct buffer *buffer, uint32_t line, uint32_t col); - -void buffer_set_mark(struct buffer *buffer); -void buffer_clear_mark(struct buffer *buffer); -void buffer_set_mark_at(struct buffer *buffer, uint32_t line, uint32_t col); - -void buffer_copy(struct buffer *buffer); -void buffer_paste(struct buffer *buffer); -void buffer_paste_older(struct buffer *buffer); -void buffer_cut(struct buffer *buffer); +void buffer_kill_line(struct buffer_view *view); +void buffer_forward_delete_char(struct buffer_view *view); +void buffer_backward_delete_char(struct buffer_view *view); +void buffer_backward_char(struct buffer_view *view); +void buffer_backward_word(struct buffer_view *view); +void buffer_forward_char(struct buffer_view *view); +void buffer_forward_word(struct buffer_view *view); +void buffer_backward_line(struct buffer_view *view); +void buffer_forward_line(struct buffer_view *view); +void buffer_end_of_line(struct buffer_view *view); +void buffer_beginning_of_line(struct buffer_view *view); +void buffer_newline(struct buffer_view *view); +void buffer_indent(struct buffer_view *view); + +void buffer_undo(struct buffer_view *view); + +void buffer_goto_beginning(struct buffer_view *view); +void buffer_goto_end(struct buffer_view *view); +void buffer_goto(struct buffer_view *view, uint32_t line, uint32_t col); + +void buffer_find(struct buffer *buffer, const char *pattern, + struct match **matches, uint32_t *nmatches); + +void buffer_set_mark(struct buffer_view *view); +void buffer_clear_mark(struct buffer_view *view); +void buffer_set_mark_at(struct buffer_view *view, uint32_t line, uint32_t col); + +void buffer_copy(struct buffer_view *view); +void buffer_paste(struct buffer_view *view); +void buffer_paste_older(struct buffer_view *view); +void buffer_cut(struct buffer_view *view); struct text_chunk buffer_get_line(struct buffer *buffer, uint32_t line); uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook, void *userdata); +uint32_t buffer_add_create_hook(create_hook_cb hook, void *userdata); + struct buffer buffer_from_file(char *filename); void buffer_to_file(struct buffer *buffer); void buffer_write_to(struct buffer *buffer, const char *filename); -void buffer_update(struct buffer *buffer, uint32_t width, uint32_t height, +void buffer_update(struct buffer_view *view, uint32_t width, uint32_t height, struct command_list *commands, uint64_t frame_time, uint32_t *relline, uint32_t *relcol); diff --git a/src/buffers.c b/src/dged/buffers.c index 38b51b7..38b51b7 100644 --- a/src/buffers.c +++ b/src/dged/buffers.c diff --git a/src/buffers.h b/src/dged/buffers.h index edf772c..edf772c 100644 --- a/src/buffers.h +++ b/src/dged/buffers.h diff --git a/src/dged/command.c b/src/dged/command.c new file mode 100644 index 0000000..9144058 --- /dev/null +++ b/src/dged/command.c @@ -0,0 +1,79 @@ +#include "command.h" +#include "buffer.h" +#include "buffers.h" +#include "hash.h" +#include "hashmap.h" +#include "minibuffer.h" + +#include <string.h> + +struct commands command_registry_create(uint32_t capacity) { + + struct commands cmds = {0}; + HASHMAP_INIT(&cmds.commands, capacity, hash_name); + return cmds; +} + +void command_registry_destroy(struct commands *commands) { + HASHMAP_DESTROY(&commands->commands); +} + +uint32_t register_command(struct commands *commands, struct command command) { + uint32_t hash = 0; + HASHMAP_INSERT(&commands->commands, struct command_entry, command.name, + command, hash); + return hash; +} + +void register_commands(struct commands *command_list, struct command *commands, + uint32_t ncommands) { + for (uint32_t ci = 0; ci < ncommands; ++ci) { + register_command(command_list, commands[ci]); + } +} + +struct command *lookup_command(struct commands *command_list, + const char *name) { + HASHMAP_GET(&command_list->commands, struct command_entry, name, + struct command * command); + return command; +} + +struct command *lookup_command_by_hash(struct commands *commands, + uint32_t hash) { + HASHMAP_GET_BY_HASH(&commands->commands, struct command_entry, hash, + struct command * command); + return command; +} + +int32_t execute_command(struct command *command, struct commands *commands, + struct window *active_window, struct buffers *buffers, + int argc, const char *argv[]) { + + return command->fn( + (struct command_ctx){ + .buffers = buffers, + .active_window = active_window, + .userdata = command->userdata, + .commands = commands, + .self = command, + .saved_argv = {0}, + .saved_argc = 0, + }, + argc, argv); +} + +void command_ctx_push_arg(struct command_ctx *ctx, const char *argv) { + if (ctx->saved_argc < 64) { + ctx->saved_argv[ctx->saved_argc] = strdup(argv); + ++ctx->saved_argc; + } +} + +void command_ctx_free(struct command_ctx *ctx) { + for (uint32_t i = 0; i < ctx->saved_argc; ++i) { + free((char *)ctx->saved_argv[i]); + } + + ctx->saved_argc = 0; +} diff --git a/src/command.h b/src/dged/command.h index 7ece486..bbc57f2 100644 --- a/src/command.h +++ b/src/dged/command.h @@ -176,30 +176,4 @@ struct command *lookup_command_by_hash(struct commands *commands, void command_ctx_push_arg(struct command_ctx *ctx, const char *argv); void command_ctx_free(struct command_ctx *ctx); -/** - * @defgroup common-commands Implementation of common commands - * @{ - */ - -/** - * Find and visit a file in the current window. - */ -int32_t find_file(struct command_ctx ctx, int argc, const char *argv[]); - -/** - * Write the active buffer to a file - */ -int32_t write_file(struct command_ctx ctx, int argc, const char *argv[]); - -/** - * Run a command interactively from the minibuffer. - */ -int32_t run_interactive(struct command_ctx ctx, int argc, const char *argv[]); - -/** - * Switch to another buffer in the currently active window - */ -int32_t switch_buffer(struct command_ctx ctx, int argc, const char *argv[]); - -/**@}*/ #endif diff --git a/src/display.c b/src/dged/display.c index 77b5b32..d9eeb11 100644 --- a/src/display.c +++ b/src/dged/display.c @@ -224,8 +224,8 @@ void command_list_draw_text(struct command_list *list, uint32_t col, struct draw_text_cmd *cmd = add_command(list, RenderCommand_DrawText)->draw_txt; cmd->data = data; - cmd->col = col + list->xoffset; - cmd->row = row + list->yoffset; + cmd->col = col; + cmd->row = row; cmd->len = len; } diff --git a/src/display.h b/src/dged/display.h index 14dd246..14dd246 100644 --- a/src/display.h +++ b/src/dged/display.h diff --git a/src/hash.h b/src/dged/hash.h index 0fd689b..0fd689b 100644 --- a/src/hash.h +++ b/src/dged/hash.h diff --git a/src/hashmap.h b/src/dged/hashmap.h index 405c193..405c193 100644 --- a/src/hashmap.h +++ b/src/dged/hashmap.h diff --git a/src/keyboard.c b/src/dged/keyboard.c index 14bb9dd..4b142ee 100644 --- a/src/keyboard.c +++ b/src/dged/keyboard.c @@ -141,7 +141,7 @@ bool key_equal(struct key *key1, struct key *key2) { return key_equal_char(key1, key2->mod, key2->key); } -void key_name(struct key *key, char *buf, size_t capacity) { +uint32_t key_name(struct key *key, char *buf, size_t capacity) { const char *mod = ""; switch (key->mod) { case Ctrl: diff --git a/src/keyboard.h b/src/dged/keyboard.h index 09a71be..e602b69 100644 --- a/src/keyboard.h +++ b/src/dged/keyboard.h @@ -139,5 +139,7 @@ bool key_equal(struct key *key1, struct key *key2); * @param key @ref key "Key" to get text representation for. * @param buf character buffer for holding the result. * @param capacity The capacity of buf. + * + * @returns The number of characters written to buf. */ -void key_name(struct key *key, char *buf, size_t capacity); +uint32_t key_name(struct key *key, char *buf, size_t capacity); diff --git a/src/lang.c b/src/dged/lang.c index 6919780..6919780 100644 --- a/src/lang.c +++ b/src/dged/lang.c diff --git a/src/lang.h b/src/dged/lang.h index 984e207..984e207 100644 --- a/src/lang.h +++ b/src/dged/lang.h diff --git a/src/minibuffer.c b/src/dged/minibuffer.c index 3fa311c..0ff32a8 100644 --- a/src/minibuffer.c +++ b/src/dged/minibuffer.c @@ -16,8 +16,10 @@ static struct minibuffer { char prompt[128]; struct command_ctx prompt_command_ctx; bool prompt_active; + bool clear; + + void (*update_callback)(); - struct keymap keymap; } g_minibuffer = {0}; void draw_prompt(struct command_list *commands, void *userdata) { @@ -27,11 +29,11 @@ void draw_prompt(struct command_list *commands, void *userdata) { command_list_reset_color(commands); } -int32_t execute(struct command_ctx ctx, int argc, const char *argv[]) { +int32_t minibuffer_execute() { if (g_minibuffer.prompt_active) { struct command_ctx *c = &g_minibuffer.prompt_command_ctx; - struct text_chunk line = buffer_get_line(g_minibuffer.buffer, 0); + struct text_chunk line = minibuffer_content(); char *l = (char *)malloc(line.nbytes + 1); memcpy(l, line.text, line.nbytes); l[line.nbytes] = '\0'; @@ -66,21 +68,17 @@ int32_t execute(struct command_ctx ctx, int argc, const char *argv[]) { } } -struct command execute_minibuffer_command = { - .fn = execute, - .name = "minibuffer-execute", - .userdata = NULL, -}; - -struct update_hook_result update(struct buffer *buffer, +struct update_hook_result update(struct buffer_view *view, struct command_list *commands, uint32_t width, uint32_t height, uint64_t frame_time, void *userdata) { struct timespec current; struct minibuffer *mb = (struct minibuffer *)userdata; clock_gettime(CLOCK_MONOTONIC, ¤t); - if (!mb->prompt_active && current.tv_sec >= mb->expires.tv_sec) { - buffer_clear(buffer); + if ((!mb->prompt_active && current.tv_sec >= mb->expires.tv_sec) || + mb->clear) { + buffer_clear(view); + mb->clear = false; } struct update_hook_result res = {0}; @@ -89,6 +87,10 @@ struct update_hook_result update(struct buffer *buffer, draw_prompt(commands, NULL); } + if (mb->update_callback != NULL) { + mb->update_callback(); + } + return res; } @@ -98,15 +100,7 @@ void minibuffer_init(struct buffer *buffer) { } g_minibuffer.buffer = buffer; - g_minibuffer.keymap = keymap_create("minibuffer", 10); - - struct binding bindings[] = { - ANONYMOUS_BINDING(Ctrl, 'M', &execute_minibuffer_command), - }; - keymap_bind_keys(&g_minibuffer.keymap, bindings, - sizeof(bindings) / sizeof(bindings[0])); - buffer_add_keymap(g_minibuffer.buffer, &g_minibuffer.keymap); - + g_minibuffer.clear = false; buffer_add_update_hook(g_minibuffer.buffer, update, &g_minibuffer); } @@ -120,9 +114,9 @@ void echo(uint32_t timeout, const char *fmt, va_list args) { // vsnprintf returns how many characters it would have wanted to write in case // of overflow - buffer_clear(g_minibuffer.buffer); - buffer_add_text(g_minibuffer.buffer, (uint8_t *)buff, + buffer_set_text(g_minibuffer.buffer, (uint8_t *)buff, nbytes > 2048 ? 2048 : nbytes); + g_minibuffer.clear = false; clock_gettime(CLOCK_MONOTONIC, &g_minibuffer.expires); g_minibuffer.expires.tv_sec += timeout; @@ -132,6 +126,14 @@ void minibuffer_destroy() { command_ctx_free(&g_minibuffer.prompt_command_ctx); } +struct text_chunk minibuffer_content() { + return buffer_get_line(g_minibuffer.buffer, 0); +} + +struct buffer *minibuffer_buffer() { + return g_minibuffer.buffer; +} + void minibuffer_echo(const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -146,8 +148,13 @@ void minibuffer_echo_timeout(uint32_t timeout, const char *fmt, ...) { va_end(args); } -int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, - ...) { +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)(), const char *fmt, + va_list args) { if (g_minibuffer.buffer == NULL) { return 1; } @@ -158,27 +165,59 @@ int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, command_ctx_free(&g_minibuffer.prompt_command_ctx); g_minibuffer.prompt_command_ctx = command_ctx; + minibuffer_set_prompt_internal(fmt, args); + + if (update_callback != NULL) { + g_minibuffer.update_callback = update_callback; + } + + return 0; +} + +int32_t minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, + ...) { va_list args; va_start(args, fmt); - vsnprintf(g_minibuffer.prompt, sizeof(g_minibuffer.prompt), fmt, args); + int32_t r = minibuffer_prompt_internal(command_ctx, NULL, fmt, args); va_end(args); - return 0; + return r; +} + +int32_t minibuffer_prompt_interactive(struct command_ctx command_ctx, + void (*update_callback)(), + const char *fmt, ...) { + va_list args; + va_start(args, fmt); + int32_t r = + minibuffer_prompt_internal(command_ctx, update_callback, fmt, args); + va_end(args); + + return r; +} + +void minibuffer_set_prompt(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + minibuffer_set_prompt_internal(fmt, args); + va_end(args); } void minibuffer_abort_prompt() { minibuffer_clear(); + g_minibuffer.update_callback = NULL; g_minibuffer.prompt_active = false; } +bool minibuffer_empty() { return !minibuffer_displaying(); } + bool minibuffer_displaying() { return g_minibuffer.buffer != NULL && !buffer_is_empty(g_minibuffer.buffer); } void minibuffer_clear() { - if (g_minibuffer.buffer != NULL) { - buffer_clear(g_minibuffer.buffer); - } + g_minibuffer.expires.tv_sec = 0; + g_minibuffer.clear = true; } bool minibuffer_focused() { return g_minibuffer.prompt_active; } diff --git a/src/minibuffer.h b/src/dged/minibuffer.h index 6845b07..24f54cf 100644 --- a/src/minibuffer.h +++ b/src/dged/minibuffer.h @@ -5,6 +5,7 @@ struct buffer; struct command_ctx; +struct keymap; /** * Initialize the minibuffer. @@ -22,6 +23,10 @@ void minibuffer_init(struct buffer *buffer); */ void minibuffer_destroy(); +struct text_chunk minibuffer_content(); + +struct buffer *minibuffer_buffer(); + /** * Echo a message to the minibuffer. * @@ -49,11 +54,23 @@ void minibuffer_echo_timeout(uint32_t timeout, const char *fmt, ...); * command (or other command) when the user confirms the input. * @param fmt Format string for the prompt. * @param ... Format arguments. - * @returns a return code suitable to return from a command to signal more input - * is needed. + * @returns 0 on success. */ 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)(), + const char *fmt, ...); + +void minibuffer_set_prompt(const char *fmt, ...); + +/** + * Evaluate the current contents of the minibuffer + * + * @returns zero on success, non-zero to indicate failure + */ +int32_t minibuffer_execute(); + /** * Abort the current minibuffer prompt. * @@ -74,6 +91,8 @@ struct minibuffer_prompt_args { */ void minibuffer_clear(); +bool minibuffer_empty(); + /** * Is the minibuffer currently displaying something? * diff --git a/src/reactor-epoll.c b/src/dged/reactor-epoll.c index e488fef..e488fef 100644 --- a/src/reactor-epoll.c +++ b/src/dged/reactor-epoll.c diff --git a/src/reactor.h b/src/dged/reactor.h index e54afda..e54afda 100644 --- a/src/reactor.h +++ b/src/dged/reactor.h diff --git a/src/settings.c b/src/dged/settings.c index 7d3fcf1..524aa9b 100644 --- a/src/settings.c +++ b/src/dged/settings.c @@ -11,18 +11,8 @@ static struct settings g_settings = {0}; -int32_t settings_get_cmd(struct command_ctx ctx, int argc, const char *argv[]); -int32_t settings_set_cmd(struct command_ctx ctx, int argc, const char *argv[]); - -void settings_init(uint32_t initial_capacity, struct commands *commands) { +void settings_init(uint32_t initial_capacity) { HASHMAP_INIT(&g_settings.settings, initial_capacity, hash_name); - static struct command settings_commands[] = { - {.name = "set", .fn = settings_set_cmd}, - {.name = "get", .fn = settings_get_cmd}, - }; - - register_commands(commands, settings_commands, - sizeof(settings_commands) / sizeof(settings_commands[0])); } void settings_destroy() { @@ -103,63 +93,3 @@ void setting_to_string(struct setting *setting, char *buf, size_t n) { break; } } - -int32_t settings_get_cmd(struct command_ctx ctx, int argc, const char *argv[]) { - if (argc == 0) { - return minibuffer_prompt(ctx, "setting: "); - } - - struct setting *setting = settings_get(argv[0]); - if (setting == NULL) { - minibuffer_echo_timeout(4, "no such setting \"%s\"", argv[0]); - return 1; - } else { - char buf[128]; - setting_to_string(setting, buf, 128); - minibuffer_echo("%s = %s", argv[0], buf); - } - - return 0; -} - -int32_t settings_set_cmd(struct command_ctx ctx, int argc, const char *argv[]) { - if (argc == 0) { - return minibuffer_prompt(ctx, "setting: "); - } else if (argc == 1) { - // validate setting here as well for a better experience - struct setting *setting = settings_get(argv[0]); - if (setting == NULL) { - minibuffer_echo_timeout(4, "no such setting \"%s\"", argv[0]); - return 1; - } - - command_ctx_push_arg(&ctx, argv[0]); - return minibuffer_prompt(ctx, "value: "); - } - - struct setting *setting = settings_get(argv[0]); - if (setting == NULL) { - minibuffer_echo_timeout(4, "no such setting \"%s\"", argv[0]); - return 1; - } else { - const char *value = argv[1]; - struct setting_value new_value = {.type = setting->value.type}; - switch (setting->value.type) { - case Setting_Bool: - new_value.bool_value = strncmp("true", value, 4) == 0 || - strncmp("yes", value, 3) == 0 || - strncmp("on", value, 2) == 0; - break; - case Setting_Number: - new_value.number_value = atol(value); - break; - case Setting_String: - new_value.string_value = (char *)value; - break; - } - - setting_set_value(setting, new_value); - } - - return 0; -} diff --git a/src/settings.h b/src/dged/settings.h index 20cca24..5d245d9 100644 --- a/src/settings.h +++ b/src/dged/settings.h @@ -69,7 +69,7 @@ struct settings { * @param initial_capacity Initial capacity of the settings collection. * @returns Nothing, the settings collection is a global instance. */ -void settings_init(uint32_t initial_capacity, struct commands *commands); +void settings_init(uint32_t initial_capacity); /** * Destroy the global collection of settings. @@ -117,3 +117,21 @@ void settings_get_prefix(const char *prefix, struct setting **settings_out[], * type for the setting. If not, the new value is ignored. */ void settings_set(const char *path, struct setting_value value); + +/** + * Set a value for a setting. + * + * @param setting Pointer to a setting to set. + * @param value The new value of the setting. The type has to match the declared + * type for the setting. If not, the new value is ignored. + */ +void setting_set_value(struct setting *setting, struct setting_value val); + +/** + * Create a string representation for a setting. + * + * @param setting Pointer to a setting to turn into a string. + * @param buf Character buffer to store resulting string in. + * @param n Size in bytes of @ref buf. + */ +void setting_to_string(struct setting *setting, char *buf, size_t n); diff --git a/src/text.c b/src/dged/text.c index cdbb796..f8ba72d 100644 --- a/src/text.c +++ b/src/dged/text.c @@ -306,6 +306,10 @@ void text_insert_at(struct text *text, uint32_t line, uint32_t col, void text_delete(struct text *text, uint32_t start_line, uint32_t start_col, uint32_t end_line, uint32_t end_col) { + if (text->nlines == 0) { + return; + } + uint32_t maxline = text->nlines > 0 ? text->nlines - 1 : 0; // make sure we stay inside @@ -455,7 +459,7 @@ struct text_chunk text_get_region(struct text *text, uint32_t start_line, // correct last line struct copy_cmd *cmd_last = ©_cmds[nlines - 1]; uint32_t byteindex = utf8_nbytes(last_line->data, last_line->nbytes, end_col); - cmd_last->nbytes -= (last_line->nchars - end_col); + cmd_last->nbytes -= (last_line->nbytes - byteindex); total_bytes -= (last_line->nbytes - byteindex); total_chars -= (last_line->nchars - end_col); diff --git a/src/text.h b/src/dged/text.h index fbee89b..fbee89b 100644 --- a/src/text.h +++ b/src/dged/text.h diff --git a/src/undo.c b/src/dged/undo.c index 2780557..8f00f0f 100644 --- a/src/undo.c +++ b/src/dged/undo.c @@ -32,19 +32,19 @@ void undo_destroy(struct undo_stack *undo) { uint32_t undo_push_boundary(struct undo_stack *undo, struct undo_boundary boundary) { - VEC_APPEND(&undo->records, struct undo_record * rec); - rec->type = Undo_Boundary; - rec->boundary = boundary; - // we can only have one save point if (boundary.save_point) { VEC_FOR_EACH(&undo->records, struct undo_record * rec) { - if (rec->type && Undo_Boundary && rec->boundary.save_point) { + if (rec->type == Undo_Boundary && rec->boundary.save_point) { rec->boundary.save_point = false; } } } + VEC_APPEND(&undo->records, struct undo_record * rec); + rec->type = Undo_Boundary; + rec->boundary = boundary; + if (!undo->undo_in_progress) { undo->top = VEC_SIZE(&undo->records) - 1; } diff --git a/src/undo.h b/src/dged/undo.h index 1ce3a8a..1ce3a8a 100644 --- a/src/undo.h +++ b/src/dged/undo.h diff --git a/src/utf8.c b/src/dged/utf8.c index abf5ef7..abf5ef7 100644 --- a/src/utf8.c +++ b/src/dged/utf8.c diff --git a/src/utf8.h b/src/dged/utf8.h index 59a959e..59a959e 100644 --- a/src/utf8.h +++ b/src/dged/utf8.h diff --git a/src/vec.h b/src/dged/vec.h index 2d5bd32..073f978 100644 --- a/src/vec.h +++ b/src/dged/vec.h @@ -1,6 +1,8 @@ #ifndef _VEC_H #define _VEC_H +#include <stdlib.h> + #define VEC(entry) \ struct { \ entry *entries; \ diff --git a/src/dged/window.c b/src/dged/window.c new file mode 100644 index 0000000..f24997c --- /dev/null +++ b/src/dged/window.c @@ -0,0 +1,445 @@ +#include "binding.h" +#include "btree.h" +#include "buffer.h" +#include "command.h" +#include "display.h" +#include "minibuffer.h" + +enum window_type { + Window_Buffer, + Window_HSplit, + Window_VSplit, +}; + +struct window { + uint32_t x; + uint32_t y; + uint32_t width; + uint32_t height; + enum window_type type; + struct buffer_view buffer_view; + struct buffer *prev_buffer; + struct command_list *commands; + uint32_t relline; + uint32_t relcol; +}; + +BINTREE_ENTRY_TYPE(window_node, struct window); + +static struct windows { + BINTREE(window_node) windows; + struct window_node *active; + struct keymap keymap; +} g_windows; + +static struct window g_minibuffer_window; + +void windows_init(uint32_t height, uint32_t width, + struct buffer *initial_buffer, struct buffer *minibuffer) { + BINTREE_INIT(&g_windows.windows); + + g_minibuffer_window = (struct window){ + .buffer_view = buffer_view_create(minibuffer, false, false), + .prev_buffer = NULL, + .x = 0, + .y = height - 1, + .height = 1, + .width = width, + }; + + struct window root_window = (struct window){ + .buffer_view = buffer_view_create(initial_buffer, true, true), + .prev_buffer = NULL, + .height = height - 1, + .width = width, + .x = 0, + .y = 0, + }; + BINTREE_SET_ROOT(&g_windows.windows, root_window); + g_windows.active = BINTREE_ROOT(&g_windows.windows); +} + +static void window_tree_clear_sub(struct window_node *root_node) { + struct window_node *n = root_node; + BINTREE_FIRST(n); + while (n != NULL) { + struct window *w = &BINTREE_VALUE(n); + if (w->type == Window_Buffer) { + buffer_view_destroy(&w->buffer_view); + } + BINTREE_NEXT(n); + } + BINTREE_FREE_NODES(root_node, window_node); +} + +static void window_tree_clear() { + window_tree_clear_sub(BINTREE_ROOT(&g_windows.windows)); +} + +void windows_destroy() { window_tree_clear(); } + +struct window *root_window() { + return &BINTREE_VALUE(BINTREE_ROOT(&g_windows.windows)); +} + +struct window *minibuffer_window() { + return &g_minibuffer_window; +} + +static void window_tree_resize(struct window_node *root, uint32_t height, + uint32_t width) { + + /* due to the way tree traversal works, we need to disconnect the subtree from + * its potential parent. Otherwise the algorithm will traverse above the root + * of the subtree. */ + struct window_node *orig_parent = BINTREE_PARENT(root); + BINTREE_PARENT(root) = NULL; + + struct window *root_window = &BINTREE_VALUE(root); + uint32_t width_ratio_percent = (width * 100) / (root_window->width); + uint32_t height_ratio_percent = (height * 100) / (root_window->height); + root_window->width = width; + root_window->height = height; + + struct window_node *n = root; + BINTREE_FIRST(n); + while (n != NULL) { + struct window *w = &BINTREE_VALUE(n); + if (BINTREE_PARENT(n) != NULL && n != root) { + if (BINTREE_LEFT(BINTREE_PARENT(n)) == n) { + // if left child, use scale from root + w->width = (width_ratio_percent * w->width) / 100; + w->height = (height_ratio_percent * w->height) / 100; + } else { + // if right child, fill rest of space after left and parent resize + struct window *left_sibling = + &BINTREE_VALUE(BINTREE_LEFT(BINTREE_PARENT(n))); + struct window *parent = &BINTREE_VALUE(BINTREE_PARENT(n)); + + w->width = parent->width; + w->height = parent->height; + if (parent->type == Window_HSplit) { + w->y = parent->y + left_sibling->height; + w->height -= left_sibling->height; + } else { + w->x = parent->x + left_sibling->width; + w->width -= left_sibling->width; + } + } + } + BINTREE_NEXT(n); + } + + BINTREE_PARENT(root) = orig_parent; +} + +void windows_resize(uint32_t height, uint32_t width) { + g_minibuffer_window.width = width; + g_minibuffer_window.y = height - 1; + + window_tree_resize(BINTREE_ROOT(&g_windows.windows), height - 1, width); +} + +void windows_update(void *(*frame_alloc)(size_t), uint64_t frame_time) { + struct window_node *n = BINTREE_ROOT(&g_windows.windows); + BINTREE_FIRST(n); + while (n != NULL) { + struct window *w = &BINTREE_VALUE(n); + if (w->type == Window_Buffer) { + w->commands = command_list_create(w->height * w->width, frame_alloc, w->x, + w->y, w->buffer_view.buffer->name); + + buffer_update(&w->buffer_view, w->width, w->height, w->commands, + frame_time, &w->relline, &w->relcol); + } + + BINTREE_NEXT(n); + } + + struct window *w = &g_minibuffer_window; + w->commands = command_list_create(w->height * w->width, frame_alloc, w->x, + w->y, w->buffer_view.buffer->name); + buffer_update(&w->buffer_view, w->width, w->height, w->commands, frame_time, + &w->relline, &w->relcol); +} + +void windows_render(struct display *display) { + struct window_node *n = BINTREE_ROOT(&g_windows.windows); + BINTREE_FIRST(n); + while (n != NULL) { + struct window *w = &BINTREE_VALUE(n); + if (w->type == Window_Buffer) { + display_render(display, w->commands); + } + BINTREE_NEXT(n); + } + + display_render(display, g_minibuffer_window.commands); +} + +struct window_node *find_window(struct window *window) { + struct window_node *n = BINTREE_ROOT(&g_windows.windows); + BINTREE_FIRST(n); + while (n != NULL) { + struct window *w = &BINTREE_VALUE(n); + if (w == window) { + return n; + } + BINTREE_NEXT(n); + } + + return NULL; +} + +void windows_set_active(struct window *window) { + struct window_node *n = find_window(window); + if (n != NULL) { + g_windows.active = n; + } +} + +struct window *windows_get_active() { + return &BINTREE_VALUE(g_windows.active); +} + +void window_set_buffer(struct window *window, struct buffer *buffer) { + window->prev_buffer = window->buffer_view.buffer; + buffer_view_destroy(&window->buffer_view); + window->buffer_view = buffer_view_create(buffer, true, true); +} + +struct buffer *window_buffer(struct window *window) { + return window->buffer_view.buffer; +} + +struct buffer_view *window_buffer_view(struct window *window) { + return &window->buffer_view; +} + +struct buffer *window_prev_buffer(struct window *window) { + return window->prev_buffer; +} + +bool window_has_prev_buffer(struct window *window) { + return window->prev_buffer != NULL; +} + +struct buffer_location window_cursor_location(struct window *window) { + return (struct buffer_location){ + .col = window->relcol, + .line = window->relline, + }; +} +struct buffer_location window_absolute_cursor_location(struct window *window) { + return (struct buffer_location){ + .col = window->x + window->relcol, + .line = window->y + window->relline, + }; +} + +void window_close(struct window *window) { + // do not want to delete last window + if (window == root_window()) { + return; + } + + struct window_node *to_delete = find_window(window); + if (to_delete == NULL) { + return; + } + + // promote other child to parent + struct window_node *target = BINTREE_PARENT(to_delete); + struct window_node *source = BINTREE_RIGHT(target) == to_delete + ? BINTREE_LEFT(target) + : BINTREE_RIGHT(target); + + buffer_view_destroy(&window->buffer_view); + BINTREE_REMOVE(to_delete); + BINTREE_FREE_NODE(to_delete); + + BINTREE_VALUE(source).x = BINTREE_VALUE(target).x; + BINTREE_VALUE(source).y = BINTREE_VALUE(target).y; + uint32_t target_width = BINTREE_VALUE(target).width; + uint32_t target_height = BINTREE_VALUE(target).height; + + // copy the node value and set it's children as children of the target node + BINTREE_VALUE(target) = BINTREE_VALUE(source); + BINTREE_LEFT(target) = BINTREE_LEFT(source); + BINTREE_RIGHT(target) = BINTREE_RIGHT(source); + + // adopt the children + if (BINTREE_HAS_LEFT(source)) { + BINTREE_PARENT(BINTREE_LEFT(source)) = target; + } + + if (BINTREE_HAS_RIGHT(source)) { + BINTREE_PARENT(BINTREE_RIGHT(source)) = target; + } + + BINTREE_FREE_NODE(source); + + window_tree_resize(target, target_height, target_width); + BINTREE_FIRST(target); + windows_set_active(&BINTREE_VALUE(target)); +} + +void window_close_others(struct window *window) { + struct window_node *root = BINTREE_ROOT(&g_windows.windows); + + // copy window and make it suitable as a root window + struct window new_root = *window; + new_root.x = 0; + new_root.y = 0; + new_root.buffer_view = buffer_view_clone(&window->buffer_view); + new_root.width = BINTREE_VALUE(root).width; + new_root.height = BINTREE_VALUE(root).height; + + window_tree_clear(); + + // create new root window + BINTREE_SET_ROOT(&g_windows.windows, new_root); + windows_set_active(&BINTREE_VALUE(BINTREE_ROOT(&g_windows.windows))); +} + +void window_hsplit(struct window *window, struct window **new_window_a, + struct window **new_window_b) { + struct window_node *n = find_window(window); + if (n != NULL) { + struct window w = BINTREE_VALUE(n); + + if (w.type == Window_Buffer) { + struct window parent = {0}; + parent.type = Window_HSplit; + parent.x = w.x; + parent.y = w.y; + parent.width = w.width; + parent.height = w.height; + BINTREE_VALUE(n) = parent; + + /* Reuse the current window as the 'left' child, halving the height */ + w.height /= 2; + BINTREE_INSERT(n, w); + *new_window_a = &BINTREE_VALUE(BINTREE_LEFT(n)); + windows_set_active(*new_window_a); + + /* Create a new window for the split, showing the same buffer as the + * original window. + */ + struct window new_window = {0}; + new_window.type = Window_Buffer; + new_window.buffer_view = + buffer_view_create(w.buffer_view.buffer, true, true); + buffer_goto(&new_window.buffer_view, w.buffer_view.dot.line, + w.buffer_view.dot.col); + new_window.prev_buffer = w.prev_buffer; + new_window.x = w.x; + new_window.y = w.y + w.height; + new_window.width = w.width; + new_window.height = parent.height - w.height; + BINTREE_INSERT(n, new_window); + *new_window_b = &BINTREE_VALUE(BINTREE_RIGHT(n)); + } + } +} + +void window_vsplit(struct window *window, struct window **new_window_a, + struct window **new_window_b) { + struct window_node *n = find_window(window); + if (n != NULL) { + struct window w = BINTREE_VALUE(n); + + if (w.type == Window_Buffer) { + /* Create a new split container to use as parent */ + struct window parent = {0}; + parent.type = Window_VSplit; + parent.x = w.x; + parent.y = w.y; + parent.width = w.width; + parent.height = w.height; + BINTREE_VALUE(n) = parent; + + /* Reuse the current window as the 'left' child, halving the width */ + w.width /= 2; + BINTREE_INSERT(n, w); + *new_window_a = &BINTREE_VALUE(BINTREE_LEFT(n)); + windows_set_active(*new_window_a); + + /* Create a new window for the split, showing the same buffer as the + * original window. + */ + struct window new_window = {0}; + new_window.type = Window_Buffer; + new_window.buffer_view = + buffer_view_create(w.buffer_view.buffer, true, true); + buffer_goto(&new_window.buffer_view, w.buffer_view.dot.line, + w.buffer_view.dot.col); + new_window.prev_buffer = w.prev_buffer; + new_window.x = w.x + w.width; + new_window.y = w.y; + new_window.width = parent.width - w.width; + new_window.height = w.height; + BINTREE_INSERT(n, new_window); + *new_window_b = &BINTREE_VALUE(BINTREE_RIGHT(n)); + } + } +} + +void window_split(struct window *window, struct window **new_window_a, + struct window **new_window_b) { + /* The height * 2 is a horrible hack, we would need to know how big the font + actually is */ + window->height * 2 > window->width + ? window_hsplit(window, new_window_a, new_window_b) + : window_vsplit(window, new_window_a, new_window_b); +} + +struct window *windows_focus_next() { + struct window *active = windows_get_active(); + struct window_node *n = find_window(active); + BINTREE_NEXT(n); + while (n != NULL) { + struct window *w = &BINTREE_VALUE(n); + if (w->type == Window_Buffer) { + windows_set_active(w); + return w; + } + BINTREE_NEXT(n); + } + + // we have moved around + n = BINTREE_ROOT(&g_windows.windows); + BINTREE_FIRST(n); + if (n != NULL) { + windows_set_active(&BINTREE_VALUE(n)); + return &BINTREE_VALUE(n); + } + + // fall back to root + windows_set_active(root_window()); + return root_window(); +} + +struct window *windows_focus(uint32_t id) { + uint32_t curr_id = 0; + + struct window_node *n = BINTREE_ROOT(&g_windows.windows); + BINTREE_FIRST(n); + while (n != NULL) { + struct window *w = &BINTREE_VALUE(n); + if (w->type == Window_Buffer) { + if (curr_id == id) { + windows_set_active(w); + return w; + } + ++curr_id; + } + BINTREE_NEXT(n); + } + + return NULL; +} + +uint32_t window_width(struct window *window) { return window->width; } + +uint32_t window_height(struct window *window) { return window->height; } diff --git a/src/dged/window.h b/src/dged/window.h new file mode 100644 index 0000000..b3284e9 --- /dev/null +++ b/src/dged/window.h @@ -0,0 +1,48 @@ +#include <stdbool.h> +#include <stdint.h> + +#include "btree.h" + +struct command_list; +struct display; +struct keymap; +struct commands; +struct buffer; + +struct window; +struct windows; + +void windows_init(uint32_t height, uint32_t width, + struct buffer *initial_buffer, struct buffer *minibuffer); + +void windows_destroy(); +void windows_resize(uint32_t height, uint32_t width); +void windows_update(void *(*frame_alloc)(size_t), uint64_t frame_time); +void windows_render(struct display *display); + +struct window *root_window(); +struct window *minibuffer_window(); + +void windows_set_active(struct window *window); +struct window *windows_focus(uint32_t id); +struct window *windows_get_active(); +struct window *windows_focus_next(); + +void window_set_buffer(struct window *window, struct buffer *buffer); +struct buffer *window_buffer(struct window *window); +struct buffer_view *window_buffer_view(struct window *window); +struct buffer *window_prev_buffer(struct window *window); +bool window_has_prev_buffer(struct window *window); +struct buffer_location window_cursor_location(struct window *window); +struct buffer_location window_absolute_cursor_location(struct window *window); +uint32_t window_width(struct window *window); +uint32_t window_height(struct window *window); + +void window_close(struct window *window); +void window_close_others(struct window *window); +void window_split(struct window *window, struct window **new_window_a, + struct window **new_window_b); +void window_hsplit(struct window *window, struct window **new_window_a, + struct window **new_window_b); +void window_vsplit(struct window *window, struct window **new_window_a, + struct window **new_window_b); diff --git a/src/main.c b/src/main.c deleted file mode 100644 index c0b6b0c..0000000 --- a/src/main.c +++ /dev/null @@ -1,364 +0,0 @@ -#include <getopt.h> -#include <locale.h> -#include <signal.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> - -#include "allocator.h" -#include "binding.h" -#include "bits/getopt_core.h" -#include "bits/getopt_ext.h" -#include "buffer.h" -#include "buffers.h" -#include "display.h" -#include "lang.h" -#include "minibuffer.h" -#include "reactor.h" -#include "settings.h" - -struct frame_allocator frame_allocator; - -void *frame_alloc(size_t sz) { - return frame_allocator_alloc(&frame_allocator, sz); -} - -bool running = true; - -void terminate() { running = false; } - -static struct display *display = NULL; -static bool display_resized = false; -void resized() { - if (display != NULL) { - display_resize(display); - } - display_resized = true; - - signal(SIGWINCH, resized); -} - -int32_t _abort(struct command_ctx ctx, int argc, const char *argv[]) { - minibuffer_abort_prompt(); - minibuffer_echo_timeout(4, "💣 aborted"); - return 0; -} - -int32_t unimplemented_command(struct command_ctx ctx, int argc, - const char *argv[]) { - minibuffer_echo("TODO: %s is not implemented", (const char *)ctx.userdata); - return 0; -} - -int32_t exit_editor(struct command_ctx ctx, int argc, const char *argv[]) { - terminate(); - return 0; -} - -static struct command GLOBAL_COMMANDS[] = { - {.name = "find-file", .fn = find_file}, - {.name = "write-file", .fn = write_file}, - {.name = "run-command-interactive", .fn = run_interactive}, - {.name = "switch-buffer", .fn = switch_buffer}, - {.name = "abort", .fn = _abort}, - {.name = "exit", .fn = exit_editor}}; - -uint64_t calc_frame_time_ns(struct timespec *timers, uint32_t num_timer_pairs) { - uint64_t total = 0; - for (uint32_t ti = 0; ti < num_timer_pairs * 2; ti += 2) { - struct timespec *start_timer = &timers[ti]; - struct timespec *end_timer = &timers[ti + 1]; - - total += - ((uint64_t)end_timer->tv_sec * 1e9 + (uint64_t)end_timer->tv_nsec) - - ((uint64_t)start_timer->tv_sec * 1e9 + (uint64_t)start_timer->tv_nsec); - } - - return total; -} - -void usage() { printf("TODO: print usage\n"); } - -int main(int argc, char *argv[]) { - - static struct option longopts[] = {{"line", required_argument, NULL, 'l'}, - {"end", no_argument, NULL, 'e'}, - {NULL, 0, NULL, 0}}; - - char *filename = NULL; - uint32_t jumpline = 1; - bool goto_end = false; - char ch; - while ((ch = getopt_long(argc, argv, "el:", longopts, NULL)) != -1) { - switch (ch) { - case 'l': - jumpline = atoi(optarg); - break; - case 'e': - goto_end = true; - break; - default: - usage(); - return 1; - } - } - argc -= optind; - argv += optind; - - if (argc > 1) { - fprintf(stderr, "More than one file to open is not supported\n"); - return 2; - } else if (argc == 1) { - filename = strdup(argv[0]); - } - - setlocale(LC_ALL, ""); - - signal(SIGTERM, terminate); - - struct commands commands = command_registry_create(32); - settings_init(64, &commands); - languages_init(true); - buffer_static_init(&commands); - - frame_allocator = frame_allocator_create(16 * 1024 * 1024); - - // create reactor - struct reactor *reactor = reactor_create(); - - // initialize display - display = display_create(); - display_clear(display); - signal(SIGWINCH, resized); - - // init keyboard - struct keyboard kbd = keyboard_create(reactor); - - // global commands, TODO: move these, they should exist even if main does not - register_commands(&commands, GLOBAL_COMMANDS, - sizeof(GLOBAL_COMMANDS) / sizeof(GLOBAL_COMMANDS[0])); - - // keymaps - struct keymap *current_keymap = NULL; - struct keymap global_keymap = keymap_create("global", 32); - struct keymap ctrlx_map = keymap_create("c-x", 32); - struct binding global_binds[] = { - PREFIX(Ctrl, 'X', &ctrlx_map), - BINDING(Ctrl, 'G', "abort"), - BINDING(Meta, 'x', "run-command-interactive"), - }; - struct binding ctrlx_bindings[] = { - BINDING(Ctrl, 'C', "exit"), - BINDING(Ctrl, 'S', "buffer-write-to-file"), - BINDING(Ctrl, 'F', "find-file"), - BINDING(Ctrl, 'W', "write-file"), - BINDING(None, 'b', "switch-buffer"), - }; - keymap_bind_keys(&global_keymap, global_binds, - sizeof(global_binds) / sizeof(global_binds[0])); - keymap_bind_keys(&ctrlx_map, ctrlx_bindings, - sizeof(ctrlx_bindings) / sizeof(ctrlx_bindings[0])); - - struct buffers buflist = {0}; - buffers_init(&buflist, 32); - struct buffer initial_buffer = buffer_create("welcome", true); - if (filename != NULL) { - buffer_destroy(&initial_buffer); - initial_buffer = buffer_from_file(filename); - if (goto_end) { - buffer_goto_end(&initial_buffer); - } else - buffer_goto(&initial_buffer, jumpline > 0 ? jumpline - 1 : 0, 0); - } else { - const char *welcome_txt = "Welcome to the editor for datagubbar 👴\n"; - buffer_add_text(&initial_buffer, (uint8_t *)welcome_txt, - strlen(welcome_txt)); - } - - // one main window - struct window main_window = (struct window){ - .buffer = buffers_add(&buflist, initial_buffer), - .prev_buffer = NULL, - .height = display_height(display) - 1, - .width = display_width(display), - .x = 0, - .y = 0, - }; - - // and one for the minibuffer - struct buffer minibuffer = buffer_create("minibuffer", false); - - minibuffer_init(&minibuffer); - struct window minibuffer_window = (struct window){ - .buffer = &minibuffer, - .prev_buffer = NULL, - .x = 0, - .y = display_height(display) - 1, - .height = 1, - .width = display_width(display), - }; - - struct timespec buffer_begin, buffer_end, display_begin, display_end, - keyboard_begin, keyboard_end; - - uint64_t frame_time = 0; - - struct window *windows[2] = { - &minibuffer_window, - &main_window, - }; - - struct command_list *command_lists[2] = {0}; - - // TODO: not always - struct window *active_window = &main_window; - - while (running) { - - clock_gettime(CLOCK_MONOTONIC, &buffer_begin); - - if (display_resized) { - minibuffer_window.width = display_width(display); - minibuffer_window.y = display_height(display) - 1; - - main_window.height = display_height(display) - 1; - main_window.width = display_width(display); - - display_resized = false; - } - - // update windows - uint32_t dot_line = 0, dot_col = 0; - for (uint32_t windowi = 0; windowi < sizeof(windows) / sizeof(windows[0]); - ++windowi) { - struct window *win = windows[windowi]; - // TODO: better capacity - command_lists[windowi] = - command_list_create(win->height * win->width, frame_alloc, win->x, - win->y, win->buffer->name); - - uint32_t relline, relcol; - window_update_buffer(win, command_lists[windowi], frame_time, &relline, - &relcol); - - if (win == active_window) { - dot_line = relline; - dot_col = relcol; - } - } - - clock_gettime(CLOCK_MONOTONIC, &buffer_end); - - // update screen - clock_gettime(CLOCK_MONOTONIC, &display_begin); - uint32_t relline, relcol; - - display_begin_render(display); - for (uint32_t windowi = 0; windowi < sizeof(windows) / sizeof(windows[0]); - ++windowi) { - display_render(display, command_lists[windowi]); - } - display_move_cursor(display, dot_line + active_window->y, - dot_col + active_window->x); - display_end_render(display); - clock_gettime(CLOCK_MONOTONIC, &display_end); - - // this blocks for events, so if nothing has happened we block here. - reactor_update(reactor); - - clock_gettime(CLOCK_MONOTONIC, &keyboard_begin); - struct keymap *local_keymaps = NULL; - uint32_t nbuffer_keymaps = - buffer_keymaps(active_window->buffer, &local_keymaps); - struct keyboard_update kbd_upd = - keyboard_update(&kbd, reactor, frame_alloc); - - uint32_t input_data_idx = 0; - for (uint32_t ki = 0; ki < kbd_upd.nkeys; ++ki) { - struct key *k = &kbd_upd.keys[ki]; - - struct lookup_result res = {.found = false}; - if (current_keymap != NULL) { - res = lookup_key(current_keymap, 1, k, &commands); - } else { - // check first the global keymap, then the buffer ones - res = lookup_key(&global_keymap, 1, k, &commands); - if (!res.found) { - res = lookup_key(local_keymaps, nbuffer_keymaps, k, &commands); - } - } - - if (res.found) { - switch (res.type) { - case BindingType_Command: { - if (res.command == NULL) { - minibuffer_echo_timeout( - 4, "binding found for key %s but not command", k); - } else { - int32_t ec = execute_command(res.command, &commands, active_window, - &buflist, 0, NULL); - if (ec != 0 && !minibuffer_displaying()) { - minibuffer_echo_timeout(4, "command %s failed with exit code %d", - res.command->name, ec); - } - } - current_keymap = NULL; - break; - } - case BindingType_Keymap: { - char keyname[16]; - key_name(k, keyname, 16); - current_keymap = res.keymap; - minibuffer_echo("%s", current_keymap->name); - break; - } - } - } else if (k->mod == 0) { - buffer_add_text(active_window->buffer, &kbd_upd.raw[k->start], - k->end - k->start); - } else { - char keyname[16]; - key_name(k, keyname, 16); - if (current_keymap == NULL) { - minibuffer_echo_timeout(4, "key \"%s\" is not bound!", keyname); - } else { - minibuffer_echo_timeout(4, "key \"%s %s\" is not bound!", - current_keymap->name, keyname); - } - current_keymap = NULL; - } - } - clock_gettime(CLOCK_MONOTONIC, &keyboard_end); - - // calculate frame time - struct timespec timers[] = {buffer_begin, buffer_end, display_begin, - display_end, keyboard_begin, keyboard_end}; - frame_time = calc_frame_time_ns(timers, 3); - - if (minibuffer_focused()) { - active_window = &minibuffer_window; - } else { - // TODO: not this - active_window = &main_window; - } - - frame_allocator_clear(&frame_allocator); - } - - minibuffer_destroy(); - buffer_destroy(&minibuffer); - buffers_destroy(&buflist); - display_clear(display); - display_destroy(display); - keymap_destroy(&global_keymap); - keymap_destroy(&ctrlx_map); - command_registry_destroy(&commands); - reactor_destroy(reactor); - frame_allocator_destroy(&frame_allocator); - buffer_static_teardown(); - settings_destroy(); - - return 0; -} diff --git a/src/main/bindings.c b/src/main/bindings.c new file mode 100644 index 0000000..10436d6 --- /dev/null +++ b/src/main/bindings.c @@ -0,0 +1,207 @@ +#include "dged/binding.h" +#include "dged/buffer.h" +#include "dged/minibuffer.h" +#include "dged/vec.h" + +static struct keymap g_global_keymap, g_ctrlx_map, g_windows_keymap, + g_buffer_default_keymap; + +struct buffer_keymap { + struct buffer *buffer; + bool active; + struct keymap keymap; +}; + +static VEC(struct buffer_keymap) g_buffer_keymaps; + +void set_default_buffer_bindings(struct keymap *keymap) { + struct binding buffer_bindings[] = { + BINDING(Ctrl, 'B', "backward-char"), + BINDING(LEFT, "backward-char"), + BINDING(Ctrl, 'F', "forward-char"), + BINDING(RIGHT, "forward-char"), + + BINDING(Ctrl, 'P', "backward-line"), + BINDING(UP, "backward-line"), + BINDING(Ctrl, 'N', "forward-line"), + BINDING(DOWN, "forward-line"), + + BINDING(Meta, 'f', "forward-word"), + BINDING(Meta, 'b', "backward-word"), + + BINDING(Ctrl, 'A', "beginning-of-line"), + BINDING(Ctrl, 'E', "end-of-line"), + + BINDING(Ctrl, 'S', "find-next"), + BINDING(Ctrl, 'R', "find-prev"), + + BINDING(Meta, '<', "goto-beginning"), + BINDING(Meta, '>', "goto-end"), + + BINDING(Ctrl, 'V', "scroll-down"), + BINDING(Meta, 'v', "scroll-up"), + + BINDING(ENTER, "newline"), + BINDING(TAB, "indent"), + + BINDING(Ctrl, 'K', "kill-line"), + BINDING(DELETE, "delete-char"), + BINDING(Ctrl, 'D', "delete-char"), + BINDING(BACKSPACE, "backward-delete-char"), + + BINDING(Ctrl, '@', "set-mark"), + + BINDING(Ctrl, 'W', "cut"), + BINDING(Ctrl, 'Y', "paste"), + BINDING(Meta, 'y', "paste-older"), + BINDING(Meta, 'w', "copy"), + + BINDING(Ctrl, '_', "undo"), + }; + + keymap_bind_keys(keymap, buffer_bindings, + sizeof(buffer_bindings) / sizeof(buffer_bindings[0])); +} + +struct keymap *register_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); + + struct binding global_binds[] = { + PREFIX(Ctrl, 'X', &g_ctrlx_map), + BINDING(Ctrl, 'G', "abort"), + BINDING(Meta, 'x', "run-command-interactive"), + }; + + struct binding ctrlx_bindings[] = { + BINDING(Ctrl, 'C', "exit"), + BINDING(Ctrl, 'S', "buffer-write-to-file"), + BINDING(Ctrl, 'F', "find-file"), + BINDING(Ctrl, 'W', "write-file"), + BINDING(None, 'b', "switch-buffer"), + + BINDING(None, '0', "window-close"), + BINDING(None, '1', "window-close-others"), + BINDING(None, '2', "window-split-horizontal"), + BINDING(None, '3', "window-split-vertical"), + BINDING(None, 'o', "window-focus-next"), + + PREFIX(None, 'w', &g_windows_keymap), + }; + + // windows + struct binding window_subbinds[] = { + BINDING(None, '0', "window-focus-0"), + BINDING(None, '1', "window-focus-1"), + BINDING(None, '2', "window-focus-2"), + BINDING(None, '3', "window-focus-3"), + BINDING(None, '4', "window-focus-4"), + BINDING(None, '5', "window-focus-5"), + BINDING(None, '6', "window-focus-6"), + BINDING(None, '7', "window-focus-7"), + BINDING(None, '8', "window-focus-8"), + BINDING(None, '9', "window-focus-9"), + }; + + // buffers + g_buffer_default_keymap = keymap_create("buffer-default", 128); + set_default_buffer_bindings(&g_buffer_default_keymap); + + keymap_bind_keys(&g_windows_keymap, window_subbinds, + sizeof(window_subbinds) / sizeof(window_subbinds[0])); + keymap_bind_keys(&g_global_keymap, global_binds, + sizeof(global_binds) / sizeof(global_binds[0])); + keymap_bind_keys(&g_ctrlx_map, ctrlx_bindings, + sizeof(ctrlx_bindings) / sizeof(ctrlx_bindings[0])); + + VEC_INIT(&g_buffer_keymaps, 32); + + return &g_global_keymap; +} + +struct keymap *buffer_default_bindings() { + return &g_buffer_default_keymap; +} + +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 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; + } + } + + 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); + } + + if (!target->active) { + target->keymap = keymap_create("buffer-overlay-keys", 32); + target->active = true; + set_default_buffer_bindings(&target->keymap); + } + + 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; + } + } +} + +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; + } + } + + return &g_buffer_default_keymap; +} + +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() { + 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; + } + } +} diff --git a/src/main/bindings.h b/src/main/bindings.h new file mode 100644 index 0000000..d0ba27c --- /dev/null +++ b/src/main/bindings.h @@ -0,0 +1,15 @@ +#include <stdint.h> + +struct keymap; +struct buffer; +struct binding; + +struct keymap *register_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); + +void destroy_keymaps(); diff --git a/src/main/cmds.c b/src/main/cmds.c new file mode 100644 index 0000000..2041cba --- /dev/null +++ b/src/main/cmds.c @@ -0,0 +1,519 @@ +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "dged/binding.h" +#include "dged/buffer.h" +#include "dged/buffers.h" +#include "dged/command.h" +#include "dged/minibuffer.h" +#include "dged/settings.h" + +#include "bindings.h" + +int32_t _abort(struct command_ctx ctx, int argc, const char *argv[]) { + minibuffer_abort_prompt(); + minibuffer_echo_timeout(4, "💣 aborted"); + return 0; +} + +int32_t unimplemented_command(struct command_ctx ctx, int argc, + const char *argv[]) { + minibuffer_echo("TODO: %s is not implemented", (const char *)ctx.userdata); + return 0; +} + +int32_t exit_editor(struct command_ctx ctx, int argc, const char *argv[]) { + ((void (*)())ctx.userdata)(); + return 0; +} + +int32_t find_file(struct command_ctx ctx, int argc, const char *argv[]) { + const char *pth = NULL; + if (argc == 0) { + return minibuffer_prompt(ctx, "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; + } + + window_set_buffer(ctx.active_window, + buffers_add(ctx.buffers, buffer_from_file((char *)pth))); + 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) { + return minibuffer_prompt(ctx, "write to file: "); + } + + pth = argv[0]; + buffer_write_to(window_buffer(ctx.active_window), pth); + + return 0; +} + +int32_t run_interactive(struct command_ctx ctx, int argc, const char *argv[]) { + if (argc == 0) { + return minibuffer_prompt(ctx, "execute: "); + } + + struct command *cmd = lookup_command(ctx.commands, argv[0]); + if (cmd != NULL) { + return execute_command(cmd, ctx.commands, ctx.active_window, ctx.buffers, + argc - 1, argv + 1); + } else { + minibuffer_echo_timeout(4, "command %s not found", argv[0]); + return 11; + } +} + +int32_t do_switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) { + const char *bufname = argv[0]; + if (argc == 0) { + // switch back to prev buffer + if (window_has_prev_buffer(ctx.active_window)) { + bufname = window_prev_buffer(ctx.active_window)->name; + } else { + return 0; + } + } + + struct buffer *buf = buffers_find(ctx.buffers, bufname); + + if (buf == NULL) { + minibuffer_echo_timeout(4, "buffer %s not found", bufname); + return 1; + } else { + window_set_buffer(ctx.active_window, buf); + return 0; + } +} + +static struct command do_switch_buffer_cmd = {.fn = do_switch_buffer, + .name = "do-switch-buffer"}; + +int32_t switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) { + if (argc == 0) { + ctx.self = &do_switch_buffer_cmd; + if (window_has_prev_buffer(ctx.active_window)) { + return minibuffer_prompt(ctx, "buffer (default %s): ", + window_prev_buffer(ctx.active_window)->name); + } else { + return minibuffer_prompt(ctx, "buffer: "); + } + } + + return execute_command(&do_switch_buffer_cmd, ctx.commands, ctx.active_window, + ctx.buffers, argc, argv); +} + +static char *g_last_search = NULL; + +int64_t matchdist(struct match *match, struct buffer_location loc) { + struct buffer_location begin = match->begin; + + int64_t linedist = (int64_t)begin.line - (int64_t)loc.line; + int64_t coldist = (int64_t)begin.col - (int64_t)loc.col; + + return linedist * linedist + coldist * coldist; +} + +int buffer_loc_cmp(struct buffer_location loc1, struct buffer_location loc2) { + if (loc1.line < loc2.line) { + return -1; + } else if (loc1.line > loc2.line) { + return 1; + } else { + if (loc1.col < loc2.col) { + return -1; + } else if (loc1.col > loc2.col) { + return 1; + } else { + return 0; + } + } +} + +const char *search_prompt(bool reverse) { + const char *txt = "search (down): "; + if (reverse) { + txt = "search (up): "; + } + + return txt; +} + +void do_search(struct buffer_view *view, const char *pattern, bool reverse) { + struct match *matches = NULL; + uint32_t nmatches = 0; + + g_last_search = strdup(pattern); + + struct buffer_view *buffer_view = window_buffer_view(windows_get_active()); + buffer_find(buffer_view->buffer, pattern, &matches, &nmatches); + + // find the "nearest" match + if (nmatches > 0) { + struct match *closest = reverse ? &matches[nmatches - 1] : &matches[0]; + int64_t closest_dist = INT64_MAX; + for (uint32_t matchi = 0; matchi < nmatches; ++matchi) { + struct match *m = &matches[matchi]; + int res = buffer_loc_cmp(m->begin, view->dot); + int64_t dist = matchdist(m, view->dot); + if (((res < 0 && reverse) || (res > 0 && !reverse)) && + dist < closest_dist) { + closest_dist = dist; + closest = m; + } + } + buffer_goto(buffer_view, closest->begin.line, closest->begin.col); + } +} + +int32_t search_interactive(struct command_ctx ctx, int argc, + const char *argv[]) { + const char *pattern = NULL; + if (minibuffer_content().nbytes == 0) { + // recall the last search, if any + if (g_last_search != NULL) { + struct buffer_view *view = window_buffer_view(minibuffer_window()); + buffer_clear(view); + buffer_add_text(view, (uint8_t *)g_last_search, strlen(g_last_search)); + pattern = g_last_search; + } + } else { + struct text_chunk content = minibuffer_content(); + char *p = malloc(content.nbytes + 1); + memcpy(p, content.text, content.nbytes); + p[content.nbytes] = '\0'; + pattern = p; + } + + minibuffer_set_prompt(search_prompt(*(bool *)ctx.userdata)); + + if (pattern != NULL) { + // ctx.active_window would be the minibuffer window + do_search(window_buffer_view(windows_get_active()), pattern, + *(bool *)ctx.userdata); + } + return 0; +} + +static bool search_dir_backward = true; +static bool search_dir_forward = false; +static struct command search_forward_command = { + .fn = search_interactive, + .name = "search-forward", + .userdata = &search_dir_forward, +}; + +static struct command search_backward_command = { + .fn = search_interactive, + .name = "search-backward", + .userdata = &search_dir_backward, +}; + +int32_t find(struct command_ctx ctx, int argc, const char *argv[]) { + bool reverse = strcmp((char *)ctx.userdata, "backward") == 0; + if (argc == 0) { + struct binding bindings[] = { + 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])); + return minibuffer_prompt(ctx, search_prompt(reverse)); + } + + reset_minibuffer_keys(minibuffer_buffer()); + do_search(window_buffer_view(ctx.active_window), argv[0], reverse); + + return 0; +} + +int32_t timers(struct command_ctx ctx, int argc, const char *argv[]) { + + struct buffer *b = buffers_add(ctx.buffers, buffer_create("timers")); + buffer_set_readonly(b, true); + struct window *new_window_a, *new_window_b; + window_split(ctx.active_window, &new_window_a, &new_window_b); + + const char *txt = + "TODO: this is not real values!\ntimer 1: 1ms\ntimer 2: 2ms\n"; + buffer_set_text(b, (uint8_t *)txt, strlen(txt)); + + window_set_buffer(new_window_b, b); + return 0; +} + +void register_global_commands(struct commands *commands, + void (*terminate_cb)()) { + + struct command global_commands[] = { + {.name = "find-file", .fn = find_file}, + {.name = "write-file", .fn = write_file}, + {.name = "run-command-interactive", .fn = run_interactive}, + {.name = "switch-buffer", .fn = switch_buffer}, + {.name = "abort", .fn = _abort}, + {.name = "find-next", .fn = find, .userdata = "forward"}, + {.name = "find-prev", .fn = find, .userdata = "backward"}, + {.name = "timers", .fn = timers}, + {.name = "exit", .fn = exit_editor, .userdata = terminate_cb}}; + + register_commands(commands, global_commands, + sizeof(global_commands) / sizeof(global_commands[0])); +} + +#define BUFFER_WRAPCMD_POS(fn) \ + static int32_t fn##_cmd(struct command_ctx ctx, int argc, \ + const char *argv[]) { \ + fn(window_buffer_view(ctx.active_window)); \ + return 0; \ + } + +#define BUFFER_WRAPCMD(fn) \ + static int32_t fn##_cmd(struct command_ctx ctx, int argc, \ + const char *argv[]) { \ + fn(window_buffer(ctx.active_window)); \ + return 0; \ + } + +BUFFER_WRAPCMD_POS(buffer_kill_line); +BUFFER_WRAPCMD_POS(buffer_forward_delete_char); +BUFFER_WRAPCMD_POS(buffer_backward_delete_char); +BUFFER_WRAPCMD_POS(buffer_backward_char); +BUFFER_WRAPCMD_POS(buffer_backward_word); +BUFFER_WRAPCMD_POS(buffer_forward_char); +BUFFER_WRAPCMD_POS(buffer_forward_word); +BUFFER_WRAPCMD_POS(buffer_backward_line); +BUFFER_WRAPCMD_POS(buffer_forward_line); +BUFFER_WRAPCMD_POS(buffer_end_of_line); +BUFFER_WRAPCMD_POS(buffer_beginning_of_line); +BUFFER_WRAPCMD_POS(buffer_newline); +BUFFER_WRAPCMD_POS(buffer_indent); +BUFFER_WRAPCMD(buffer_to_file); +BUFFER_WRAPCMD_POS(buffer_set_mark); +BUFFER_WRAPCMD_POS(buffer_clear_mark); +BUFFER_WRAPCMD_POS(buffer_copy); +BUFFER_WRAPCMD_POS(buffer_cut); +BUFFER_WRAPCMD_POS(buffer_paste); +BUFFER_WRAPCMD_POS(buffer_paste_older); +BUFFER_WRAPCMD_POS(buffer_goto_beginning); +BUFFER_WRAPCMD_POS(buffer_goto_end); +BUFFER_WRAPCMD_POS(buffer_undo); +static int32_t buffer_view_scroll_up_cmd(struct command_ctx ctx, int argc, + const char *argv[]) { + buffer_view_scroll_up(window_buffer_view(ctx.active_window), + window_height(ctx.active_window)); + return 0; +}; +static int32_t buffer_view_scroll_down_cmd(struct command_ctx ctx, int argc, + const char *argv[]) { + buffer_view_scroll_down(window_buffer_view(ctx.active_window), + window_height(ctx.active_window)); + return 0; +}; + +void register_buffer_commands(struct commands *commands) { + + static struct command buffer_commands[] = { + {.name = "kill-line", .fn = buffer_kill_line_cmd}, + {.name = "delete-char", .fn = buffer_forward_delete_char_cmd}, + {.name = "backward-delete-char", .fn = buffer_backward_delete_char_cmd}, + {.name = "backward-char", .fn = buffer_backward_char_cmd}, + {.name = "backward-word", .fn = buffer_backward_word_cmd}, + {.name = "forward-char", .fn = buffer_forward_char_cmd}, + {.name = "forward-word", .fn = buffer_forward_word_cmd}, + {.name = "backward-line", .fn = buffer_backward_line_cmd}, + {.name = "forward-line", .fn = buffer_forward_line_cmd}, + {.name = "end-of-line", .fn = buffer_end_of_line_cmd}, + {.name = "beginning-of-line", .fn = buffer_beginning_of_line_cmd}, + {.name = "newline", .fn = buffer_newline_cmd}, + {.name = "indent", .fn = buffer_indent_cmd}, + {.name = "buffer-write-to-file", .fn = buffer_to_file_cmd}, + {.name = "set-mark", .fn = buffer_set_mark_cmd}, + {.name = "clear-mark", .fn = buffer_clear_mark_cmd}, + {.name = "copy", .fn = buffer_copy_cmd}, + {.name = "cut", .fn = buffer_cut_cmd}, + {.name = "paste", .fn = buffer_paste_cmd}, + {.name = "paste-older", .fn = buffer_paste_older_cmd}, + {.name = "goto-beginning", .fn = buffer_goto_beginning_cmd}, + {.name = "goto-end", .fn = buffer_goto_end_cmd}, + {.name = "undo", .fn = buffer_undo_cmd}, + {.name = "scroll-down", .fn = buffer_view_scroll_down_cmd}, + {.name = "scroll-up", .fn = buffer_view_scroll_up_cmd}, + }; + + register_commands(commands, buffer_commands, + sizeof(buffer_commands) / sizeof(buffer_commands[0])); +} + +static int32_t window_close_cmd(struct command_ctx ctx, int argc, + const char *argv[]) { + window_close(ctx.active_window); + return 0; +} + +static int32_t window_split_cmd(struct command_ctx ctx, int argc, + const char *argv[]) { + struct window *resa, *resb; + window_split(ctx.active_window, &resa, &resb); + return 0; +} + +static int32_t window_hsplit_cmd(struct command_ctx ctx, int argc, + const char *argv[]) { + struct window *resa, *resb; + window_hsplit(ctx.active_window, &resa, &resb); + return 0; +} + +static int32_t window_vsplit_cmd(struct command_ctx ctx, int argc, + const char *argv[]) { + struct window *resa, *resb; + window_vsplit(ctx.active_window, &resa, &resb); + return 0; +} + +static int32_t window_close_others_cmd(struct command_ctx ctx, int argc, + const char *argv[]) { + window_close_others(ctx.active_window); + return 0; +} + +static int32_t window_focus_next_cmd(struct command_ctx ctx, int argc, + const char *argv[]) { + windows_focus_next(); + return 0; +} + +static int32_t window_focus_cmd(struct command_ctx ctx, int argc, + const char *argv[]) { + if (argc == 0) { + return minibuffer_prompt(ctx, "window id: "); + } + + if (argc == 1) { + uint32_t req_id = atoi(argv[0]); + windows_focus(req_id); + } + + return 0; +} + +static int32_t window_focus_n_cmd(struct command_ctx ctx, int argc, + const char *argv[]) { + char *window_id = (char *)ctx.userdata; + const char *argv_[] = {window_id}; + return window_focus_cmd(ctx, 1, argv_); +} + +void register_window_commands(struct commands *commands) { + static struct command window_commands[] = { + {.name = "window-close", .fn = window_close_cmd}, + {.name = "window-close-others", .fn = window_close_others_cmd}, + {.name = "window-split", .fn = window_split_cmd}, + {.name = "window-split-vertical", .fn = window_vsplit_cmd}, + {.name = "window-split-horizontal", .fn = window_hsplit_cmd}, + {.name = "window-focus-next", .fn = window_focus_next_cmd}, + {.name = "window-focus", .fn = window_focus_cmd}, + {.name = "window-focus-0", .fn = window_focus_n_cmd, .userdata = "0"}, + {.name = "window-focus-1", .fn = window_focus_n_cmd, .userdata = "1"}, + {.name = "window-focus-2", .fn = window_focus_n_cmd, .userdata = "2"}, + {.name = "window-focus-3", .fn = window_focus_n_cmd, .userdata = "3"}, + {.name = "window-focus-4", .fn = window_focus_n_cmd, .userdata = "4"}, + {.name = "window-focus-5", .fn = window_focus_n_cmd, .userdata = "5"}, + {.name = "window-focus-6", .fn = window_focus_n_cmd, .userdata = "6"}, + {.name = "window-focus-7", .fn = window_focus_n_cmd, .userdata = "7"}, + {.name = "window-focus-8", .fn = window_focus_n_cmd, .userdata = "8"}, + {.name = "window-focus-9", .fn = window_focus_n_cmd, .userdata = "9"}, + }; + + register_commands(commands, window_commands, + sizeof(window_commands) / sizeof(window_commands[0])); +} + +int32_t settings_set_cmd(struct command_ctx ctx, int argc, const char *argv[]) { + if (argc == 0) { + return minibuffer_prompt(ctx, "setting: "); + } else if (argc == 1) { + // validate setting here as well for a better experience + struct setting *setting = settings_get(argv[0]); + if (setting == NULL) { + minibuffer_echo_timeout(4, "no such setting \"%s\"", argv[0]); + return 1; + } + + command_ctx_push_arg(&ctx, argv[0]); + return minibuffer_prompt(ctx, "value: "); + } + + struct setting *setting = settings_get(argv[0]); + if (setting == NULL) { + minibuffer_echo_timeout(4, "no such setting \"%s\"", argv[0]); + return 1; + } else { + const char *value = argv[1]; + struct setting_value new_value = {.type = setting->value.type}; + switch (setting->value.type) { + case Setting_Bool: + new_value.bool_value = strncmp("true", value, 4) == 0 || + strncmp("yes", value, 3) == 0 || + strncmp("on", value, 2) == 0; + break; + case Setting_Number: + new_value.number_value = atol(value); + break; + case Setting_String: + new_value.string_value = (char *)value; + break; + } + + setting_set_value(setting, new_value); + } + + return 0; +} + +int32_t settings_get_cmd(struct command_ctx ctx, int argc, const char *argv[]) { + if (argc == 0) { + return minibuffer_prompt(ctx, "setting: "); + } + + struct setting *setting = settings_get(argv[0]); + if (setting == NULL) { + minibuffer_echo_timeout(4, "no such setting \"%s\"", argv[0]); + return 1; + } else { + char buf[128]; + setting_to_string(setting, buf, 128); + minibuffer_echo("%s = %s", argv[0], buf); + } + + return 0; +} + +void register_settings_commands(struct commands *commands) { + static struct command settings_commands[] = { + {.name = "set", .fn = settings_set_cmd}, + {.name = "get", .fn = settings_get_cmd}, + }; + + register_commands(commands, settings_commands, + sizeof(settings_commands) / sizeof(settings_commands[0])); +} diff --git a/src/main/cmds.h b/src/main/cmds.h new file mode 100644 index 0000000..a392e06 --- /dev/null +++ b/src/main/cmds.h @@ -0,0 +1,10 @@ +struct commands; + +void register_global_commands(struct commands *commands, + void (*terminate_cb)()); + +void register_buffer_commands(struct commands *commands); + +void register_window_commands(struct commands *commands); + +void register_settings_commands(struct commands *commands); diff --git a/src/main/main.c b/src/main/main.c new file mode 100644 index 0000000..f13e77e --- /dev/null +++ b/src/main/main.c @@ -0,0 +1,294 @@ +#include <getopt.h> +#include <locale.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "dged/allocator.h" +#include "dged/binding.h" +#include "dged/buffer.h" +#include "dged/buffers.h" +#include "dged/display.h" +#include "dged/lang.h" +#include "dged/minibuffer.h" +#include "dged/reactor.h" +#include "dged/settings.h" + +#include "bindings.h" +#include "cmds.h" + +struct frame_allocator frame_allocator; + +void *frame_alloc(size_t sz) { + return frame_allocator_alloc(&frame_allocator, sz); +} + +bool running = true; + +void terminate() { running = false; } + +static struct display *display = NULL; +static bool display_resized = false; +void resized() { + if (display != NULL) { + display_resize(display); + } + display_resized = true; + + signal(SIGWINCH, resized); +} + +uint64_t calc_frame_time_ns(struct timespec *timers, uint32_t num_timer_pairs) { + uint64_t total = 0; + for (uint32_t ti = 0; ti < num_timer_pairs * 2; ti += 2) { + struct timespec *start_timer = &timers[ti]; + struct timespec *end_timer = &timers[ti + 1]; + + total += + ((uint64_t)end_timer->tv_sec * 1e9 + (uint64_t)end_timer->tv_nsec) - + ((uint64_t)start_timer->tv_sec * 1e9 + (uint64_t)start_timer->tv_nsec); + } + + return total; +} + +#define DECLARE_TIMER(timer) struct timespec timer##_begin, timer##_end +#define TIMED_SCOPE_BEGIN(timer) clock_gettime(CLOCK_MONOTONIC, &timer##_begin) +#define TIMED_SCOPE_END(timer) clock_gettime(CLOCK_MONOTONIC, &timer##_end) + +void usage() { printf("TODO: print usage\n"); } + +int main(int argc, char *argv[]) { + + static struct option longopts[] = {{"line", required_argument, NULL, 'l'}, + {"end", no_argument, NULL, 'e'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0}}; + + char *filename = NULL; + uint32_t jumpline = 1; + bool goto_end = false; + char ch; + while ((ch = getopt_long(argc, argv, "hel:", longopts, NULL)) != -1) { + switch (ch) { + case 'l': + jumpline = atoi(optarg); + break; + case 'e': + goto_end = true; + break; + case 'h': + usage(); + return 0; + break; + default: + usage(); + return 1; + } + } + argc -= optind; + argv += optind; + + if (argc > 1) { + fprintf(stderr, "More than one file to open is not supported\n"); + return 2; + } else if (argc == 1) { + filename = strdup(argv[0]); + } + + setlocale(LC_ALL, ""); + + signal(SIGTERM, terminate); + + struct commands commands = command_registry_create(32); + + settings_init(64); + languages_init(true); + buffer_static_init(); + + frame_allocator = frame_allocator_create(16 * 1024 * 1024); + + struct reactor *reactor = reactor_create(); + + display = display_create(); + 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(); + + struct buffers buflist = {0}; + buffers_init(&buflist, 32); + struct buffer initial_buffer = buffer_create("welcome"); + if (filename != NULL) { + buffer_destroy(&initial_buffer); + initial_buffer = buffer_from_file(filename); + } else { + const char *welcome_txt = "Welcome to the editor for datagubbar 👴\n"; + buffer_set_text(&initial_buffer, (uint8_t *)welcome_txt, + strlen(welcome_txt)); + } + + 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); + struct window *active = windows_get_active(); + if (goto_end) { + buffer_goto_end(window_buffer_view(active)); + } else { + buffer_goto(window_buffer_view(active), jumpline > 0 ? jumpline - 1 : 0, 0); + } + + DECLARE_TIMER(buffer); + DECLARE_TIMER(display); + DECLARE_TIMER(keyboard); + + uint64_t frame_time = 0; + static char keyname[64] = {0}; + static uint32_t nkeychars = 0; + + while (running) { + + if (display_resized) { + windows_resize(display_height(display), display_width(display)); + display_resized = false; + } + + /* Update all windows together with the buffers in them. */ + TIMED_SCOPE_BEGIN(buffer); + windows_update(frame_alloc, frame_time); + TIMED_SCOPE_END(buffer); + + struct window *active_window = windows_get_active(); + if (minibuffer_focused()) { + active_window = minibuffer_window(); + } + + /* Update the screen by flushing command lists collected from updating the + * buffers. + */ + TIMED_SCOPE_BEGIN(display); + display_begin_render(display); + windows_render(display); + struct buffer_location cursor = + window_absolute_cursor_location(active_window); + display_move_cursor(display, cursor.line, cursor.col); + display_end_render(display); + TIMED_SCOPE_END(display); + + /* This blocks for events, so if nothing has happened we block here and let + * the CPU do something more useful than updating this narcissistic editor. + * This is also the reason that there is no timed scope around this, it + * simply makes no sense. + */ + reactor_update(reactor); + + TIMED_SCOPE_BEGIN(keyboard); + struct keyboard_update kbd_upd = + keyboard_update(&kbd, reactor, frame_alloc); + + uint32_t input_data_idx = 0; + for (uint32_t ki = 0; ki < kbd_upd.nkeys; ++ki) { + struct key *k = &kbd_upd.keys[ki]; + + struct lookup_result res = {.found = false}; + 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); + } + } + + if (res.found) { + switch (res.type) { + case BindingType_Command: { + if (res.command == NULL) { + minibuffer_echo_timeout( + 4, "binding found for key %s but not command", k); + } else { + int32_t ec = execute_command(res.command, &commands, active_window, + &buflist, 0, NULL); + if (ec != 0 && !minibuffer_displaying()) { + minibuffer_echo_timeout(4, "command %s failed with exit code %d", + res.command->name, ec); + } + } + current_keymap = NULL; + nkeychars = 0; + keyname[0] = '\0'; + break; + } + case BindingType_Keymap: { + if (nkeychars > 0 && nkeychars < 64) { + keyname[nkeychars] = '-'; + ++nkeychars; + } + + if (nkeychars < 64) { + nkeychars += key_name(k, keyname + nkeychars, 64 - nkeychars); + minibuffer_echo("%s", keyname); + } + + current_keymap = res.keymap; + break; + } + } + } else if (k->mod == 0) { + buffer_add_text(window_buffer_view(active_window), + &kbd_upd.raw[k->start], k->end - k->start); + } else { + char keyname[16]; + key_name(k, keyname, 16); + if (current_keymap == NULL) { + minibuffer_echo_timeout(4, "key \"%s\" is not bound!", keyname); + } else { + minibuffer_echo_timeout(4, "key \"%s %s\" is not bound!", + current_keymap->name, keyname); + } + current_keymap = NULL; + nkeychars = 0; + keyname[0] = '\0'; + } + } + TIMED_SCOPE_END(keyboard); + + // calculate frame time + struct timespec timers[] = {buffer_begin, buffer_end, display_begin, + display_end, keyboard_begin, keyboard_end}; + frame_time = calc_frame_time_ns(timers, 3); + frame_allocator_clear(&frame_allocator); + } + + windows_destroy(); + minibuffer_destroy(); + buffer_destroy(&minibuffer); + buffers_destroy(&buflist); + display_clear(display); + display_destroy(display); + destroy_keymaps(); + command_registry_destroy(&commands); + reactor_destroy(reactor); + frame_allocator_destroy(&frame_allocator); + buffer_static_teardown(); + settings_destroy(); + + return 0; +} diff --git a/src/window.c b/src/window.c deleted file mode 100644 index 10ded5e..0000000 --- a/src/window.c +++ /dev/null @@ -1,14 +0,0 @@ -#include "buffer.h" -#include "display.h" - -void window_update_buffer(struct window *window, struct command_list *commands, - uint64_t frame_time, uint32_t *relline, - uint32_t *relcol) { - buffer_update(window->buffer, window->width, window->height, commands, - frame_time, relline, relcol); -} - -void window_set_buffer(struct window *window, struct buffer *buffer) { - window->prev_buffer = window->buffer; - window->buffer = buffer; -} diff --git a/src/window.h b/src/window.h deleted file mode 100644 index 14e041e..0000000 --- a/src/window.h +++ /dev/null @@ -1,18 +0,0 @@ -#include <stdint.h> - -struct command_list; - -struct window { - uint32_t x; - uint32_t y; - uint32_t width; - uint32_t height; - struct buffer *buffer; - struct buffer *prev_buffer; -}; - -void window_update_buffer(struct window *window, struct command_list *commands, - uint64_t frame_time, uint32_t *relline, - uint32_t *relcol); - -void window_set_buffer(struct window *window, struct buffer *buffer); |
