summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlbert Cervin <albert@acervin.com>2023-01-26 13:07:07 +0100
committerAlbert Cervin <albert@acervin.com>2023-01-26 13:07:07 +0100
commite65158a0326108d1fc724ee683b7fa900ef2671a (patch)
tree9bad30b377a326e0d0e3101c4f96228ae7a41673
parent9a2b138a03e27d0f04101fe6ae3977d79518c513 (diff)
downloaddged-e65158a0326108d1fc724ee683b7fa900ef2671a.tar.gz
dged-e65158a0326108d1fc724ee683b7fa900ef2671a.tar.xz
dged-e65158a0326108d1fc724ee683b7fa900ef2671a.zip
More tests and documentation
Also, split out platform-specific parts and add mocks for tests.
-rw-r--r--Doxyfile12
-rw-r--r--GNUmakefile2
-rw-r--r--Makefile2
-rw-r--r--common.mk56
-rw-r--r--linux.mk3
-rw-r--r--src/allocator.h51
-rw-r--r--src/buffer.c4
-rw-r--r--src/command.c5
-rw-r--r--src/command.h54
-rw-r--r--src/display.c8
-rw-r--r--src/display.h3
-rw-r--r--src/keyboard.c86
-rw-r--r--src/keyboard.h104
-rw-r--r--src/main.c34
-rw-r--r--src/reactor-linux.c (renamed from src/reactor.c)21
-rw-r--r--src/reactor.h7
-rw-r--r--targets.mk43
-rw-r--r--test/command.c22
-rw-r--r--test/fake-reactor.c47
-rw-r--r--test/fake-reactor.h13
-rw-r--r--test/keyboard.c208
-rw-r--r--test/main.c17
-rw-r--r--test/test.h1
23 files changed, 648 insertions, 155 deletions
diff --git a/Doxyfile b/Doxyfile
index bc61b49..7d47437 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -827,7 +827,7 @@ EXCLUDE_SYMLINKS = NO
#
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories for example use the pattern */test/*
-
+
EXCLUDE_PATTERNS =
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
@@ -921,13 +921,6 @@ FILTER_SOURCE_FILES = NO
FILTER_SOURCE_PATTERNS =
-# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
-# is part of the input, its contents will be placed on the main page
-# (index.html). This can be useful if you have a project on for instance GitHub
-# and want to reuse the introduction page also for the doxygen output.
-
-USE_MDFILE_AS_MAINPAGE =
-
# The Fortran standard specifies that for fixed formatted Fortran code all
# characters from position 72 are to be considered as comment. A common
# extension is to allow longer lines before the automatic comment starts. The
@@ -1680,3 +1673,6 @@ EXTERNAL_SEARCH_ID =
EXTRA_SEARCH_MAPPINGS =
GENERATE_LATEX = NO
+
+INPUT += README.md
+USE_MDFILE_AS_MAINPAGE = README.md
diff --git a/GNUmakefile b/GNUmakefile
index 3671e2f..6b58971 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -3,4 +3,6 @@ include common.mk
sinclude $(UNAME_S).mk
+include targets.mk
+
include $(DEPS)
diff --git a/Makefile b/Makefile
index e154497..2e50ae6 100644
--- a/Makefile
+++ b/Makefile
@@ -4,6 +4,8 @@
.sinclude "$(UNAME_S).mk"
+.include "targets.mk"
+
# in this case we need a separate depend target
depend: $(DEPS)
@:
diff --git a/common.mk b/common.mk
index 7efc408..eeb7e0a 100644
--- a/common.mk
+++ b/common.mk
@@ -4,12 +4,12 @@
default: dged
SOURCES = src/binding.c src/buffer.c src/command.c src/display.c \
- src/keyboard.c src/minibuffer.c src/reactor.c src/text.c \
+ src/keyboard.c src/minibuffer.c src/text.c \
src/utf8.c src/buffers.c src/window.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/command.c test/keyboard.c test/fake-reactor.c
prefix != if [ -n "$$prefix" ]; then echo "$$prefix"; else echo "/usr"; fi
@@ -20,59 +20,9 @@ UNAME_S != uname -s | tr '[:upper:]' '[:lower:]'
CFLAGS = -Werror -g -std=c99 -I ./src
-# dependency generation
-.c.d:
- $(CC) -MM $(CFLAGS) -MT $*.o $< > $@
- @sed -i 's,\($*\)\.o[ :]*,\1.o $@ : ,g' $@
-
-.c.o:
- $(CC) $(CFLAGS) -c $< -o $@
-
DEPS = $(DGED_SOURCES:.c=.d) $(TEST_SOURCES:.c=.d)
-FILES += $(DEPS)
OBJS = $(SOURCES:.c=.o)
-FILES += $(DGED_SOURCES:.c=.o)
-
-dged: src/main.o libdged.a
- $(CC) $(LDFLAGS) src/main.o libdged.a -o dged
-
-FILES += dged
-
-libdged.a: $(OBJS)
- $(AR) -rc libdged.a $(OBJS)
-
-FILES += libdged.a
-
TEST_OBJS = $(TEST_SOURCES:.c=.o)
-run-tests: $(TEST_OBJS) libdged.a
- $(CC) $(LDFLAGS) $(TEST_OBJS) libdged.a -o run-tests
-
-FILES += $(TEST_OBJS)
-
-check: run-tests
- ./run-tests
-
-run: dged
- ./dged
-
-debug: dged
- gdb ./dged
-
-debug-tests: run-tests
- gdb ./run-tests
-
-clean:
- rm -f $(FILES)
- rm -rf docs
-
-install: dged
- install -d $(prefix)/bin
- install -m 755 dged $(prefix)/bin/dged
-
- install -d $(prefix)/share/man/man1
- install -m 644 dged.1 $(prefix)/share/man/man1/dged.1
-
-docs:
- doxygen Doxyfile
+FILES = $(DEPS) $(DGED_SOURCES:.c=.o) dged libdged.a $(TEST_OBJS)
diff --git a/linux.mk b/linux.mk
index 3db7a1f..b7c193a 100644
--- a/linux.mk
+++ b/linux.mk
@@ -1 +1,4 @@
CFLAGS += -DLINUX -D_XOPEN_SOURCE=700
+
+PLATFORM_SOURCES += src/reactor-linux.c
+PLATFORM_OBJS = $(PLATFORM_SOURCES:.c=.o)
diff --git a/src/allocator.h b/src/allocator.h
new file mode 100644
index 0000000..d89bd29
--- /dev/null
+++ b/src/allocator.h
@@ -0,0 +1,51 @@
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/**
+ * Simple bump allocator that can be used for
+ * allocations with a frame lifetime.
+ */
+struct frame_allocator {
+ uint8_t *buf;
+ size_t offset;
+ size_t capacity;
+};
+
+/**
+ * Create a new frame allocator
+ *
+ * @param capacity The capacity in bytes of the frame allocator
+ * @returns The frame allocator
+ */
+struct frame_allocator frame_allocator_create(size_t capacity) {
+ return (struct frame_allocator){
+ .capacity = capacity, .offset = 0, .buf = (uint8_t *)malloc(capacity)};
+}
+
+/**
+ * Allocate memory in this @ref frame_allocator "frame allocator"
+ *
+ * @param alloc The allocator to allocate in
+ * @param sz The size in bytes to allocate.
+ * @returns void* representing the start of the allocated region on success,
+ * NULL on failure.
+ */
+void *frame_allocator_alloc(struct frame_allocator *alloc, size_t sz) {
+ if (alloc->offset + sz > alloc->capacity) {
+ return NULL;
+ }
+
+ void *mem = alloc->buf + alloc->offset;
+ alloc->offset += sz;
+
+ return mem;
+}
+
+/**
+ * Clear this @ref frame_allocator "frame allocator".
+ *
+ * This does not free any memory, but simply resets the offset to 0.
+ * @param alloc The frame allocator to clear
+ */
+void frame_allocator_clear(struct frame_allocator *alloc) { alloc->offset = 0; }
diff --git a/src/buffer.c b/src/buffer.c
index c3d2925..895922d 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -56,8 +56,8 @@ struct buffer buffer_create(char *name, bool modeline) {
BINDING(Ctrl, 'A', "beginning-of-line"),
BINDING(Ctrl, 'E', "end-of-line"),
- BINDING(Ctrl, 'M', "newline"),
- BINDING(Ctrl, 'I', "indent"),
+ BINDING(ENTER, "newline"),
+ BINDING(TAB, "indent"),
BINDING(Ctrl, 'K', "kill-line"),
BINDING(DELETE, "delete-char"),
diff --git a/src/command.c b/src/command.c
index f8add1b..b2f0c71 100644
--- a/src/command.c
+++ b/src/command.c
@@ -4,6 +4,11 @@
#include <stdlib.h>
+struct hashed_command {
+ uint32_t hash;
+ struct command command;
+};
+
struct commands command_registry_create(uint32_t capacity) {
return (struct commands){
.commands = calloc(capacity, sizeof(struct hashed_command)),
diff --git a/src/command.h b/src/command.h
index 278a894..20c7d74 100644
--- a/src/command.h
+++ b/src/command.h
@@ -1,4 +1,4 @@
-/**
+/** @file command.h
* Commands and command registries
*/
#include <stdint.h>
@@ -7,28 +7,70 @@ struct buffer;
struct buffers;
struct window;
+/**
+ * Execution context for a command
+ */
struct command_ctx {
+ /**
+ * The current list of buffers.
+ *
+ * Can be used to insert new buffers or
+ * inspect existing.
+ */
struct buffers *buffers;
+
+ /**
+ * The currently active window.
+ */
struct window *active_window;
+
+ /**
+ * A registry of available commands.
+ *
+ * Can be used to execute other commands as part of a command implementation.
+ */
struct commands *commands;
+
+ /**
+ * The command that is currently being executed
+ */
struct command *self;
+
+ /**
+ * User data set up by the command currently being executed.
+ */
void *userdata;
};
+/** A command function callback which holds the implementation of a command */
typedef int32_t (*command_fn)(struct command_ctx ctx, int argc,
const char *argv[]);
+/**
+ * A command that can be bound to a key or executed directly
+ */
struct command {
+ /**
+ * Name of the command
+ *
+ * Used to look the command up for execution and keybinds.
+ */
const char *name;
+
+ /**
+ * Implementation of command behavior
+ */
command_fn fn;
- void *userdata;
-};
-struct hashed_command {
- uint32_t hash;
- struct command command;
+ /**
+ * Userdata passed to each invocation of the command.
+ */
+ void *userdata;
};
+/**
+ * A command registry
+ */
struct commands {
struct hashed_command *commands;
uint32_t ncommands;
diff --git a/src/display.c b/src/display.c
index 3a2a0d9..de99e36 100644
--- a/src/display.c
+++ b/src/display.c
@@ -106,7 +106,11 @@ void display_destroy(struct display *display) {
tcsetattr(0, TCSADRAIN, &display->orig_term);
}
-void putbyte(uint8_t c) { putc(c, stdout); }
+void putbyte(uint8_t c) {
+ if (c != '\r') {
+ putc(c, stdout);
+ }
+}
void putbyte_ws(uint8_t c, bool show_whitespace) {
if (show_whitespace && c == '\t') {
@@ -114,7 +118,7 @@ void putbyte_ws(uint8_t c, bool show_whitespace) {
} else if (show_whitespace && c == ' ') {
fputs("\x1b[90mยท\x1b[0m", stdout);
} else {
- fputc(c, stdout);
+ putbyte(c);
}
}
diff --git a/src/display.h b/src/display.h
index 9a1cee5..7ffc660 100644
--- a/src/display.h
+++ b/src/display.h
@@ -13,6 +13,8 @@ struct display {
struct render_command;
struct command_list;
+/** Typedef for any allocation function */
+typedef void *(*alloc_fn)(size_t);
struct display display_create();
void display_resize(struct display *display);
@@ -25,7 +27,6 @@ void display_begin_render(struct display *display);
void display_render(struct display *display, struct command_list *command_list);
void display_end_render(struct display *display);
-typedef void *(*alloc_fn)(size_t);
struct command_list *command_list_create(uint32_t capacity, alloc_fn allocator,
uint32_t xoffset, uint32_t yoffset,
const char *name);
diff --git a/src/keyboard.c b/src/keyboard.c
index e76630e..7059dec 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -6,6 +6,7 @@
#include <ctype.h>
#include <errno.h>
+#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
@@ -18,46 +19,46 @@ struct keyboard keyboard_create(struct reactor *reactor) {
term.c_cc[VMIN] = 0;
term.c_cc[VTIME] = 0;
tcsetattr(0, TCSADRAIN, &term);
+ return keyboard_create_fd(reactor, STDIN_FILENO);
+}
+struct keyboard keyboard_create_fd(struct reactor *reactor, int fd) {
return (struct keyboard){
- .reactor_event_id =
- reactor_register_interest(reactor, STDIN_FILENO, ReadInterest),
- .has_data = false,
+ .fd = fd,
+ .reactor_event_id = reactor_register_interest(reactor, fd, ReadInterest),
};
}
void parse_keys(uint8_t *bytes, uint32_t nbytes, struct key *out_keys,
uint32_t *out_nkeys) {
+ // TODO: can be optimized if "bytes" contains no special chars
uint32_t nkps = 0;
for (uint32_t bytei = 0; bytei < nbytes; ++bytei) {
uint8_t b = bytes[bytei];
+ bool has_more = bytei + 1 < nbytes;
+ uint8_t next = has_more ? bytes[bytei + 1] : 0;
+ struct key *kp = &out_keys[nkps];
if (b == 0x1b) { // meta
- struct key *kp = &out_keys[nkps];
kp->start = bytei;
kp->mod = Meta;
- } else if (b == '[' ||
- b == '0') { // special char (function keys, pgdn, etc)
- struct key *kp = &out_keys[nkps];
- if (kp->mod & Meta) {
- kp->mod = Spec;
- }
- } else if (b >= 0x00 && b <= 0x1f) { // ctrl char
- struct key *kp = &out_keys[nkps];
+ } else if (has_more && isalnum(next) && kp->mod & Meta &&
+ (b == '[' ||
+ b == '0')) { // special char (function keys, pgdn, etc)
+ kp->mod = Spec;
+ } else if (b == 0x7f) { // ?
kp->mod |= Ctrl;
- kp->key = b | 0x40;
+ kp->key = '?';
kp->start = bytei;
kp->end = bytei + 1;
++nkps;
- } else if (b == 0x7f) { // ?
- struct key *kp = &out_keys[nkps];
+ } else if (iscntrl(b)) { // ctrl char
kp->mod |= Ctrl;
- kp->key = '?';
+ kp->key = b | 0x40;
kp->start = bytei;
kp->end = bytei + 1;
++nkps;
} else {
- struct key *kp = &out_keys[nkps];
if (kp->mod & Spec && b == '~') {
// skip tilde in special chars
kp->end = bytei + 1;
@@ -66,19 +67,18 @@ void parse_keys(uint8_t *bytes, uint32_t nbytes, struct key *out_keys,
kp->key = b;
kp->end = bytei + 1;
- bool has_more = bytei + 1 < nbytes;
-
- if (kp->mod & Meta ||
- (kp->mod & Spec && !(has_more && bytes[bytei + 1] == '~'))) {
+ if (kp->mod & Meta || (kp->mod & Spec && next != '~')) {
++nkps;
}
- } else if (utf8_byte_is_unicode_start(b)) {
+ } else if (utf8_byte_is_unicode_continuation(b)) {
+ // do nothing for these
+ } else if (utf8_byte_is_unicode_start(b)) { // unicode char
kp->mod = None;
kp->key = 0;
kp->start = bytei;
kp->end = bytei + utf8_nbytes(bytes + bytei, nbytes - bytei, 1);
++nkps;
- } else {
+ } else { // normal ASCII char
kp->mod = None;
kp->key = b;
kp->start = bytei;
@@ -92,36 +92,44 @@ void parse_keys(uint8_t *bytes, uint32_t nbytes, struct key *out_keys,
}
struct keyboard_update keyboard_update(struct keyboard *kbd,
- struct reactor *reactor) {
+ struct reactor *reactor,
+ alloc_fn frame_alloc) {
struct keyboard_update upd = (struct keyboard_update){
- .keys = {0},
+ .keys = NULL,
.nkeys = 0,
.nbytes = 0,
- .raw = {0},
+ .raw = NULL,
};
- if (!kbd->has_data) {
- if (reactor_poll_event(reactor, kbd->reactor_event_id)) {
- kbd->has_data = true;
- } else {
- return upd;
- }
+ // check if there is anything to do
+ if (!reactor_poll_event(reactor, kbd->reactor_event_id)) {
+ return upd;
+ }
+
+ // read all input in chunks of `bufsize` bytes
+ const uint32_t bufsize = 128;
+ uint8_t *buf = malloc(bufsize), *writepos = buf;
+ int nbytes = 0, nread = 0;
+ while ((nread = read(kbd->fd, writepos, bufsize)) == bufsize) {
+ nbytes += bufsize;
+ buf = realloc(buf, nbytes + bufsize);
+ writepos = buf + nbytes;
}
- int nbytes = read(STDIN_FILENO, upd.raw, 64);
+ nbytes += nread;
if (nbytes > 0) {
upd.nbytes = nbytes;
- parse_keys(upd.raw, upd.nbytes, upd.keys, &upd.nkeys);
+ upd.raw = frame_alloc(nbytes);
+ memcpy(upd.raw, buf, nbytes);
+ upd.keys = frame_alloc(sizeof(struct key) * nbytes);
+ memset(upd.keys, 0, sizeof(struct key) * nbytes);
- if (nbytes < 64) {
- kbd->has_data = false;
- }
- } else if (nbytes == EAGAIN) {
- kbd->has_data = false;
+ parse_keys(upd.raw, upd.nbytes, upd.keys, &upd.nkeys);
}
+ free(buf);
return upd;
}
diff --git a/src/keyboard.h b/src/keyboard.h
index 9bf36de..a44a58f 100644
--- a/src/keyboard.h
+++ b/src/keyboard.h
@@ -2,48 +2,144 @@
#include <stddef.h>
#include <stdint.h>
+/**
+ * Key press modifiers
+ *
+ * Modifiers a key press can have.
+ */
enum modifiers {
+ /** No modifier, bare key press */
None = 0,
+
+ /** Ctrl key */
Ctrl = 1 << 0,
+
+ /** Meta (Alt) key */
Meta = 1 << 1,
+
+ /** Special key (F keys, arrow keys, etc) */
Spec = 1 << 2,
};
+/** Backspace key */
#define BACKSPACE Ctrl, '?'
+/** Tab key */
+#define TAB Ctrl, 'I'
+/** Enter key */
+#define ENTER Ctrl, 'M'
+/** Delete key */
#define DELETE Spec, '3'
+/** Up arrow key */
#define UP Spec, 'A'
+/** Down arrow key */
#define DOWN Spec, 'B'
+/** Right arrow key */
#define RIGHT Spec, 'C'
+/** Left arrow key */
#define LEFT Spec, 'D'
+/**
+ * A key press
+ */
struct key {
+ /** The key pressed, will be 0 for a unicode char */
uint8_t key;
+ /** Modifier keys pressed (or-ed together) */
uint8_t mod;
+ /** Index where this key press starts in the raw input buffer */
uint8_t start;
+ /** Index where this key press ends in the raw input buffer */
uint8_t end;
};
+/**
+ * The keyboard used to input characters.
+ */
struct keyboard {
uint32_t reactor_event_id;
- bool has_data;
+ int fd;
};
+/**
+ * The result of updating the keyboard
+ */
struct keyboard_update {
- struct key keys[32];
+ /** The key presses */
+ struct key *keys;
+ /** Number of key presses in @ref keys */
uint32_t nkeys;
- uint8_t raw[64];
+ /** The raw input */
+ uint8_t *raw;
+ /** The number of bytes in the raw input */
uint32_t nbytes;
};
struct reactor;
+/** Typedef for any allocation function */
+typedef void *(*alloc_fn)(size_t);
+/**
+ * Create a new keyboard
+ *
+ * @param reactor @ref reactor "Reactor" to use for polling keyboard for
+ * readiness.
+ * @returns The created keyboard.
+ */
struct keyboard keyboard_create(struct reactor *reactor);
+/**
+ * Create a new keyboard, reading input from fd
+ *
+ * @param reactor @ref reactor "Reactor" to use for polling keyboard for
+ * readiness.
+ * @param fd The file descriptor to get input from
+ * @returns The created keyboard.
+ */
+struct keyboard keyboard_create_fd(struct reactor *reactor, int fd);
+
+/**
+ * Update the keyboard.
+ *
+ * This will check the reactor for readiness to avoid blocking. If there is
+ * data, it will be read and converted to key presses.
+ *
+ * @param kbd The @ref keyboard to update.
+ * @param reactor The @ref reactor used when creating the @ref keyboard.
+ * @returns An instance of @ref keyboard_update representing the result of the
+ * update operation.
+ */
struct keyboard_update keyboard_update(struct keyboard *kbd,
- struct reactor *reactor);
+ struct reactor *reactor,
+ alloc_fn frame_alloc);
+/**
+ * Does key represent the same key press as mod and c.
+ *
+ * @param key The key to check.
+ * @param mod Modifier of a key to compare against.
+ * @param c Char of a key to compare against.
+ * @returns true if key represents the same key press as mod together with c,
+ * false otherwise
+ */
bool key_equal_char(struct key *key, uint8_t mod, uint8_t c);
+
+/**
+ * Does key1 represent the same key press as key2?
+ *
+ * @param key1 First key to compare.
+ * @param key2 Second key to compare.
+ * @returns true if key1 and key2 represents the same key press, false
+ * otherwise.
+ */
bool key_equal(struct key *key1, struct key *key2);
+
+/**
+ * Get a text representation of a key
+ *
+ * @param key @ref key "Key" to get text representation for.
+ * @param buf character buffer for holding the result.
+ * @param capacity The capacity of buf.
+ */
void key_name(struct key *key, char *buf, size_t capacity);
diff --git a/src/main.c b/src/main.c
index 4048aba..d67ee94 100644
--- a/src/main.c
+++ b/src/main.c
@@ -6,6 +6,7 @@
#include <string.h>
#include <time.h>
+#include "allocator.h"
#include "binding.h"
#include "buffer.h"
#include "buffers.h"
@@ -13,30 +14,6 @@
#include "minibuffer.h"
#include "reactor.h"
-struct frame_allocator {
- uint8_t *buf;
- size_t offset;
- size_t capacity;
-};
-
-struct frame_allocator frame_allocator_create(size_t capacity) {
- return (struct frame_allocator){
- .capacity = capacity, .offset = 0, .buf = (uint8_t *)malloc(capacity)};
-}
-
-void *frame_allocator_alloc(struct frame_allocator *alloc, size_t sz) {
- if (alloc->offset + sz > alloc->capacity) {
- return NULL;
- }
-
- void *mem = alloc->buf + alloc->offset;
- alloc->offset += sz;
-
- return mem;
-}
-
-void frame_allocator_clear(struct frame_allocator *alloc) { alloc->offset = 0; }
-
struct frame_allocator frame_allocator;
void *frame_alloc(size_t sz) {
@@ -107,7 +84,7 @@ int main(int argc, char *argv[]) {
frame_allocator = frame_allocator_create(16 * 1024 * 1024);
// create reactor
- struct reactor reactor = reactor_create();
+ struct reactor *reactor = reactor_create();
// initialize display
display = display_create();
@@ -115,7 +92,7 @@ int main(int argc, char *argv[]) {
signal(SIGWINCH, resized);
// init keyboard
- struct keyboard kbd = keyboard_create(&reactor);
+ struct keyboard kbd = keyboard_create(reactor);
// commands
struct commands commands = command_registry_create(32);
@@ -243,13 +220,14 @@ int main(int argc, char *argv[]) {
clock_gettime(CLOCK_MONOTONIC, &display_end);
// this blocks for events, so if nothing has happened we block here.
- reactor_update(&reactor);
+ 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);
+ 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) {
diff --git a/src/reactor.c b/src/reactor-linux.c
index 7bdb4a4..e488fef 100644
--- a/src/reactor.c
+++ b/src/reactor-linux.c
@@ -4,24 +4,33 @@
#include <stdlib.h>
#include <sys/epoll.h>
+struct reactor {
+ int epoll_fd;
+ void *events;
+};
+
struct events {
struct epoll_event events[10];
uint32_t nevents;
};
-struct reactor reactor_create() {
+struct reactor *reactor_create() {
int epollfd = epoll_create1(0);
if (epollfd == -1) {
perror("epoll_create1");
}
- return (struct reactor){
- .epoll_fd = epollfd,
- .events = calloc(1, sizeof(struct events)),
- };
+ struct reactor *r = (struct reactor *)calloc(1, sizeof(struct reactor));
+ r->epoll_fd = epollfd;
+ r->events = calloc(1, sizeof(struct events));
+
+ return r;
}
-void reactor_destroy(struct reactor *reactor) { free(reactor->events); }
+void reactor_destroy(struct reactor *reactor) {
+ free(reactor->events);
+ free(reactor);
+}
uint32_t reactor_register_interest(struct reactor *reactor, int fd,
enum interest interest) {
diff --git a/src/reactor.h b/src/reactor.h
index 01e2443..e54afda 100644
--- a/src/reactor.h
+++ b/src/reactor.h
@@ -6,12 +6,9 @@ enum interest {
WriteInterest = 2,
};
-struct reactor {
- int epoll_fd;
- void *events;
-};
+struct reactor;
-struct reactor reactor_create();
+struct reactor *reactor_create();
void reactor_destroy(struct reactor *reactor);
void reactor_update(struct reactor *reactor);
bool reactor_poll_event(struct reactor *reactor, uint32_t ev_id);
diff --git a/targets.mk b/targets.mk
new file mode 100644
index 0000000..c130237
--- /dev/null
+++ b/targets.mk
@@ -0,0 +1,43 @@
+# dependency generation
+.c.d:
+ $(CC) -MM $(CFLAGS) -MT $*.o $< > $@
+ @sed -i 's,\($*\)\.o[ :]*,\1.o $@ : ,g' $@
+
+.c.o:
+ $(CC) $(CFLAGS) -c $< -o $@
+
+
+dged: src/main.o libdged.a
+ $(CC) $(LDFLAGS) src/main.o libdged.a -o dged
+
+libdged.a: $(OBJS) $(PLATFORM_OBJS)
+ $(AR) -rc libdged.a $(OBJS) $(PLATFORM_OBJS)
+
+run-tests: $(TEST_OBJS) $(OBJS)
+ $(CC) $(LDFLAGS) $(TEST_OBJS) $(OBJS) -o run-tests
+
+check: run-tests
+ ./run-tests
+
+run: dged
+ ./dged
+
+debug: dged
+ gdb ./dged
+
+debug-tests: run-tests
+ gdb ./run-tests
+
+clean:
+ rm -f $(FILES)
+ rm -rf docs
+
+install: dged
+ install -d $(prefix)/bin
+ install -m 755 dged $(prefix)/bin/dged
+
+ install -d $(prefix)/share/man/man1
+ install -m 644 dged.1 $(prefix)/share/man/man1/dged.1
+
+docs:
+ doxygen Doxyfile
diff --git a/test/command.c b/test/command.c
index 738f5f9..be5fffc 100644
--- a/test/command.c
+++ b/test/command.c
@@ -72,8 +72,30 @@ void test_lookup_command() {
"Expected the found function to have the correct name");
}
+int32_t failing_command(struct command_ctx ctx, int argc, const char *argv[]) {
+ return 100;
+}
+
+void test_execute_command() {
+ struct commands cmds = single_fake_command("fake");
+ struct command *cmd = lookup_command(&cmds, "fake");
+
+ int32_t res = execute_command(cmd, &cmds, NULL, NULL, 0, NULL);
+ ASSERT(res == 0, "Expected to be able to execute command successfully");
+
+ register_command(&cmds, (struct command){
+ .fn = failing_command,
+ .name = "fejl",
+ .userdata = NULL,
+ });
+ struct command *fail_cmd = lookup_command(&cmds, "fejl");
+ int32_t res2 = execute_command(fail_cmd, &cmds, NULL, NULL, 0, NULL);
+ ASSERT(res2 != 0, "Expected failing command to fail");
+}
+
void run_command_tests() {
run_test(test_command_registry_create);
run_test(test_register_command);
run_test(test_lookup_command);
+ run_test(test_execute_command);
}
diff --git a/test/fake-reactor.c b/test/fake-reactor.c
new file mode 100644
index 0000000..aafe8a3
--- /dev/null
+++ b/test/fake-reactor.c
@@ -0,0 +1,47 @@
+#include "fake-reactor.h"
+#include <stdlib.h>
+
+struct reactor {
+ struct fake_reactor_impl *impl;
+};
+
+struct reactor *reactor_create() {
+ return (struct reactor *)calloc(1, sizeof(struct reactor));
+}
+
+void reactor_destroy(struct reactor *reactor) { free(reactor); }
+
+void reactor_update(struct reactor *reactor) {}
+bool reactor_poll_event(struct reactor *reactor, uint32_t ev_id) {
+ if (reactor->impl != NULL) {
+ return reactor->impl->poll_event(reactor->impl->userdata, ev_id);
+ } else {
+ return false;
+ }
+}
+
+uint32_t reactor_register_interest(struct reactor *reactor, int fd,
+ enum interest interest) {
+ if (reactor->impl != NULL) {
+ return reactor->impl->register_interest(reactor->impl->userdata, fd,
+ interest);
+ } else {
+ return 0;
+ }
+}
+
+void reactor_unregister_interest(struct reactor *reactor, uint32_t ev_id) {
+ if (reactor->impl != NULL) {
+ return reactor->impl->unregister_interest(reactor->impl->userdata, ev_id);
+ }
+}
+
+struct reactor *fake_reactor_create(struct fake_reactor_impl *impl) {
+ struct reactor *r = reactor_create();
+ set_reactor_impl(r, impl);
+ return r;
+}
+
+void set_reactor_impl(struct reactor *reactor, struct fake_reactor_impl *impl) {
+ reactor->impl = impl;
+}
diff --git a/test/fake-reactor.h b/test/fake-reactor.h
new file mode 100644
index 0000000..04d8306
--- /dev/null
+++ b/test/fake-reactor.h
@@ -0,0 +1,13 @@
+#include "reactor.h"
+#include <stdbool.h>
+#include <stdint.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);
+ void (*unregister_interest)(void *userdata, uint32_t ev_id);
+ void *userdata;
+};
+
+struct reactor *fake_reactor_create(struct fake_reactor_impl *impl);
+void set_reactor_impl(struct reactor *reactor, struct fake_reactor_impl *impl);
diff --git a/test/keyboard.c b/test/keyboard.c
new file mode 100644
index 0000000..b85c4ad
--- /dev/null
+++ b/test/keyboard.c
@@ -0,0 +1,208 @@
+#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;
+ uint32_t unreg;
+};
+
+bool fake_poll(void *userdata, uint32_t ev_id) {
+ if (userdata != NULL) {
+ struct call_count *cc = (struct call_count *)userdata;
+ ++cc->poll;
+ }
+ return true;
+}
+uint32_t fake_register_interest(void *userdata, int fd,
+ enum interest interest) {
+ if (userdata != NULL) {
+ struct call_count *cc = (struct call_count *)userdata;
+ ++cc->reg;
+ }
+ return 0;
+}
+
+void fake_unregister_interest(void *userdata, uint32_t ev_id) {
+ if (userdata != NULL) {
+ struct call_count *cc = (struct call_count *)userdata;
+ ++cc->unreg;
+ }
+}
+
+struct fake_keyboard {
+ struct keyboard inner;
+ struct reactor *reactor;
+ int writefd;
+};
+
+struct fake_keyboard create_fake_keyboard(struct fake_reactor_impl *reactor) {
+ struct reactor *r = fake_reactor_create(reactor);
+
+ int pipefd[2];
+ int res = pipe(pipefd);
+ ASSERT(res == 0, "Failed to create a pipe?");
+
+ struct keyboard k = keyboard_create_fd(r, pipefd[0]);
+
+ return (struct fake_keyboard){
+ .inner = k,
+ .reactor = r,
+ .writefd = pipefd[1],
+ };
+}
+
+void fake_keyboard_write(struct fake_keyboard *kbd, const char *s) {
+ write(kbd->writefd, s, strlen(s));
+}
+
+void fake_keyboard_close_write(struct fake_keyboard *kbd) {
+ close(kbd->writefd);
+}
+
+void fake_keyboard_destroy(struct fake_keyboard *kbd) {
+ fake_keyboard_close_write(kbd);
+}
+
+void simple_key() {
+ struct call_count cc = {0};
+ struct fake_reactor_impl fake = {
+ .poll_event = fake_poll,
+ .register_interest = fake_register_interest,
+ .unregister_interest = fake_unregister_interest,
+ .userdata = &cc,
+ };
+ struct fake_keyboard k = create_fake_keyboard(&fake);
+ ASSERT(cc.reg == 1, "Expected keyboard to register read interest");
+
+ fake_keyboard_write(&k, "q");
+ fake_keyboard_close_write(&k);
+
+ struct keyboard_update upd = keyboard_update(&k.inner, k.reactor, malloc);
+
+ ASSERT(upd.nkeys == 1, "Expected to get 1 key from update");
+ ASSERT(cc.poll, "Expected keyboard update to call reactor poll");
+
+ fake_keyboard_destroy(&k);
+ free(upd.keys);
+ free(upd.raw);
+}
+
+void ctrl_key() {
+ struct fake_reactor_impl fake = {
+ .poll_event = fake_poll,
+ .register_interest = fake_register_interest,
+ .unregister_interest = fake_unregister_interest,
+ .userdata = NULL,
+ };
+ struct fake_keyboard k = create_fake_keyboard(&fake);
+ fake_keyboard_write(&k, "");
+ fake_keyboard_close_write(&k);
+
+ struct keyboard_update upd = keyboard_update(&k.inner, k.reactor, malloc);
+ ASSERT(upd.nkeys == 2, "Expected to get 2 keys from update");
+ ASSERT(upd.keys[0].mod == Ctrl && upd.keys[0].key == 'H',
+ "Expected first key to be c-h");
+ ASSERT(upd.keys[1].mod == Ctrl && upd.keys[1].key == 'P',
+ "Expected first key to be c-p");
+
+ fake_keyboard_destroy(&k);
+ free(upd.keys);
+ free(upd.raw);
+}
+
+void meta_key() {
+ struct fake_reactor_impl fake = {
+ .poll_event = fake_poll,
+ .register_interest = fake_register_interest,
+ .unregister_interest = fake_unregister_interest,
+ .userdata = NULL,
+ };
+ struct fake_keyboard k = create_fake_keyboard(&fake);
+ fake_keyboard_write(&k, "d[x");
+ fake_keyboard_close_write(&k);
+
+ struct keyboard_update upd = keyboard_update(&k.inner, k.reactor, malloc);
+ ASSERT(upd.nkeys == 3, "Expected to get 3 keys from update");
+ ASSERT(upd.keys[0].mod == Meta && upd.keys[0].key == 'd',
+ "Expected first key to be m-d");
+ ASSERT(upd.keys[1].mod == Meta && upd.keys[1].key == '[',
+ "Expected second key to be m-[");
+ ASSERT(upd.keys[2].mod == Meta && upd.keys[2].key == 'x',
+ "Expected third key to be m-x");
+
+ fake_keyboard_destroy(&k);
+ free(upd.keys);
+ free(upd.raw);
+}
+
+void spec_key() {
+ struct fake_reactor_impl fake = {
+ .poll_event = fake_poll,
+ .register_interest = fake_register_interest,
+ .unregister_interest = fake_unregister_interest,
+ .userdata = NULL,
+ };
+ struct fake_keyboard k = create_fake_keyboard(&fake);
+ fake_keyboard_write(&k, "[6~");
+ fake_keyboard_close_write(&k);
+
+ struct keyboard_update upd = keyboard_update(&k.inner, k.reactor, malloc);
+ ASSERT(upd.nkeys == 2, "Expected to get 2 keys from update");
+ ASSERT(upd.keys[0].mod == Spec && upd.keys[0].key == 'A',
+ "Expected first key to be up arrow");
+ ASSERT(upd.keys[1].mod == Spec && upd.keys[1].key == '6',
+ "Expected second key to be PgDn");
+
+ fake_keyboard_destroy(&k);
+ free(upd.keys);
+ free(upd.raw);
+}
+
+void test_utf8() {
+ struct fake_reactor_impl fake = {
+ .poll_event = fake_poll,
+ .register_interest = fake_register_interest,
+ .unregister_interest = fake_unregister_interest,
+ .userdata = NULL,
+ };
+ struct fake_keyboard k = create_fake_keyboard(&fake);
+ fake_keyboard_write(&k, "๐ŸŽ ");
+ fake_keyboard_close_write(&k);
+
+ struct keyboard_update upd = keyboard_update(&k.inner, k.reactor, malloc);
+ ASSERT(upd.nbytes == 4, "Expected there to be four bytes of raw input");
+ ASSERT(upd.nkeys == 1, "Expected to get 1 key from update");
+ ASSERT(upd.keys[0].start == 0 && upd.keys[0].end == 4,
+ "Expected first key to be 4 bytes");
+
+ fake_keyboard_destroy(&k);
+ free(upd.keys);
+ free(upd.raw);
+}
+
+void test_key_equal() {
+ struct key k1 = {.mod = Ctrl, .key = 'A'};
+ ASSERT(key_equal(&k1, &k1), "Expected key to be equal to itself");
+ ASSERT(key_equal_char(&k1, Ctrl, 'A'), "Expected key to be c-a");
+
+ struct key k2 = {.mod = None, .key = 'A'};
+ ASSERT(!key_equal(&k1, &k2), "Expected key to not be equal to different key");
+ ASSERT(!key_equal_char(&k2, Spec, 'A'),
+ "Expected yet another different key to not be the same");
+}
+
+void run_keyboard_tests() {
+ run_test(simple_key);
+ run_test(ctrl_key);
+ run_test(meta_key);
+ run_test(spec_key);
+ run_test(test_utf8);
+ run_test(test_key_equal);
+}
diff --git a/test/main.c b/test/main.c
index 8102a58..68241f9 100644
--- a/test/main.c
+++ b/test/main.c
@@ -1,7 +1,10 @@
#include <locale.h>
#include <signal.h>
+#include <stdint.h>
#include <stdlib.h>
+#include <time.h>
+#include "bits/time.h"
#include "test.h"
void handle_abort() { exit(1); }
@@ -10,6 +13,9 @@ int main() {
setlocale(LC_ALL, "");
signal(SIGABRT, handle_abort);
+ struct timespec test_begin;
+ clock_gettime(CLOCK_MONOTONIC, &test_begin);
+
printf("\n๐ŸŒ \x1b[1;36mRunning utf8 tests...\x1b[0m\n");
run_utf8_tests();
@@ -22,6 +28,15 @@ int main() {
printf("\n๐Ÿ’ \x1b[1;36mRunning command tests...\x1b[0m\n");
run_command_tests();
- printf("\n๐ŸŽ‰ \x1b[1;32mDone! All tests successful!\x1b[0m\n");
+ printf("\n๐Ÿ“  \x1b[1;36mRunning keyboard tests...\x1b[0m\n");
+ run_keyboard_tests();
+
+ struct timespec elapsed;
+ clock_gettime(CLOCK_MONOTONIC, &elapsed);
+ uint64_t elapsed_nanos =
+ ((uint64_t)elapsed.tv_sec * 1e9 + (uint64_t)elapsed.tv_nsec) -
+ ((uint64_t)test_begin.tv_sec * 1e9 + (uint64_t)test_begin.tv_nsec);
+ printf("\n๐ŸŽ‰ \x1b[1;32mDone! All tests successful in %.2f ms!\x1b[0m\n",
+ (double)elapsed_nanos / 1e6);
return 0;
}
diff --git a/test/test.h b/test/test.h
index 3fdcd0d..2d9d8af 100644
--- a/test/test.h
+++ b/test/test.h
@@ -10,3 +10,4 @@ void run_buffer_tests();
void run_utf8_tests();
void run_text_tests();
void run_command_tests();
+void run_keyboard_tests();