From f90d5e1f07fdc9dea7c24b11107049b613a5be7a Mon Sep 17 00:00:00 2001 From: Albert Cervin Date: Sun, 29 Jan 2023 22:22:54 +0100 Subject: More tests and documentation Also improve find file and switch buffer a bit. Implement word backward/forward. --- common.mk | 3 +- src/binding.h | 68 ++++++++++++++++++++ src/buffer.c | 63 +++++++++++++++++- src/buffer.h | 48 ++++++++++++-- src/command.c | 51 +++++++++++++-- src/display.c | 37 ++++++++--- src/display.h | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++---- src/keyboard.c | 2 +- src/keyboard.h | 4 +- src/main.c | 40 ++++++------ src/minibuffer.c | 25 ++++++-- src/minibuffer.h | 53 ++++++++++++++++ src/text.c | 5 ++ src/text.h | 2 + src/window.c | 5 ++ src/window.h | 3 + test/allocator.c | 31 +++++++++ test/main.c | 7 +- test/minibuffer.c | 72 +++++++++++++++++++++ test/test.h | 7 ++ 20 files changed, 648 insertions(+), 65 deletions(-) create mode 100644 test/allocator.c create mode 100644 test/minibuffer.c diff --git a/common.mk b/common.mk index c20a1a0..83f0e2b 100644 --- a/common.mk +++ b/common.mk @@ -9,7 +9,8 @@ SOURCES = src/binding.c src/buffer.c src/command.c src/display.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/command.c test/keyboard.c test/fake-reactor.c test/allocator.c \ + test/minibuffer.c prefix != if [ -n "$$prefix" ]; then echo "$$prefix"; else echo "/usr"; fi diff --git a/src/binding.h b/src/binding.h index 18a7278..db4867b 100644 --- a/src/binding.h +++ b/src/binding.h @@ -1,15 +1,35 @@ #include "keyboard.h" +/** + * Directory of keyboard mappings. + */ struct keymap { + /** Keymap name */ const char *name; + + /** The bindings in this keymap */ struct binding *bindings; + + /** The number of bindings in this keymap */ uint32_t nbindings; + + /** The number of bindings this keymap can currently hold */ uint32_t capacity; }; +/** + * Type of a keyboard binding + */ enum binding_type { + /** This binding is to a command */ BindingType_Command, + + /** This binding is to another keymap */ BindingType_Keymap, + + /** This binding is to an already resolved command, + * a.k.a. anonymous binding. + */ BindingType_DirectCommand }; @@ -35,33 +55,81 @@ enum binding_type { #define PREFIX(...) PREFIX_INNER(__VA_ARGS__) #define ANONYMOUS_BINDING(...) ANONYMOUS_BINDING_INNER(__VA_ARGS__) +/** + * A keyboard key bound to an action + */ struct binding { + /** The keyboard key that triggers the action in this binding */ struct key key; + /** Type of this binding, see @ref binding_type */ uint8_t type; union { + /** A hash of a command name */ uint32_t command; + /** A command */ struct command *direct_command; + /** A keymap */ struct keymap *keymap; }; }; +/** + * Result of a binding lookup + */ struct lookup_result { + /** True if a binding was found */ bool found; + + /** Type of binding in the result */ uint8_t type; + union { + /** A command */ struct command *command; + /** A keymap */ struct keymap *keymap; }; }; struct commands; +/** + * Create a new keymap + * + * @param name Name of the keymap + * @param capacity Initial capacity of the keymap. + * @returns The created keymap + */ struct keymap keymap_create(const char *name, uint32_t capacity); + +/** + * Bind keys in a keymap. + * + * @param keymap The keymap to bind keys in. + * @param bindings Bindings to add. + * @param nbindings Number of bindings in @ref bindings. + */ void keymap_bind_keys(struct keymap *keymap, struct binding *bindings, uint32_t nbindings); + +/** + * Destroy a keymap. + * + * This clears all keybindings associated with the keymap. + * @param keymap Keymap to destroy. + */ void keymap_destroy(struct keymap *keymap); +/** + * Lookup the binding for a key in a set of keymaps. + * + * @param keymaps The keymaps to look in. + * @param nkeymaps The number of keymaps in @ref keymaps. + * @param key The keystroke to look up bindings for. + * @param commands Available commands for lookup. + * @returns A @ref lookup_result with the result of the lookup. + */ struct lookup_result lookup_key(struct keymap *keymaps, uint32_t nkeymaps, struct key *key, struct commands *commands); diff --git a/src/buffer.c b/src/buffer.c index 895922d..b9ec8fc 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -1,6 +1,7 @@ #include "buffer.h" #include "binding.h" #include "display.h" +#include "errno.h" #include "minibuffer.h" #include "reactor.h" #include "utf8.h" @@ -15,6 +16,10 @@ #include #include +struct modeline { + uint8_t *buffer; +}; + struct update_hook_result buffer_linenum_hook(struct buffer *buffer, struct command_list *commands, uint32_t width, uint32_t height, @@ -53,6 +58,9 @@ struct buffer buffer_create(char *name, bool modeline) { 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"), @@ -93,7 +101,7 @@ void buffer_clear(struct buffer *buffer) { } bool buffer_is_empty(struct buffer *buffer) { - return text_num_lines(buffer->text) == 0 && + return text_num_lines(buffer->text) == 1 && text_line_size(buffer->text, 0) == 0; } @@ -170,6 +178,40 @@ void buffer_backward_delete_char(struct buffer *buffer) { void buffer_backward_char(struct buffer *buffer) { moveh(buffer, -1); } void buffer_forward_char(struct buffer *buffer) { moveh(buffer, 1); } +void buffer_forward_word(struct buffer *buffer) { + moveh(buffer, 1); + struct text_chunk line = text_get_line(buffer->text, buffer->dot_line); + uint32_t bytei = + text_col_to_byteindex(buffer->text, buffer->dot_line, buffer->dot_col); + for (; bytei < line.nbytes; ++bytei) { + uint8_t b = line.text[bytei]; + if (b == ' ' || b == '.') { + break; + } + } + + uint32_t target_col = + text_byteindex_to_col(buffer->text, buffer->dot_line, bytei); + moveh(buffer, target_col - buffer->dot_col); +} + +void buffer_backward_word(struct buffer *buffer) { + moveh(buffer, -1); + struct text_chunk line = text_get_line(buffer->text, buffer->dot_line); + uint32_t bytei = + text_col_to_byteindex(buffer->text, buffer->dot_line, buffer->dot_col); + for (; bytei > 0; --bytei) { + uint8_t b = line.text[bytei]; + if (b == ' ' || b == '.') { + break; + } + } + + uint32_t target_col = + text_byteindex_to_col(buffer->text, buffer->dot_line, bytei); + moveh(buffer, (int32_t)target_col - buffer->dot_col); +} + void buffer_backward_line(struct buffer *buffer) { movev(buffer, -1); } void buffer_forward_line(struct buffer *buffer) { movev(buffer, 1); } @@ -185,6 +227,11 @@ struct buffer buffer_from_file(char *filename) { if (access(b.filename, F_OK) == 0) { FILE *file = fopen(filename, "r"); + if (file == NULL) { + minibuffer_echo("Error opening %s: %s", filename, strerror(errno)); + return b; + } + while (true) { uint8_t buff[4096]; int bytes = fread(buff, 1, 4096, file); @@ -194,7 +241,9 @@ struct buffer buffer_from_file(char *filename) { } else if (bytes == 0) { break; // EOF } else { - // TODO: handle error + minibuffer_echo("error reading from %s: %s", filename, strerror(errno)); + fclose(file); + return b; } } @@ -207,18 +256,26 @@ struct buffer buffer_from_file(char *filename) { void write_line(struct text_chunk *chunk, void *userdata) { FILE *file = (FILE *)userdata; fwrite(chunk->text, 1, chunk->nbytes, file); + + // final newline is not optional! fputc('\n', file); } void buffer_to_file(struct buffer *buffer) { if (!buffer->filename) { - minibuffer_echo("TODO: buffer \"%s\" is not associated with a file", + minibuffer_echo("buffer \"%s\" is not associated with a file", buffer->name); return; } // TODO: handle errors FILE *file = fopen(buffer->filename, "w"); + if (file == NULL) { + minibuffer_echo("failed to open file %s for writing: %s", buffer->filename, + strerror(errno)); + return; + } + uint32_t nlines = text_num_lines(buffer->text); struct text_chunk lastline = text_get_line(buffer->text, nlines - 1); uint32_t nlines_to_write = lastline.nbytes == 0 ? nlines - 1 : nlines; diff --git a/src/buffer.h b/src/buffer.h index dcc4a8c..a565a6e 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -7,9 +7,11 @@ #include "window.h" struct keymap; -struct reactor; struct command_list; +/** + * Margins where buffer text should not be + */ struct margin { uint32_t left; uint32_t right; @@ -17,37 +19,67 @@ struct margin { uint32_t bottom; }; +/** Callback for line rendering hooks */ typedef void (*line_render_cb)(struct text_chunk *line_data, uint32_t line, struct command_list *commands, void *userdata); +/** + * A line render hook + * + * A callback paired with userdata + */ struct line_render_hook { line_render_cb callback; void *userdata; }; +/** + * Result of updating a buffer hook + */ struct update_hook_result { + /** Desired margins for this hook */ struct margin margins; + + /** Hook to be added to rendering of buffer lines */ struct line_render_hook line_render_hook; }; +/** Buffer update hook callback function */ typedef struct update_hook_result (*update_hook_cb)( struct buffer *buffer, struct command_list *commands, uint32_t width, uint32_t height, uint64_t frame_time, void *userdata); +/** + * A buffer update hook. + * + * Can be used to implement custom behavior on top of a buffer. Used for + * minibuffer, line numbers, modeline etc. + */ struct update_hook { + /** Callback function */ update_hook_cb callback; + + /** Optional userdata to pass to the callback function unmodified */ void *userdata; }; +/** + * A set of update hooks + */ struct update_hooks { + /** The update hooks */ struct update_hook hooks[32]; - uint32_t nhooks; -}; -struct modeline { - uint8_t *buffer; + /** The number of update hooks */ + uint32_t nhooks; }; +/** + * A buffer of text that can be modified, read from and written to disk. + * + * This is the central data structure of dged and most other behavior is + * implemented on top of it. + */ struct buffer { char *name; char *filename; @@ -82,7 +114,9 @@ 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); @@ -114,7 +148,9 @@ 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); @@ -128,7 +164,9 @@ static struct command BUFFER_COMMANDS[] = { {.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}, diff --git a/src/command.c b/src/command.c index b2f0c71..c958a1b 100644 --- a/src/command.c +++ b/src/command.c @@ -2,7 +2,10 @@ #include "buffers.h" #include "minibuffer.h" +#include #include +#include +#include struct hashed_command { uint32_t hash; @@ -91,8 +94,19 @@ int32_t find_file(struct command_ctx ctx, int argc, const char *argv[]) { const char *pth = NULL; if (argc == 1) { pth = argv[0]; - ctx.active_window->buffer = - buffers_add(ctx.buffers, buffer_from_file((char *)pth)); + struct stat sb; + if (stat(pth, &sb) < 0) { + minibuffer_echo("stat on %s failed: %s", pth, strerror(errno)); + return 1; + } + + if (S_ISDIR(sb.st_mode)) { + 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); } else { @@ -118,20 +132,43 @@ int32_t run_interactive(struct command_ctx ctx, int argc, const char *argv[]) { } } -int32_t switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) { +int32_t do_switch_buffer(struct command_ctx ctx, int argc, const char *argv[]) { + const char *bufname = argv[0]; if (argc == 0) { - minibuffer_prompt(ctx, "buffer: "); - return 0; + // switch back to prev buffer + if (ctx.active_window->prev_buffer != NULL) { + bufname = ctx.active_window->prev_buffer->name; + } else { + return 0; + } } - const char *bufname = argv[0]; struct buffer *buf = buffers_find(ctx.buffers, bufname); if (buf == NULL) { minibuffer_echo_timeout(4, "buffer %s not found", bufname); return 1; } else { - ctx.active_window->buffer = buf; + 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) { + minibuffer_prompt( + ctx, "buffer (default %s): ", ctx.active_window->prev_buffer->name); + } else { + minibuffer_prompt(ctx, "buffer: "); + } + return 0; + } + + return execute_command(&do_switch_buffer_cmd, ctx.commands, ctx.active_window, + ctx.buffers, argc, argv); +} diff --git a/src/display.c b/src/display.c index de99e36..2f147d5 100644 --- a/src/display.c +++ b/src/display.c @@ -8,10 +8,18 @@ #include #include #include +#include #include #define ESC 0x1b +struct display { + struct termios term; + struct termios orig_term; + uint32_t width; + uint32_t height; +}; + enum render_cmd_type { RenderCommand_DrawText = 0, RenderCommand_PushFormat = 1, @@ -62,7 +70,7 @@ struct command_list { uint32_t xoffset; uint32_t yoffset; - alloc_fn allocator; + void *(*allocator)(size_t); char name[16]; }; @@ -73,7 +81,7 @@ struct winsize getsize() { return ws; } -struct display display_create() { +struct display *display_create() { struct winsize ws = getsize(); @@ -87,12 +95,12 @@ struct display display_create() { tcsetattr(0, TCSADRAIN, &term); - return (struct display){ - .orig_term = orig_term, - .term = term, - .height = ws.ws_row, - .width = ws.ws_col, - }; + struct display *d = calloc(1, sizeof(struct display)); + d->orig_term = orig_term; + d->term = term; + d->height = ws.ws_row; + d->width = ws.ws_col; + return d; } void display_resize(struct display *display) { @@ -104,8 +112,13 @@ void display_resize(struct display *display) { void display_destroy(struct display *display) { // reset old terminal mode tcsetattr(0, TCSADRAIN, &display->orig_term); + + free(display); } +uint32_t display_width(struct display *display) { return display->width; } +uint32_t display_height(struct display *display) { return display->height; } + void putbyte(uint8_t c) { if (c != '\r') { putc(c, stdout); @@ -156,7 +169,8 @@ void display_clear(struct display *display) { putbytes(bytes, 3, false); } -struct command_list *command_list_create(uint32_t capacity, alloc_fn allocator, +struct command_list *command_list_create(uint32_t capacity, + void *(*allocator)(size_t), uint32_t xoffset, uint32_t yoffset, const char *name) { struct command_list *command_list = allocator(sizeof(struct command_list)); @@ -176,7 +190,10 @@ struct command_list *command_list_create(uint32_t capacity, alloc_fn allocator, struct render_command *add_command(struct command_list *list, enum render_cmd_type tp) { if (list->ncmds == list->capacity) { - // TODO: better + /* TODO: better. Currently a bit tricky to provide dynamic scaling of this + * since it is initially allocated with the frame allocator that does not + * support realloc. + */ return NULL; } diff --git a/src/display.h b/src/display.h index 7ffc660..14dd246 100644 --- a/src/display.h +++ b/src/display.h @@ -2,48 +2,211 @@ #include #include -#include - -struct display { - struct termios term; - struct termios orig_term; - uint32_t width; - uint32_t height; -}; +struct display; struct render_command; struct command_list; -/** Typedef for any allocation function */ -typedef void *(*alloc_fn)(size_t); -struct display display_create(); +/** + * Create a new display + * + * The only implementation of this is currently a termios one. + * @returns A pointer to the display. + */ +struct display *display_create(); + +/** + * Resize the display + * + * Resize the display to match the underlying size of the device. + * @param display The display to resize. + */ void display_resize(struct display *display); + +/** + * Destroy the display. + * + * Clear all resources associated with the display and reset the underlying + * device to original state. + * @param display The display to destroy. + */ void display_destroy(struct display *display); +/** + * Get the current width of the display. + * + * @param display The display to get width for. + * @returns The width in number of chars as a positive integer. + */ +uint32_t display_width(struct display *display); + +/** + * Get the current height of the display. + * + * @param display The display to get height for. + * @returns The height in number of chars as a positive integer. + */ +uint32_t display_height(struct display *display); + +/** + * Clear the display + * + * This will clear all text from the display. + * @param display The display to clear. + */ void display_clear(struct display *display); + +/** + * Move the cursor to the specified location + * + * Move the cursor to the specified row and column. + * @param display The display to move the cursor for. + * @param row The row to move the cursor to. + * @param col The col to move the cursor to. + */ void display_move_cursor(struct display *display, uint32_t row, uint32_t col); +/** + * Start a render pass on the display. + * + * A begin_render call can be followed by any number of render calls followed by + * a single end_render call. + * @param display The display to begin rendering on. + */ void display_begin_render(struct display *display); + +/** + * Render a command list on the display. + * + * Render a command list on the given display. A command list is a series of + * rendering instructions. + * @param display The display to render on. + * @param command_list The command list to render. + */ void display_render(struct display *display, struct command_list *command_list); + +/** + * Finish a render pass on the display. + * + * This tells the display that rendering is done for now and a flush is + * triggered to update the display hardware. + * @param display The display to end rendering on. + */ void display_end_render(struct display *display); -struct command_list *command_list_create(uint32_t capacity, alloc_fn allocator, +/** + * Create a new command list. + * + * A command list records a series of commands for drawing text to a display. + * @param capacity The capacity of the command list. + * @param allocator Allocation callback to use for data in the command list. + * @param xoffset Column offset to apply to all operations in the list. + * @param yoffset Row offset to apply to all operations in the list. + * @param name Name for the command list. Useful for debugging. + * @returns A pointer to the created command list. + */ +struct command_list *command_list_create(uint32_t capacity, + void *(*allocator)(size_t), uint32_t xoffset, uint32_t yoffset, const char *name); +/** + * Enable/disable rendering of whitespace characters. + * + * ' ' will be rendered with a dot and '\t' as an arrow. + * @param list Command list to record command in. + * @param show True if whitespace chars should be displayed, false otherwise. + */ void command_list_set_show_whitespace(struct command_list *list, bool show); + +/** + * Set background color + * + * Set the background color to use for following draw commands to the specified + * index. Note that color indices > 15 might not be supported on all displays. + * @param list The command list to record command in. + * @param color_idx The color index to use as background (0-255) + */ void command_list_set_index_color_bg(struct command_list *list, uint8_t color_idx); + +/** + * Set background color + * + * Set the background color to use for following draw commands to the specified + * RGB color. Note that this might not be supported on all displays. + * @param list The command list to record command in. + * @param red Red value. + * @param green Green value. + * @param blue Blue value. + */ void command_list_set_color_bg(struct command_list *list, uint8_t red, uint8_t green, uint8_t blue); + +/** + * Set foreground color + * + * Set the foreground color to use for following draw commands to the specified + * index. Note that color indices > 15 might not be supported on all displays. + * @param list The command list to record command in. + * @param color_idx The color index to use as foreground (0-255) + */ void command_list_set_index_color_fg(struct command_list *list, uint8_t color_idx); + +/** + * Set foreground color + * + * Set the foreground color to use for following draw commands to the specified + * RGB color. Note that this might not be supported on all displays. + * @param list The command list to record command in. + * @param red Red value. + * @param green Green value. + * @param blue Blue value. + */ void command_list_set_color_fg(struct command_list *list, uint8_t red, uint8_t green, uint8_t blue); + +/** + * Reset the color and styling information. + * + * The following draw commands will have their formatting reset to the default. + * @param list The command list to record command in. + */ void command_list_reset_color(struct command_list *list); + +/** + * Draw text + * + * @param list Command list to record draw command in. + * @param col Column to start text at. + * @param row Row to start text at. + * @param data Bytes to write. + * @param len Number of bytes to write. + */ void command_list_draw_text(struct command_list *list, uint32_t col, uint32_t row, uint8_t *data, uint32_t len); + +/** + * Draw text, copying it to internal storage first. + * + * @param list Command list to record draw command in. + * @param col Column to start text at. + * @param row Row to start text at. + * @param data Bytes to write. + * @param len Number of bytes to write. + */ void command_list_draw_text_copy(struct command_list *list, uint32_t col, uint32_t row, uint8_t *data, uint32_t len); + +/** + * Draw a repeated character. + * + * @param list Command list to record draw command in. + * @param col Column to start text at. + * @param row Row to start text at. + * @param c Byte to repeat. + * @param nrepeat Number of times to repeat byte. + */ void command_list_draw_repeated(struct command_list *list, uint32_t col, uint32_t row, uint8_t c, uint32_t nrepeat); diff --git a/src/keyboard.c b/src/keyboard.c index 7059dec..14bb9dd 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -93,7 +93,7 @@ void parse_keys(uint8_t *bytes, uint32_t nbytes, struct key *out_keys, struct keyboard_update keyboard_update(struct keyboard *kbd, struct reactor *reactor, - alloc_fn frame_alloc) { + void *(*frame_alloc)(size_t)) { struct keyboard_update upd = (struct keyboard_update){ .keys = NULL, diff --git a/src/keyboard.h b/src/keyboard.h index a44a58f..09a71be 100644 --- a/src/keyboard.h +++ b/src/keyboard.h @@ -77,8 +77,6 @@ struct keyboard_update { }; struct reactor; -/** Typedef for any allocation function */ -typedef void *(*alloc_fn)(size_t); /** * Create a new keyboard @@ -112,7 +110,7 @@ struct keyboard keyboard_create_fd(struct reactor *reactor, int fd); */ struct keyboard_update keyboard_update(struct keyboard *kbd, struct reactor *reactor, - alloc_fn frame_alloc); + void *(*frame_alloc)(size_t)); /** * Does key represent the same key press as mod and c. diff --git a/src/main.c b/src/main.c index 0125110..3e4f334 100644 --- a/src/main.c +++ b/src/main.c @@ -24,10 +24,12 @@ bool running = true; void terminate() { running = false; } -static struct display display; +static struct display *display = NULL; static bool display_resized = false; void resized() { - display_resize(&display); + if (display != NULL) { + display_resize(display); + } display_resized = true; signal(SIGWINCH, resized); @@ -88,7 +90,7 @@ int main(int argc, char *argv[]) { // initialize display display = display_create(); - display_clear(&display); + display_clear(display); signal(SIGWINCH, resized); // init keyboard @@ -135,8 +137,9 @@ int main(int argc, char *argv[]) { // one main window struct window main_window = (struct window){ .buffer = buffers_add(&buflist, initial_buffer), - .height = display.height - 1, - .width = display.width, + .prev_buffer = NULL, + .height = display_height(display) - 1, + .width = display_width(display), .x = 0, .y = 0, }; @@ -148,10 +151,11 @@ int main(int argc, char *argv[]) { minibuffer_init(minibuffer); struct window minibuffer_window = (struct window){ .buffer = minibuffer, + .prev_buffer = NULL, .x = 0, - .y = display.height - 1, + .y = display_height(display) - 1, .height = 1, - .width = display.width, + .width = display_width(display), }; struct timespec buffer_begin, buffer_end, display_begin, display_end, @@ -174,11 +178,11 @@ int main(int argc, char *argv[]) { clock_gettime(CLOCK_MONOTONIC, &buffer_begin); if (display_resized) { - minibuffer_window.width = display.width; - minibuffer_window.y = display.height - 1; + minibuffer_window.width = display_width(display); + minibuffer_window.y = display_height(display) - 1; - main_window.height = display.height - 1; - main_window.width = display.width; + main_window.height = display_height(display) - 1; + main_window.width = display_width(display); display_resized = false; } @@ -209,14 +213,14 @@ int main(int argc, char *argv[]) { clock_gettime(CLOCK_MONOTONIC, &display_begin); uint32_t relline, relcol; - display_begin_render(&display); + display_begin_render(display); for (uint32_t windowi = 0; windowi < sizeof(windows) / sizeof(windows[0]); ++windowi) { - display_render(&display, command_lists[windowi]); + display_render(display, command_lists[windowi]); } - display_move_cursor(&display, dot_line + active_window->y, + display_move_cursor(display, dot_line + active_window->y, dot_col + active_window->x); - display_end_render(&display); + display_end_render(display); clock_gettime(CLOCK_MONOTONIC, &display_end); // this blocks for events, so if nothing has happened we block here. @@ -294,7 +298,7 @@ int main(int argc, char *argv[]) { if (minibuffer_focused()) { active_window = &minibuffer_window; } else { - // TODO: no + // TODO: not this active_window = &main_window; } @@ -302,8 +306,8 @@ int main(int argc, char *argv[]) { } buffers_destroy(&buflist); - display_clear(&display); - display_destroy(&display); + display_clear(display); + display_destroy(display); keymap_destroy(&global_keymap); command_registry_destroy(&commands); frame_allocator_destroy(&frame_allocator); diff --git a/src/minibuffer.c b/src/minibuffer.c index cf09e73..5bf5043 100644 --- a/src/minibuffer.c +++ b/src/minibuffer.c @@ -35,7 +35,7 @@ int32_t execute(struct command_ctx ctx, int argc, const char *argv[]) { // split on ' ' const char *argv[128] = {l}; - argc = 1; + argc = line.nbytes > 0 ? 1 : 0; for (uint32_t bytei = 0; bytei < line.nbytes; ++bytei) { uint8_t byte = line.text[bytei]; if (byte == ' ') { @@ -94,6 +94,10 @@ struct update_hook_result update(struct buffer *buffer, } void minibuffer_init(struct buffer *buffer) { + if (g_minibuffer.buffer != NULL) { + return; + } + g_minibuffer.buffer = buffer; struct binding bindings[] = { ANONYMOUS_BINDING(Ctrl, 'M', &execute_minibuffer_command), @@ -106,7 +110,7 @@ void minibuffer_init(struct buffer *buffer) { } void echo(uint32_t timeout, const char *fmt, va_list args) { - if (g_minibuffer.prompt_active) { + if (g_minibuffer.prompt_active || g_minibuffer.buffer == NULL) { return; } @@ -138,9 +142,14 @@ void minibuffer_echo_timeout(uint32_t timeout, const char *fmt, ...) { } void minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, ...) { + if (g_minibuffer.buffer == NULL) { + return; + } + minibuffer_clear(); g_minibuffer.prompt_active = true; g_minibuffer.prompt_command_ctx = command_ctx; + va_list args; va_start(args, fmt); vsnprintf(g_minibuffer.prompt, sizeof(g_minibuffer.prompt), fmt, args); @@ -152,6 +161,14 @@ void minibuffer_abort_prompt() { g_minibuffer.prompt_active = false; } -bool minibuffer_displaying() { return !buffer_is_empty(g_minibuffer.buffer); } -void minibuffer_clear() { buffer_clear(g_minibuffer.buffer); } +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); + } +} + bool minibuffer_focused() { return g_minibuffer.prompt_active; } diff --git a/src/minibuffer.h b/src/minibuffer.h index ec24f49..71885ec 100644 --- a/src/minibuffer.h +++ b/src/minibuffer.h @@ -6,14 +6,67 @@ struct buffer; struct command_ctx; +/** + * Initialize the minibuffer. + * + * Note that the minibuffer is a global instance and this function will do + * nothing if called more than once. + * @param buffer underlying buffer to use for text IO in the minibuffer. + */ void minibuffer_init(struct buffer *buffer); +/** + * Echo a message to the minibuffer. + * + * @param fmt Format string for the message. + * @param ... Format arguments. + */ void minibuffer_echo(const char *fmt, ...); + +/** + * Echo a message to the minibuffer that disappears after @ref timeout. + * + * @param timeout The timeout in seconds after which the message should + * disappear. + * @param fmt Format string for the message. + * @param ... Format arguments. + */ void minibuffer_echo_timeout(uint32_t timeout, const char *fmt, ...); +/** + * Prompt for user input in the minibuffer. + * + * This will move focus to the minibuffer and wait for user input, with the + * given prompt. + * @param command_ctx The command context to use to re-execute the calling + * command (or other command) when the user confirms the input. + * @param fmt Format string for the prompt. + * @param ... Format arguments. + */ void minibuffer_prompt(struct command_ctx command_ctx, const char *fmt, ...); + +/** + * Abort the current minibuffer prompt. + * + * This returns focus to the previously focused window. + */ void minibuffer_abort_prompt(); +/** + * Clear the current text in the minibuffer. + */ void minibuffer_clear(); + +/** + * Is the minibuffer currently displaying something? + * + * @returns True if the minibuffer is displaying anything, false otherwise. + */ bool minibuffer_displaying(); + +/** + * Is the minibuffer currently focused? + * + * @returns True if the minibuffer is currently focused, receiving user input. + */ bool minibuffer_focused(); diff --git a/src/text.c b/src/text.c index d1a0db3..ec80643 100644 --- a/src/text.c +++ b/src/text.c @@ -80,6 +80,11 @@ uint32_t byteidx_to_charidx(struct line *line, uint32_t byte_idx) { return utf8_nchars(line->data, byte_idx); } +uint32_t text_byteindex_to_col(struct text *text, uint32_t line, + uint32_t byteindex) { + return byteidx_to_charidx(&text->lines[line], byteindex); +} + void insert_at_col(struct line *line, uint32_t col, uint8_t *text, uint32_t len, uint32_t nchars) { diff --git a/src/text.h b/src/text.h index 213cf9e..85eb522 100644 --- a/src/text.h +++ b/src/text.h @@ -29,6 +29,8 @@ uint32_t text_num_lines(struct text *text); uint32_t text_line_length(struct text *text, uint32_t lineidx); uint32_t text_line_size(struct text *text, uint32_t lineidx); uint32_t text_col_to_byteindex(struct text *text, uint32_t line, uint32_t col); +uint32_t text_byteindex_to_col(struct text *text, uint32_t line, + uint32_t byteindex); struct text_chunk { uint8_t *text; diff --git a/src/window.c b/src/window.c index 5f1911a..10ded5e 100644 --- a/src/window.c +++ b/src/window.c @@ -7,3 +7,8 @@ void window_update_buffer(struct window *window, struct command_list *commands, 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 index 81535a0..14e041e 100644 --- a/src/window.h +++ b/src/window.h @@ -8,8 +8,11 @@ struct window { 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 new file mode 100644 index 0000000..ca1a7a0 --- /dev/null +++ b/test/allocator.c @@ -0,0 +1,31 @@ +#include "assert.h" +#include "test.h" + +#include "allocator.h" + +void test_frame_allocator() { + struct frame_allocator fa = frame_allocator_create(128); + + ASSERT(fa.capacity == 128, + "Expected frame allocator to be created with specified capacity"); + + void *bytes = frame_allocator_alloc(&fa, 128); + ASSERT( + bytes != NULL, + "Expected to be able to allocate bytes from frame allocator"); + + void *bytes_again = frame_allocator_alloc(&fa, 128); + ASSERT(bytes_again == NULL, + "Expected to not be able to allocate bytes " + "from frame allocator a second time"); + + frame_allocator_clear(&fa); + void *bytes_after_clear = frame_allocator_alloc(&fa, 128); + ASSERT(bytes_after_clear != NULL, + "Expected to be able to allocate bytes from frame " + "allocator again after clearing it"); + + frame_allocator_destroy(&fa); +} + +void run_allocator_tests() { run_test(test_frame_allocator); } diff --git a/test/main.c b/test/main.c index 68241f9..7663f8f 100644 --- a/test/main.c +++ b/test/main.c @@ -4,7 +4,6 @@ #include #include -#include "bits/time.h" #include "test.h" void handle_abort() { exit(1); } @@ -31,6 +30,12 @@ int main() { printf("\nšŸ“  \x1b[1;36mRunning keyboard tests...\x1b[0m\n"); run_keyboard_tests(); + printf("\nšŸ’¾ \x1b[1;36mRunning allocator tests...\x1b[0m\n"); + run_allocator_tests(); + + printf("\n🐜 \x1b[1;36mRunning minibuffer tests...\x1b[0m\n"); + run_minibuffer_tests(); + struct timespec elapsed; clock_gettime(CLOCK_MONOTONIC, &elapsed); uint64_t elapsed_nanos = diff --git a/test/minibuffer.c b/test/minibuffer.c new file mode 100644 index 0000000..dc29648 --- /dev/null +++ b/test/minibuffer.c @@ -0,0 +1,72 @@ +#include "assert.h" +#include "stdlib.h" +#include "test.h" + +#include "buffer.h" +#include "display.h" +#include "minibuffer.h" + +static struct buffer b = {0}; + +void init() { + if (b.name == NULL) { + b = buffer_create("minibuffer", false); + } + + minibuffer_init(&b); +} + +void test_minibuffer_echo() { + uint32_t relline, relcol; + + // TODO: how to clear this? + struct command_list *list = + command_list_create(10, malloc, 0, 0, "minibuffer"); + + init(); + ASSERT(!minibuffer_displaying(), + "Minibuffer should have nothing to display before echoing"); + + minibuffer_echo("Test %s", "test"); + ASSERT(minibuffer_displaying(), "Minibuffer should now have text to display"); + + minibuffer_clear(); + 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); + ASSERT(!minibuffer_displaying(), + "A zero timeout echo should be cleared after first update"); +} + +int32_t fake(struct command_ctx ctx, int argc, const char *argv[]) { return 0; } + +void test_minibuffer_prompt() { + init(); + ASSERT(!minibuffer_focused(), + "Minibuffer should not be focused without reason"); + + struct command cmd = { + .fn = fake, + .name = "fake", + .userdata = NULL, + }; + struct command_ctx ctx = {.commands = NULL, + .active_window = NULL, + .buffers = NULL, + .userdata = NULL, + .self = &cmd}; + minibuffer_prompt(ctx, "prompt %s: ", "yes"); + + ASSERT(minibuffer_focused(), "Minibuffer should get focused when prompting"); + + minibuffer_abort_prompt(); + ASSERT(!minibuffer_focused(), + "Minibuffer must not be focused after prompt has been aborted"); +} + +void run_minibuffer_tests() { + run_test(test_minibuffer_echo); + run_test(test_minibuffer_prompt); +} diff --git a/test/test.h b/test/test.h index 2d9d8af..f777916 100644 --- a/test/test.h +++ b/test/test.h @@ -1,3 +1,6 @@ +#ifndef _TEST_H_ +#define _TEST_H_ + #include #define run_test(fn) \ @@ -11,3 +14,7 @@ void run_utf8_tests(); void run_text_tests(); void run_command_tests(); void run_keyboard_tests(); +void run_allocator_tests(); +void run_minibuffer_tests(); + +#endif -- cgit v1.2.3