summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorAlbert Cervin <albert@acervin.com>2023-04-06 23:23:46 +0200
committerAlbert Cervin <albert@acervin.com>2023-05-01 22:19:14 +0200
commita123725a12e948d78badb2cb686d38548f1c633b (patch)
treec92c46134ef5536fbbf3bf08983c4f0dea1aaf58 /src/main
parentb5ed4cf757afc50afb6ac499eee7b87a2648fa4c (diff)
downloaddged-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.c207
-rw-r--r--src/main/bindings.h15
-rw-r--r--src/main/cmds.c519
-rw-r--r--src/main/cmds.h10
-rw-r--r--src/main/main.c294
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;
+}