summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common.mk3
-rw-r--r--src/binding.h68
-rw-r--r--src/buffer.c63
-rw-r--r--src/buffer.h48
-rw-r--r--src/command.c51
-rw-r--r--src/display.c37
-rw-r--r--src/display.h187
-rw-r--r--src/keyboard.c2
-rw-r--r--src/keyboard.h4
-rw-r--r--src/main.c40
-rw-r--r--src/minibuffer.c25
-rw-r--r--src/minibuffer.h53
-rw-r--r--src/text.c5
-rw-r--r--src/text.h2
-rw-r--r--src/window.c5
-rw-r--r--src/window.h3
-rw-r--r--test/allocator.c31
-rw-r--r--test/main.c7
-rw-r--r--test/minibuffer.c72
-rw-r--r--test/test.h7
20 files changed, 648 insertions, 65 deletions
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 <unistd.h>
#include <wchar.h>
+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 <errno.h>
#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
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 <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
+#include <termios.h>
#include <unistd.h>
#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 <stddef.h>
#include <stdint.h>
-#include <termios.h>
-
-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 <capacity> bytes from frame allocator");
+
+ void *bytes_again = frame_allocator_alloc(&fa, 128);
+ ASSERT(bytes_again == NULL,
+ "Expected to not be able to allocate <capacity> 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 <capacity> 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 <stdlib.h>
#include <time.h>
-#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 <stdio.h>
#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