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/main | |
| 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/main')
| -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 |
5 files changed, 1045 insertions, 0 deletions
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; +} |
