diff options
| -rw-r--r-- | Doxyfile | 12 | ||||
| -rw-r--r-- | GNUmakefile | 2 | ||||
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | common.mk | 56 | ||||
| -rw-r--r-- | linux.mk | 3 | ||||
| -rw-r--r-- | src/allocator.h | 51 | ||||
| -rw-r--r-- | src/buffer.c | 4 | ||||
| -rw-r--r-- | src/command.c | 5 | ||||
| -rw-r--r-- | src/command.h | 54 | ||||
| -rw-r--r-- | src/display.c | 8 | ||||
| -rw-r--r-- | src/display.h | 3 | ||||
| -rw-r--r-- | src/keyboard.c | 86 | ||||
| -rw-r--r-- | src/keyboard.h | 104 | ||||
| -rw-r--r-- | src/main.c | 34 | ||||
| -rw-r--r-- | src/reactor-linux.c (renamed from src/reactor.c) | 21 | ||||
| -rw-r--r-- | src/reactor.h | 7 | ||||
| -rw-r--r-- | targets.mk | 43 | ||||
| -rw-r--r-- | test/command.c | 22 | ||||
| -rw-r--r-- | test/fake-reactor.c | 47 | ||||
| -rw-r--r-- | test/fake-reactor.h | 13 | ||||
| -rw-r--r-- | test/keyboard.c | 208 | ||||
| -rw-r--r-- | test/main.c | 17 | ||||
| -rw-r--r-- | test/test.h | 1 |
23 files changed, 648 insertions, 155 deletions
@@ -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) @@ -4,6 +4,8 @@ .sinclude "$(UNAME_S).mk" +.include "targets.mk" + # in this case we need a separate depend target depend: $(DEPS) @: @@ -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) @@ -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); @@ -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, "[A[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(); |
