summaryrefslogtreecommitdiff
path: root/src
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 /src
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.
Diffstat (limited to 'src')
-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
11 files changed, 284 insertions, 93 deletions
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);