summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile26
-rw-r--r--src/command.c175
-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.h113
-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.c79
-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.c445
-rw-r--r--src/dged/window.h48
-rw-r--r--src/main.c364
-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
-rw-r--r--src/window.c14
-rw-r--r--src/window.h18
-rw-r--r--test/allocator.c4
-rw-r--r--test/buffer.c43
-rw-r--r--test/command.c6
-rw-r--r--test/container.c102
-rw-r--r--test/fake-reactor.h3
-rw-r--r--test/keyboard.c11
-rw-r--r--test/main.c5
-rw-r--r--test/minibuffer.c22
-rw-r--r--test/settings.c15
-rw-r--r--test/test.h1
-rw-r--r--test/text.c12
-rw-r--r--test/undo.c7
-rw-r--r--test/utf8.c9
57 files changed, 2482 insertions, 1222 deletions
diff --git a/Makefile b/Makefile
index 6ba7cca..7c6a4af 100644
--- a/Makefile
+++ b/Makefile
@@ -7,15 +7,16 @@ default: dged
build:
mkdir -p build
-SOURCES = src/binding.c src/buffer.c src/command.c src/display.c \
- src/keyboard.c src/minibuffer.c src/text.c \
- src/utf8.c src/buffers.c src/window.c src/allocator.c src/undo.c \
- src/settings.c src/lang.c
+SOURCES = src/dged/binding.c src/dged/buffer.c src/dged/command.c src/dged/display.c \
+ src/dged/keyboard.c src/dged/minibuffer.c src/dged/text.c \
+ src/dged/utf8.c src/dged/buffers.c src/dged/window.c src/dged/allocator.c src/dged/undo.c \
+ src/dged/settings.c src/dged/lang.c
+
+MAIN_SOURCES = src/main/main.c src/main/cmds.c src/main/bindings.c
-DGED_SOURCES = $(SOURCES) src/main.c
TEST_SOURCES = test/assert.c test/buffer.c test/text.c test/utf8.c test/main.c \
test/command.c test/keyboard.c test/fake-reactor.c test/allocator.c \
- test/minibuffer.c test/undo.c test/settings.c
+ test/minibuffer.c test/undo.c test/settings.c test/container.c
prefix ?= "/usr"
@@ -24,14 +25,15 @@ prefix ?= "/usr"
UNAME_S != uname -s | tr '[:upper:]' '[:lower:]'
-CFLAGS = -Werror -g -std=c99 -I $(.CURDIR)/src
+CFLAGS = -Werror -g -std=c99 -I $(.CURDIR)/src -I $(.CURDIR)/src/main
DEPS = $(DGED_SOURCES:.c=.d) $(TEST_SOURCES:.c=.d)
OBJS = $(SOURCES:.c=.o)
+MAIN_OBJS = $(MAIN_SOURCES:.c=.o)
TEST_OBJS = $(TEST_SOURCES:.c=.o)
-FILES = $(DEPS) $(DGED_SOURCES:.c=.o) dged libdged.a $(TEST_OBJS)
+FILES = $(DEPS) $(MAIN_OBJS) $(OBJS) dged libdged.a $(TEST_OBJS)
.sinclude "$(UNAME_S).mk"
@@ -45,8 +47,8 @@ FILES = $(DEPS) $(DGED_SOURCES:.c=.o) dged libdged.a $(TEST_OBJS)
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $< -o $@
-dged: src/main.o libdged.a
- $(CC) $(LDFLAGS) src/main.o libdged.a -o dged
+dged: $(MAIN_OBJS) libdged.a
+ $(CC) $(LDFLAGS) $(MAIN_OBJS) libdged.a -o dged
libdged.a: $(OBJS) $(PLATFORM_OBJS)
$(AR) -rc libdged.a $(OBJS) $(PLATFORM_OBJS)
@@ -55,7 +57,7 @@ run-tests: $(TEST_OBJS) $(OBJS)
$(CC) $(LDFLAGS) $(TEST_OBJS) $(OBJS) -o run-tests
check: run-tests
- clang-format --dry-run --Werror $(DGED_SOURCES:%.c=../%.c) $(TEST_SOURCES:%c=../%c)
+ clang-format --dry-run --Werror $(SOURCES:%.c=../%.c) $(MAIN_SOURCES:%.c=../%.c) $(TEST_SOURCES:%c=../%c)
./run-tests
run: dged
@@ -68,7 +70,7 @@ debug-tests: run-tests
gdb ./run-tests
format:
- clang-format -i $(DGED_SOURCES) $(TEST_SOURCES)
+ clang-format -i $(SOURCES:%.c=../%.c) $(MAIN_SOURCES:%.c=../%.c) $(TEST_SOURCES:%c=../%c)
clean:
rm -f $(FILES)
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, &current);
- 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 = &copy_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);
diff --git a/test/allocator.c b/test/allocator.c
index ca1a7a0..fe01334 100644
--- a/test/allocator.c
+++ b/test/allocator.c
@@ -1,8 +1,8 @@
+#include "dged/allocator.h"
+
#include "assert.h"
#include "test.h"
-#include "allocator.h"
-
void test_frame_allocator() {
struct frame_allocator fa = frame_allocator_create(128);
diff --git a/test/buffer.c b/test/buffer.c
index 38ce468..a4ac754 100644
--- a/test/buffer.c
+++ b/test/buffer.c
@@ -1,45 +1,46 @@
-#include "assert.h"
-#include "test.h"
+#include <string.h>
-#include "buffer.h"
+#include "dged/buffer.h"
-#include <string.h>
+#include "assert.h"
+#include "test.h"
void test_move() {
- struct buffer b = buffer_create("test-buffer", false);
- ASSERT(b.dot.col == 0 && b.dot.line == 0,
+ struct buffer b = buffer_create("test-buffer");
+ struct buffer_view v = buffer_view_create(&b, false, false);
+ ASSERT(v.dot.col == 0 && v.dot.line == 0,
"Expected dot to be at buffer start");
// make sure we cannot move now
- buffer_backward_char(&b);
- buffer_backward_line(&b);
- ASSERT(b.dot.col == 0 && b.dot.line == 0,
+ buffer_backward_char(&v);
+ buffer_backward_line(&v);
+ ASSERT(v.dot.col == 0 && v.dot.line == 0,
"Expected to not be able to move backward in empty buffer");
- buffer_forward_char(&b);
- buffer_forward_line(&b);
- ASSERT(b.dot.col == 0 && b.dot.line == 0,
+ buffer_forward_char(&v);
+ buffer_forward_line(&v);
+ ASSERT(v.dot.col == 0 && v.dot.line == 0,
"Expected to not be able to move forward in empty buffer");
// add some text and try again
const char *txt = "testing movement";
- int lineindex = buffer_add_text(&b, (uint8_t *)txt, strlen(txt));
+ int lineindex = buffer_add_text(&v, (uint8_t *)txt, strlen(txt));
ASSERT(lineindex + 1 == 1, "Expected buffer to have one line");
- buffer_beginning_of_line(&b);
- buffer_forward_char(&b);
- ASSERT(b.dot.col == 1 && b.dot.line == 0,
+ buffer_beginning_of_line(&v);
+ buffer_forward_char(&v);
+ ASSERT(v.dot.col == 1 && v.dot.line == 0,
"Expected to be able to move forward by one char");
// now we have two lines
const char *txt2 = "\n";
- int lineindex2 = buffer_add_text(&b, (uint8_t *)txt2, strlen(txt2));
+ int lineindex2 = buffer_add_text(&v, (uint8_t *)txt2, strlen(txt2));
ASSERT(lineindex2 + 1 == 2, "Expected buffer to have two lines");
- buffer_backward_line(&b);
- buffer_beginning_of_line(&b);
- buffer_backward_char(&b);
+ buffer_backward_line(&v);
+ buffer_beginning_of_line(&v);
+ buffer_backward_char(&v);
ASSERT(
- b.dot.col == 0 && b.dot.line == 0,
+ v.dot.col == 0 && v.dot.line == 0,
"Expected to not be able to move backwards when at beginning of buffer");
buffer_destroy(&b);
diff --git a/test/command.c b/test/command.c
index e09cf35..8db02e0 100644
--- a/test/command.c
+++ b/test/command.c
@@ -1,9 +1,9 @@
#include "assert.h"
#include "test.h"
-#include "command.h"
-#include "hash.h"
-#include "hashmap.h"
+#include "dged/command.h"
+#include "dged/hash.h"
+#include "dged/hashmap.h"
void test_command_registry_create() {
struct commands cmds = command_registry_create(10);
diff --git a/test/container.c b/test/container.c
new file mode 100644
index 0000000..8be7fc9
--- /dev/null
+++ b/test/container.c
@@ -0,0 +1,102 @@
+#include <stdint.h>
+
+#include "dged/btree.h"
+
+#include "assert.h"
+#include "test.h"
+
+void test_empty_bintree() {
+ BINTREE_ENTRY_TYPE(node, int);
+ BINTREE(node) tree;
+
+ BINTREE_INIT(&tree);
+
+ ASSERT(BINTREE_ROOT(&tree) == NULL,
+ "Expected root of tree to be NULL initially");
+ struct node *res = BINTREE_ROOT(&tree);
+ BINTREE_NEXT(res);
+ ASSERT(res == NULL, "Expected there to be no \"next\" initially");
+ struct node *n = BINTREE_ROOT(&tree);
+ BINTREE_FIRST(n);
+ bool loop_empty = true;
+ while (n != NULL) {
+ loop_empty = false;
+ BINTREE_NEXT(n);
+ }
+
+ ASSERT(loop_empty, "Expected an empty tree to yield an empty loop");
+
+ BINTREE_FREE_NODES(BINTREE_ROOT(&tree), node);
+}
+
+void test_bintree_iter() {
+ BINTREE_ENTRY_TYPE(node, char);
+ BINTREE(node) tree;
+ BINTREE_INIT(&tree);
+
+ // insert at the root
+ BINTREE_SET_ROOT(&tree, 'a');
+
+ struct node *root = BINTREE_ROOT(&tree);
+ ASSERT(BINTREE_VALUE(root) == 'a', "Expected root to have its value");
+
+ // insert first child (left)
+ BINTREE_INSERT(root, 'b');
+ ASSERT(BINTREE_VALUE(root->left) == 'b',
+ "Expected first child to have its value");
+
+ // insert second child
+ BINTREE_INSERT(root, 'c');
+ ASSERT(BINTREE_VALUE(root->right) == 'c',
+ "Expected second child to have its value");
+
+ // insert first child of second child
+ struct node *right = BINTREE_RIGHT(root);
+ BINTREE_INSERT(right, 'd');
+
+ // iterate
+ char chars[4] = {0};
+ uint32_t nchars = 0;
+ struct node *n = BINTREE_ROOT(&tree);
+ BINTREE_FIRST(n);
+ while (n != NULL) {
+ chars[nchars] = BINTREE_VALUE(n);
+ ++nchars;
+ BINTREE_NEXT(n);
+ }
+
+ ASSERT(nchars == 4, "Expected tree to have 4 nodes after insertions");
+ ASSERT(chars[0] == 'b' && chars[1] == 'a' && chars[2] == 'd' &&
+ chars[3] == 'c',
+ "Expected tree to have been traversed correctly");
+
+ // find
+ struct node *res = NULL;
+ BINTREE_FIND(&tree, 'b', res);
+ ASSERT(res != NULL && res == BINTREE_LEFT(root),
+ "Expected existing value to be found");
+ ASSERT(BINTREE_VALUE(res) == 'b',
+ "Expected found node to contain the searched for value");
+
+ // remove
+ struct node *to_remove = BINTREE_LEFT(right);
+ BINTREE_REMOVE(to_remove);
+
+ nchars = 0;
+ n = BINTREE_ROOT(&tree);
+ BINTREE_FIRST(n);
+ while (n != NULL) {
+ ++nchars;
+ BINTREE_NEXT(n);
+ }
+
+ ASSERT(nchars == 3, "Expected three nodes to remain after removing one");
+ BINTREE_FREE_NODES(to_remove, node);
+
+ BINTREE_FREE_NODES(root, node);
+}
+
+void run_container_tests() {
+ run_test(test_empty_bintree);
+ run_test(test_bintree_iter);
+}
diff --git a/test/fake-reactor.h b/test/fake-reactor.h
index 04d8306..33d1fbc 100644
--- a/test/fake-reactor.h
+++ b/test/fake-reactor.h
@@ -1,7 +1,8 @@
-#include "reactor.h"
#include <stdbool.h>
#include <stdint.h>
+#include "dged/reactor.h"
+
struct fake_reactor_impl {
bool (*poll_event)(void *userdata, uint32_t ev_id);
uint32_t (*register_interest)(void *userdata, int fd, enum interest interest);
diff --git a/test/keyboard.c b/test/keyboard.c
index a76d2a7..1ddbba5 100644
--- a/test/keyboard.c
+++ b/test/keyboard.c
@@ -1,12 +1,13 @@
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "dged/keyboard.h"
+
#include "assert.h"
#include "fake-reactor.h"
#include "test.h"
-#include "keyboard.h"
-#include "unistd.h"
-#include <stdlib.h>
-#include <string.h>
-
struct call_count {
uint32_t poll;
uint32_t reg;
diff --git a/test/main.c b/test/main.c
index 01b2645..4c241b3 100644
--- a/test/main.c
+++ b/test/main.c
@@ -4,8 +4,6 @@
#include <stdlib.h>
#include <time.h>
-#include "lang.h"
-#include "settings.h"
#include "test.h"
void handle_abort() { exit(1); }
@@ -44,6 +42,9 @@ int main() {
printf("\n 📓 \x1b[1;36mRunning settings tests...\x1b[0m\n");
run_settings_tests();
+ printf("\n 🎁 \x1b[1;36mRunning container tests...\x1b[0m\n");
+ run_container_tests();
+
struct timespec elapsed;
clock_gettime(CLOCK_MONOTONIC, &elapsed);
uint64_t elapsed_nanos =
diff --git a/test/minibuffer.c b/test/minibuffer.c
index 4e2b7f1..a6c7f96 100644
--- a/test/minibuffer.c
+++ b/test/minibuffer.c
@@ -2,12 +2,12 @@
#include "stdlib.h"
#include "test.h"
-#include "allocator.h"
-#include "buffer.h"
-#include "command.h"
-#include "display.h"
-#include "minibuffer.h"
-#include "settings.h"
+#include "dged/allocator.h"
+#include "dged/buffer.h"
+#include "dged/command.h"
+#include "dged/display.h"
+#include "dged/minibuffer.h"
+#include "dged/settings.h"
static struct buffer b = {0};
@@ -17,9 +17,8 @@ void *alloc_fn(size_t sz) { return frame_allocator_alloc(g_alloc, sz); }
void init() {
if (b.name == NULL) {
- struct commands commands = command_registry_create(10);
- settings_init(10, &commands);
- b = buffer_create("minibuffer", false);
+ settings_init(10);
+ b = buffer_create("minibuffer");
}
minibuffer_init(&b);
@@ -34,6 +33,7 @@ void destroy() {
void test_minibuffer_echo() {
uint32_t relline, relcol;
+ struct buffer_view view = buffer_view_create(&b, false, false);
// TODO: how to clear this?
struct frame_allocator alloc = frame_allocator_create(1024);
@@ -50,11 +50,13 @@ void test_minibuffer_echo() {
ASSERT(minibuffer_displaying(), "Minibuffer should now have text to display");
minibuffer_clear();
+ buffer_update(&view, 100, 1, list, 0, &relline, &relcol);
ASSERT(!minibuffer_displaying(),
"Minibuffer should have nothing to display after clearing");
minibuffer_echo_timeout(0, "You will not see me");
- buffer_update(&b, 100, 1, list, 0, &relline, &relcol);
+
+ buffer_update(&view, 100, 1, list, 0, &relline, &relcol);
ASSERT(!minibuffer_displaying(),
"A zero timeout echo should be cleared after first update");
diff --git a/test/settings.c b/test/settings.c
index dc448a5..b1fdc9a 100644
--- a/test/settings.c
+++ b/test/settings.c
@@ -1,14 +1,12 @@
-#include "assert.h"
-#include "test.h"
+#include <stdlib.h>
-#include "command.h"
-#include "settings.h"
+#include "dged/settings.h"
-#include <stdlib.h>
+#include "assert.h"
+#include "test.h"
void test_get() {
- struct commands commands = command_registry_create(10);
- settings_init(10, &commands);
+ settings_init(10);
settings_register_setting(
"my.setting",
(struct setting_value){.type = Setting_Bool, .bool_value = false});
@@ -40,8 +38,7 @@ void test_get() {
}
void test_set() {
- struct commands commands = command_registry_create(10);
- settings_init(10, &commands);
+ settings_init(10);
settings_register_setting(
"my.setting",
(struct setting_value){.type = Setting_Bool, .bool_value = false});
diff --git a/test/test.h b/test/test.h
index 047bbfb..b01fde4 100644
--- a/test/test.h
+++ b/test/test.h
@@ -18,5 +18,6 @@ void run_keyboard_tests();
void run_allocator_tests();
void run_minibuffer_tests();
void run_settings_tests();
+void run_container_tests();
#endif
diff --git a/test/text.c b/test/text.c
index cb18df5..3092dcc 100644
--- a/test/text.c
+++ b/test/text.c
@@ -1,13 +1,13 @@
-#include "assert.h"
-#include "stdio.h"
-#include "test.h"
-
-#include "text.h"
-
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
+#include "dged/text.h"
+
+#include "assert.h"
+#include "stdio.h"
+#include "test.h"
+
void assert_line_eq(struct text_chunk line, const char *txt, const char *msg) {
ASSERT(strncmp((const char *)line.text, txt, line.nbytes) == 0, msg);
}
diff --git a/test/undo.c b/test/undo.c
index d0e2fdb..a4b6ad0 100644
--- a/test/undo.c
+++ b/test/undo.c
@@ -1,9 +1,10 @@
+#include <stdlib.h>
+
+#include "dged/undo.h"
+
#include "assert.h"
#include "test.h"
-#include "undo.h"
-#include <stdlib.h>
-
void test_undo_insert() {
struct undo_stack undo;
diff --git a/test/utf8.c b/test/utf8.c
index 88e6a8c..d67c409 100644
--- a/test/utf8.c
+++ b/test/utf8.c
@@ -1,9 +1,10 @@
-#include "utf8.h"
+#include <string.h>
+#include <wchar.h>
+
+#include "dged/utf8.h"
+
#include "assert.h"
#include "test.h"
-#include "wchar.h"
-
-#include <string.h>
void test_nchars_nbytes() {
ASSERT(utf8_nchars((uint8_t *)"👴", strlen("👴")) == 1,