diff options
| -rw-r--r-- | src/binding.h | 8 | ||||
| -rw-r--r-- | src/buffer.c | 238 | ||||
| -rw-r--r-- | src/buffer.h | 69 | ||||
| -rw-r--r-- | src/command.c | 13 | ||||
| -rw-r--r-- | src/command.h | 8 | ||||
| -rw-r--r-- | src/display.c | 148 | ||||
| -rw-r--r-- | src/display.h | 33 | ||||
| -rw-r--r-- | src/keyboard.c | 65 | ||||
| -rw-r--r-- | src/keyboard.h | 8 | ||||
| -rw-r--r-- | src/main.c | 93 | ||||
| -rw-r--r-- | src/minibuffer.c | 11 | ||||
| -rw-r--r-- | src/text.c | 123 | ||||
| -rw-r--r-- | src/text.h | 5 | ||||
| -rw-r--r-- | src/utf8.c | 49 | ||||
| -rw-r--r-- | src/utf8.h | 2 | ||||
| -rw-r--r-- | test/text.c | 23 | ||||
| -rw-r--r-- | test/utf8.c | 6 |
17 files changed, 548 insertions, 354 deletions
diff --git a/src/binding.h b/src/binding.h index f00efed..bfde9fc 100644 --- a/src/binding.h +++ b/src/binding.h @@ -11,14 +11,14 @@ enum binding_type { BindingType_Command, BindingType_Keymap }; #define BINDING(mod_, c_, command_) \ (struct binding) { \ - .key = {.mod = mod_, .bytes[0] = c_, .nbytes = 1}, \ - .type = BindingType_Command, .command = hash_command_name(command_) \ + .key = {.mod = mod_, .key = c_}, .type = BindingType_Command, \ + .command = hash_command_name(command_) \ } #define PREFIX(mod_, c_, keymap_) \ (struct binding) { \ - .key = {.mod = mod_, .bytes[0] = c_, .nbytes = 1}, \ - .type = BindingType_Keymap, .keymap = keymap_ \ + .key = {.mod = mod_, .key = c_}, .type = BindingType_Keymap, \ + .keymap = keymap_ \ } struct binding { diff --git a/src/buffer.c b/src/buffer.c index 17d62af..9d9fb68 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -19,40 +19,47 @@ struct buffer buffer_create(const char *name, bool modeline) { .text = text_create(10), .dot_col = 0, .dot_line = 0, - .modeline_buf = modeline ? (uint8_t *)malloc(1024) : NULL, .keymaps = calloc(10, sizeof(struct keymap)), .nkeymaps = 1, .scroll_col = 0, .scroll_line = 0, - .npre_update_hooks = 0, - .pre_update_hooks = {0}, - .npost_update_hooks = 0, - .post_update_hooks = {0}, + .update_hooks = {0}, .nkeymaps_max = 10}; b.keymaps[0] = keymap_create("buffer-default", 128); struct binding bindings[] = { BINDING(Ctrl, 'B', "backward-char"), + BINDING(Meta, 'D', "backward-char"), BINDING(Ctrl, 'F', "forward-char"), + BINDING(Meta, 'C', "forward-char"), BINDING(Ctrl, 'P', "backward-line"), + BINDING(Meta, 'A', "backward-line"), BINDING(Ctrl, 'N', "forward-line"), + BINDING(Meta, 'B', "forward-line"), BINDING(Ctrl, 'A', "beginning-of-line"), BINDING(Ctrl, 'E', "end-of-line"), BINDING(Ctrl, 'M', "newline"), + BINDING(Ctrl, 'K', "kill-line"), + BINDING(Meta, '~', "delete-char"), BINDING(Ctrl, '?', "backward-delete-char"), }; keymap_bind_keys(&b.keymaps[0], bindings, sizeof(bindings) / sizeof(bindings[0])); + if (modeline) { + struct modeline *modeline = calloc(1, sizeof(struct modeline)); + modeline->buffer = malloc(1024); + buffer_add_update_hook(&b, buffer_modeline_hook, modeline); + } + return b; } void buffer_destroy(struct buffer *buffer) { - free(buffer->modeline_buf); text_destroy(buffer->text); free(buffer->text); } @@ -117,6 +124,16 @@ void moveh(struct buffer *buffer, int coldelta) { } } +void buffer_kill_line(struct buffer *buffer) { + uint32_t nchars = + text_line_length(buffer->text, buffer->dot_line) - buffer->dot_col; + if (nchars == 0) { + nchars = 1; + } + + text_delete(buffer->text, buffer->dot_line, buffer->dot_col, nchars); +} + void buffer_forward_delete_char(struct buffer *buffer) { text_delete(buffer->text, buffer->dot_line, buffer->dot_col, 1); } @@ -185,7 +202,7 @@ void buffer_to_file(struct buffer *buffer) { int buffer_add_text(struct buffer *buffer, uint8_t *text, uint32_t nbytes) { uint32_t lines_added, cols_added; - text_append_at(buffer->text, buffer->dot_line, buffer->dot_col, text, nbytes, + text_insert_at(buffer->text, buffer->dot_line, buffer->dot_col, text, nbytes, &lines_added, &cols_added); movev(buffer, lines_added); @@ -202,78 +219,28 @@ void buffer_newline(struct buffer *buffer) { buffer_add_text(buffer, (uint8_t *)"\n", 1); } -uint32_t buffer_add_pre_update_hook(struct buffer *buffer, - pre_update_hook hook) { - buffer->pre_update_hooks[buffer->npre_update_hooks] = hook; - ++buffer->npre_update_hooks; - - return buffer->npre_update_hooks - 1; -} -uint32_t buffer_add_post_update_hook(struct buffer *buffer, - post_update_hook hook) { - buffer->post_update_hooks[buffer->npost_update_hooks] = hook; - ++buffer->npost_update_hooks; - - return buffer->npost_update_hooks - 1; -} - -void buffer_remove_pre_update_hook(struct buffer *buffer, uint32_t hook_id) { - // TODO: is it needed? -} - -void buffer_remove_post_update_hook(struct buffer *buffer, uint32_t hook_id) { - // TODO: is it needed? -} - -bool modeline_update(struct buffer *buffer, uint32_t width, - uint64_t frame_time) { - char buf[width * 4]; - - static uint64_t samples[10] = {0}; - static uint32_t samplei = 0; - static uint64_t avg = 0; - - // calc a moving average with a window of the last 10 frames - ++samplei; - samplei %= 10; - avg += 0.1 * (frame_time - samples[samplei]); - samples[samplei] = frame_time; +uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook, + void *userdata) { + struct update_hook *h = + &buffer->update_hooks.hooks[buffer->update_hooks.nhooks]; + h->callback = hook; + h->userdata = userdata; - time_t now = time(NULL); - struct tm *lt = localtime(&now); - char left[128], right[128]; - snprintf(left, 128, " %-16s (%d, %d)", buffer->name, buffer->dot_line + 1, - buffer->dot_col); - snprintf(right, 128, "(%.2f ms) %02d:%02d", frame_time / 1e6, lt->tm_hour, - lt->tm_min); + ++buffer->update_hooks.nhooks; - snprintf(buf, width * 4, "\x1b[100m%s%*s%s\x1b[0m", left, - (int)(width - (strlen(left) + strlen(right))), "", right); - if (strcmp(buf, (char *)buffer->modeline_buf) != 0) { - buffer->modeline_buf = realloc(buffer->modeline_buf, width * 4); - strcpy((char *)buffer->modeline_buf, buf); - return true; - } else { - return false; - } + // TODO: cant really have this if someone wants to remove a hook + return buffer->update_hooks.nhooks - 1; } struct cmdbuf { - struct render_cmd *cmds; - uint32_t ncmds; + struct command_list *cmds; uint32_t first_line; }; void render_line(struct text_chunk *line, void *userdata) { struct cmdbuf *cmdbuf = (struct cmdbuf *)userdata; - - struct render_cmd *cmd = &cmdbuf->cmds[cmdbuf->ncmds]; - cmd->col = 0; - cmd->data = line->text; - cmd->len = line->nbytes; - cmd->row = line->line - cmdbuf->first_line; - - ++cmdbuf->ncmds; + command_list_draw_text(cmdbuf->cmds, 0, line->line - cmdbuf->first_line, + line->text, line->nbytes); } void scroll(struct buffer *buffer, int line_delta, int col_delta) { @@ -296,29 +263,107 @@ void to_relative(struct buffer *buffer, uint32_t line, uint32_t col, *rel_line = (int64_t)line - (int64_t)buffer->scroll_line; } +uint32_t visual_dot_col(struct buffer *buffer, uint32_t dot_col) { + uint32_t visual_dot_col = dot_col; + struct text_chunk line = text_get_line(buffer->text, buffer->dot_line); + for (uint32_t bytei = 0; + bytei < + text_col_to_byteindex(buffer->text, buffer->dot_line, buffer->dot_col); + ++bytei) { + if (line.text[bytei] == '\t') { + visual_dot_col += 3; + } + } + + return visual_dot_col; +} + void buffer_relative_dot_pos(struct buffer *buffer, uint32_t *relline, uint32_t *relcol) { int64_t rel_col, rel_line; - to_relative(buffer, buffer->dot_line, buffer->dot_col, &rel_line, &rel_col); + uint32_t visual_col = visual_dot_col(buffer, buffer->dot_col); + to_relative(buffer, buffer->dot_line, visual_col, &rel_line, &rel_col); *relline = rel_line < 0 ? 0 : (uint32_t)rel_line; *relcol = rel_col < 0 ? 0 : (uint32_t)rel_col; } -struct buffer_update buffer_update(struct buffer *buffer, uint32_t width, - uint32_t height, alloc_fn frame_alloc, - uint64_t frame_time) { +struct margin buffer_modeline_hook(struct buffer *buffer, + struct command_list *commands, + uint32_t width, uint32_t height, + uint64_t frame_time, void *userdata) { + char buf[width * 4]; + + static uint64_t samples[10] = {0}; + static uint32_t samplei = 0; + static uint64_t avg = 0; + + // calc a moving average with a window of the last 10 frames + ++samplei; + samplei %= 10; + avg += 0.1 * (frame_time - samples[samplei]); + samples[samplei] = frame_time; + + time_t now = time(NULL); + struct tm *lt = localtime(&now); + char left[128], right[128]; + + uint32_t relcol, relline; + buffer_relative_dot_pos(buffer, &relline, &relcol); + + snprintf(left, 128, " %-16s (%d, %d)", buffer->name, relline + 1, relcol); + snprintf(right, 128, "(%.2f ms) %02d:%02d", frame_time / 1e6, lt->tm_hour, + lt->tm_min); + + snprintf(buf, width * 4, "%s%*s%s", left, + (int)(width - (strlen(left) + strlen(right))), "", right); + + struct modeline *modeline = (struct modeline *)userdata; + if (strcmp(buf, (char *)modeline->buffer) != 0) { + modeline->buffer = realloc(modeline->buffer, width * 4); + strcpy((char *)modeline->buffer, buf); + + command_list_set_index_color_bg(commands, 8); + command_list_draw_text(commands, 0, height - 1, modeline->buffer, + strlen((char *)modeline->buffer)); + command_list_reset_color(commands); + } + + struct margin m = {0}; + m.bottom = 1; + return m; +} + +void buffer_update(struct buffer *buffer, uint32_t width, uint32_t height, + struct command_list *commands, uint64_t frame_time) { if (width == 0 || height == 0) { - return (struct buffer_update){.cmds = NULL, .ncmds = 0}; + return; } - for (uint32_t hooki = 0; hooki < buffer->npre_update_hooks; ++hooki) { - buffer->pre_update_hooks[hooki](buffer); + struct margin total_margins = {0}; + for (uint32_t hooki = 0; hooki < buffer->update_hooks.nhooks; ++hooki) { + struct update_hook *h = &buffer->update_hooks.hooks[hooki]; + struct margin margins = + h->callback(buffer, commands, width, height, frame_time, h->userdata); + + if (margins.left > total_margins.left) { + total_margins.left = margins.left; + } + if (margins.right > total_margins.right) { + total_margins.right = margins.right; + } + if (margins.top > total_margins.top) { + total_margins.top = margins.top; + } + if (margins.bottom > total_margins.bottom) { + total_margins.bottom = margins.bottom; + } } // reserve space for modeline - uint32_t bufheight = buffer->modeline_buf != NULL ? height - 1 : height; + uint32_t bufheight = height - (total_margins.top + total_margins.bottom); + uint32_t bufwidth = width - (total_margins.left + total_margins.right); int64_t rel_line, rel_col; to_relative(buffer, buffer->dot_line, buffer->dot_col, &rel_line, &rel_col); @@ -331,53 +376,22 @@ struct buffer_update buffer_update(struct buffer *buffer, uint32_t width, if (rel_col < 0) { col_delta = rel_col; - } else if (rel_col > width) { - col_delta = rel_col - width; + } else if (rel_col > bufwidth) { + col_delta = rel_col - bufwidth; } scroll(buffer, line_delta, col_delta); - struct render_cmd *cmds = - (struct render_cmd *)frame_alloc(sizeof(struct render_cmd) * (height)); - struct cmdbuf cmdbuf = (struct cmdbuf){ - .cmds = cmds, - .ncmds = 0, + .cmds = commands, .first_line = buffer->scroll_line, }; text_for_each_line(buffer->text, buffer->scroll_line, bufheight, render_line, &cmdbuf); uint32_t nlines = text_num_lines(buffer->text); - uint32_t ncmds = cmdbuf.ncmds; for (uint32_t linei = nlines - buffer->scroll_line; linei < bufheight; ++linei) { - cmds[ncmds] = (struct render_cmd){ - .col = 0, - .row = linei, - .data = NULL, - .len = 0, - }; - ++ncmds; - } - - if (buffer->modeline_buf != NULL && - modeline_update(buffer, width, frame_time)) { - cmds[ncmds] = (struct render_cmd){ - .col = 0, - .row = bufheight, - .data = buffer->modeline_buf, - .len = strlen((char *)buffer->modeline_buf), - }; - ++ncmds; - } - - for (uint32_t hooki = 0; hooki < buffer->npost_update_hooks; ++hooki) { - buffer->post_update_hooks[hooki](buffer); + command_list_draw_text(commands, 0, linei, NULL, 0); } - - struct buffer_update upd = - (struct buffer_update){.cmds = cmds, .ncmds = ncmds}; - - return upd; } diff --git a/src/buffer.h b/src/buffer.h index e20e40a..141c1ea 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -7,9 +7,33 @@ struct keymap; struct reactor; +struct command_list; -typedef void (*pre_update_hook)(struct buffer *); -typedef void (*post_update_hook)(struct buffer *); +struct margin { + uint32_t left; + uint32_t right; + uint32_t top; + uint32_t bottom; +}; + +typedef struct margin (*update_hook_cb)(struct buffer *buffer, + struct command_list *commands, + uint32_t width, uint32_t height, + uint64_t frame_time, void *userdata); + +struct update_hook { + update_hook_cb callback; + void *userdata; +}; + +struct update_hooks { + struct update_hook hooks[32]; + uint32_t nhooks; +}; + +struct modeline { + uint8_t *buffer; +}; struct buffer { const char *name; @@ -20,8 +44,6 @@ struct buffer { uint32_t dot_line; uint32_t dot_col; - uint8_t *modeline_buf; - // local keymaps struct keymap *keymaps; uint32_t nkeymaps; @@ -30,19 +52,9 @@ struct buffer { uint32_t scroll_line; uint32_t scroll_col; - pre_update_hook pre_update_hooks[32]; - uint32_t npre_update_hooks; - post_update_hook post_update_hooks[32]; - uint32_t npost_update_hooks; -}; - -struct buffer_update { - struct render_cmd *cmds; - uint64_t ncmds; + struct update_hooks update_hooks; }; -typedef void *(alloc_fn)(size_t); - struct buffer buffer_create(const char *name, bool modeline); void buffer_destroy(struct buffer *buffer); @@ -53,6 +65,7 @@ int buffer_add_text(struct buffer *buffer, uint8_t *text, uint32_t nbytes); void buffer_clear(struct buffer *buffer); bool buffer_is_empty(struct buffer *buffer); +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); @@ -66,26 +79,30 @@ void buffer_newline(struct buffer *buffer); void buffer_relative_dot_pos(struct buffer *buffer, uint32_t *relline, uint32_t *relcol); -uint32_t buffer_add_pre_update_hook(struct buffer *buffer, - pre_update_hook hook); -uint32_t buffer_add_post_update_hook(struct buffer *buffer, - post_update_hook hook); -void buffer_remove_pre_update_hook(struct buffer *buffer, uint32_t hook_id); -void buffer_remove_post_update_hook(struct buffer *buffer, uint32_t hook_id); +uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook, + void *userdata); struct buffer buffer_from_file(const char *filename, struct reactor *reactor); void buffer_to_file(struct buffer *buffer); -struct buffer_update buffer_update(struct buffer *buffer, uint32_t width, - uint32_t height, alloc_fn frame_alloc, - uint64_t frame_time); +void buffer_update(struct buffer *buffer, uint32_t width, uint32_t height, + struct command_list *commands, uint64_t frame_time); + +struct margin buffer_modeline_hook(struct buffer *buffer, + struct command_list *commands, + uint32_t width, uint32_t height, + uint64_t frame_time, void *userdata); // commands #define BUFFER_WRAPCMD(fn) \ - static void fn##_cmd(struct command_ctx ctx, int argc, const char *argv[]) { \ + static int32_t fn##_cmd(struct command_ctx ctx, int argc, \ + const char *argv[]) { \ fn(ctx.current_buffer); \ + return 0; \ } +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_forward_char); @@ -97,6 +114,8 @@ BUFFER_WRAPCMD(buffer_newline) BUFFER_WRAPCMD(buffer_to_file); static struct command BUFFER_COMMANDS[] = { + {.name = "kill-line", .fn = buffer_kill_line_cmd}, + {.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 = "forward-char", .fn = buffer_forward_char_cmd}, diff --git a/src/command.c b/src/command.c index fcf53e2..a667750 100644 --- a/src/command.c +++ b/src/command.c @@ -2,7 +2,7 @@ #include <stdlib.h> -struct commands command_list_create(uint32_t capacity) { +struct commands command_registry_create(uint32_t capacity) { return (struct commands){ .commands = calloc(capacity, sizeof(struct hashed_command)), .ncommands = 0, @@ -10,7 +10,7 @@ struct commands command_list_create(uint32_t capacity) { }; } -void command_list_destroy(struct commands *commands) { +void command_registry_destroy(struct commands *commands) { free(commands->commands); commands->ncommands = 0; commands->capacity = 0; @@ -68,10 +68,7 @@ struct command *lookup_command_by_hash(struct commands *commands, int32_t execute_command(struct command *command, struct buffer *current_buffer, int argc, const char *argv[]) { - command->fn((struct command_ctx){.current_buffer = current_buffer, - .userdata = command->userdata}, - argc, argv); - - // TODO - return 0; + return command->fn((struct command_ctx){.current_buffer = current_buffer, + .userdata = command->userdata}, + argc, argv); } diff --git a/src/command.h b/src/command.h index b02c74a..fe998ae 100644 --- a/src/command.h +++ b/src/command.h @@ -7,8 +7,8 @@ struct command_ctx { void *userdata; }; -typedef void (*command_fn)(struct command_ctx ctx, int argc, - const char *argv[]); +typedef int32_t (*command_fn)(struct command_ctx ctx, int argc, + const char *argv[]); struct command { const char *name; @@ -27,8 +27,8 @@ struct commands { uint32_t capacity; }; -struct commands command_list_create(uint32_t capacity); -void command_list_destroy(struct commands *commands); +struct commands command_registry_create(uint32_t capacity); +void command_registry_destroy(struct commands *commands); uint32_t register_command(struct commands *commands, struct command *command); void register_commands(struct commands *command_list, struct command *commands, diff --git a/src/display.c b/src/display.c index b382ea1..94b1c36 100644 --- a/src/display.c +++ b/src/display.c @@ -3,6 +3,7 @@ #include "buffer.h" +#include <stdarg.h> #include <stdio.h> #include <sys/ioctl.h> #include <unistd.h> @@ -47,7 +48,15 @@ void display_destroy(struct display *display) { } void putbytes(uint8_t *line_bytes, uint32_t line_length) { - fwrite(line_bytes, 1, line_length, stdout); + for (uint32_t bytei = 0; bytei < line_length; ++bytei) { + uint8_t byte = line_bytes[bytei]; + + if (byte == '\t') { + fputs(" ", stdout); + } else { + fputc(byte, stdout); + } + } } void putbyte(uint8_t c) { putc(c, stdout); } @@ -84,23 +93,130 @@ void delete_to_eol() { putbytes(bytes, 3); } -void display_update(struct display *display, struct render_cmd_buf *cmd_bufs, - uint32_t ncmd_bufs, uint32_t currow, uint32_t curcol) { - for (uint32_t bufi = 0; bufi < ncmd_bufs; ++bufi) { - struct render_cmd_buf *buf = &cmd_bufs[bufi]; - uint64_t ncmds = buf->ncmds; - struct render_cmd *cmds = buf->cmds; +struct command_list *command_list_create(uint32_t capacity, alloc_fn allocator, + uint32_t xoffset, uint32_t yoffset) { + struct command_list *command_list = allocator(sizeof(struct command_list)); - for (uint64_t cmdi = 0; cmdi < ncmds; ++cmdi) { - struct render_cmd *cmd = &cmds[cmdi]; - display_move_cursor(display, cmd->row + buf->yoffset, - cmd->col + buf->xoffset); - putbytes(cmd->data, cmd->len); - delete_to_eol(); - } + command_list->capacity = capacity; + command_list->ncmds = 0; + command_list->xoffset = xoffset; + command_list->yoffset = yoffset; + command_list->format_len = 0; + + command_list->cmds = allocator(sizeof(struct render_command) * capacity); + + return command_list; +} + +void push_format(struct command_list *list, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + if (list->format_len == 0) { + list->format[0] = ESC; + list->format[1] = '['; + + list->format_len = 2; + } + + if (list->format_len > 2) { + list->format[list->format_len] = ';'; + ++list->format_len; + } + + uint32_t format_space_left = sizeof(list->format) - 1 - list->format_len; + list->format_len += vsnprintf((char *)(list->format + list->format_len), + format_space_left, fmt, args); + + va_end(args); +} + +void flush_format(struct command_list *list, uint32_t col, uint32_t row) { + list->format[list->format_len] = 'm'; + ++list->format_len; + + struct render_command *cmd = &list->cmds[list->ncmds]; + cmd->data = list->format; + cmd->col = col + list->xoffset; + cmd->row = row + list->yoffset; + cmd->len = list->format_len; + + list->format_len = 0; + + ++list->ncmds; +} + +void command_list_draw_text(struct command_list *list, uint32_t col, + uint32_t row, uint8_t *data, uint32_t len) { + uint32_t needed_capacity = list->ncmds + 1; + if (list->format_len > 0) { + ++needed_capacity; } - display_move_cursor(display, currow, curcol); + if (needed_capacity > list->capacity) { + // TODO: better + return; + } + + if (list->format_len > 0) { + flush_format(list, col, row); + } + + struct render_command *cmd = &list->cmds[list->ncmds]; + cmd->data = data; + cmd->col = col + list->xoffset; + cmd->row = row + list->yoffset; + cmd->len = len; + + ++list->ncmds; +} - fflush(stdout); +void command_list_set_index_color_fg(struct command_list *list, + uint8_t color_idx) { + if (color_idx < 8) { + push_format(list, "%d", 30 + color_idx); + } else if (color_idx < 16) { + push_format(list, "%d", 90 + color_idx); + } else { + push_format(list, "30;5;%d", color_idx); + } +} +void command_list_set_color_fg(struct command_list *list, uint8_t red, + uint8_t green, uint8_t blue) { + push_format(list, "30;2;%d;%d;%d", red, green, blue); +} + +void command_list_set_index_color_bg(struct command_list *list, + uint8_t color_idx) { + if (color_idx < 8) { + push_format(list, "%d", 40 + color_idx); + } else if (color_idx < 16) { + push_format(list, "%d", 100 + color_idx); + } else { + push_format(list, "40;5;%d", color_idx); + } } + +void command_list_set_color_bg(struct command_list *list, uint8_t red, + uint8_t green, uint8_t blue) { + push_format(list, "40;2;%d;%d;%d", red, green, blue); +} + +void command_list_reset_color(struct command_list *list) { + push_format(list, "0"); +} + +void display_render(struct display *display, + struct command_list *command_list) { + + struct command_list *cl = command_list; + + for (uint64_t cmdi = 0; cmdi < cl->ncmds; ++cmdi) { + struct render_command *cmd = &cl->cmds[cmdi]; + display_move_cursor(display, cmd->row, cmd->col); + putbytes(cmd->data, cmd->len); + delete_to_eol(); + } +} + +void display_begin_render(struct display *display) {} +void display_end_render(struct display *display) { fflush(stdout); } diff --git a/src/display.h b/src/display.h index 088a487..c1c3667 100644 --- a/src/display.h +++ b/src/display.h @@ -1,3 +1,4 @@ +#include <stddef.h> #include <stdint.h> #include <termios.h> @@ -9,7 +10,7 @@ struct display { uint32_t height; }; -struct render_cmd { +struct render_command { uint32_t col; uint32_t row; @@ -17,11 +18,15 @@ struct render_cmd { uint32_t len; }; -struct render_cmd_buf { - struct render_cmd *cmds; +struct command_list { + struct render_command *cmds; uint64_t ncmds; + uint64_t capacity; uint32_t xoffset; uint32_t yoffset; + + uint8_t format[64]; + uint32_t format_len; }; struct display display_create(); @@ -29,5 +34,23 @@ void display_destroy(struct display *display); void display_clear(struct display *display); void display_move_cursor(struct display *display, uint32_t row, uint32_t col); -void display_update(struct display *display, struct render_cmd_buf *cmd_bufs, - uint32_t ncmd_bufs, uint32_t currow, uint32_t curcol); + +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); + +void command_list_set_index_color_bg(struct command_list *list, + uint8_t color_idx); +void command_list_set_color_bg(struct command_list *list, uint8_t red, + uint8_t green, uint8_t blue); +void command_list_set_index_color_fg(struct command_list *list, + uint8_t color_idx); +void command_list_set_color_fg(struct command_list *list, uint8_t red, + uint8_t green, uint8_t blue); +void command_list_reset_color(struct command_list *list); +void command_list_draw_text(struct command_list *list, uint32_t col, + uint32_t row, uint8_t *data, uint32_t len); diff --git a/src/keyboard.c b/src/keyboard.c index b4c0c6d..ae74e8e 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -26,33 +26,28 @@ void parse_keys(uint8_t *bytes, uint32_t nbytes, struct key *out_keys, uint8_t b = bytes[bytei]; struct key *kp = &out_keys[nkps]; - kp->nbytes = 1; + kp->start = bytei; + bool inserted = true; if (b == 0x1b) { // meta kp->mod |= Meta; } else if (b >= 0x00 && b <= 0x1f) { // ctrl char kp->mod |= Ctrl; - kp->bytes[0] = b | 0x40; - ++nkps; - prevkp = kp; + kp->key = b | 0x40; } else if (b == 0x7f) { // ^? kp->mod |= Ctrl; - kp->bytes[0] = '?'; - ++nkps; - prevkp = kp; - } else if (utf8_byte_is_unicode_start((uint8_t)b)) { - kp->bytes[0] = b; - ++nkps; - prevkp = kp; - } else if (utf8_byte_is_unicode_continuation((uint8_t)b)) { - prevkp->bytes[prevkp->nbytes] = b; - ++prevkp->nbytes; - } else { /* ascii char */ - if (prevkp->mod & Meta) { - prevkp->bytes[0] = b; - } else { - kp->bytes[0] = b; - } + kp->key = '?'; + } else if (prevkp->mod & Meta) { + prevkp->key = b; + prevkp->end = bytei + 1; + inserted = false; + } else { + inserted = false; + } + + kp->end = bytei + 1; + + if (inserted) { ++nkps; prevkp = kp; } @@ -64,8 +59,12 @@ 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 keyboard_update upd = - (struct keyboard_update){.keys = {0}, .nkeys = 0}; + struct keyboard_update upd = (struct keyboard_update){ + .keys = {0}, + .nkeys = 0, + .nbytes = 0, + .raw = {0}, + }; if (!kbd->has_data) { if (reactor_poll_event(reactor, kbd->reactor_event_id)) { @@ -75,11 +74,15 @@ struct keyboard_update keyboard_update(struct keyboard *kbd, } } - uint8_t bytes[32] = {0}; - int nbytes = read(STDIN_FILENO, bytes, 32); + int nbytes = read(STDIN_FILENO, upd.raw, 32); if (nbytes > 0) { - parse_keys(bytes, nbytes, upd.keys, &upd.nkeys, &kbd->last_key); + upd.nbytes = nbytes; + parse_keys(upd.raw, upd.nbytes, upd.keys, &upd.nkeys, &kbd->last_key); + + if (nbytes < 32) { + kbd->has_data = false; + } } else if (nbytes == EAGAIN) { kbd->has_data = false; } @@ -88,12 +91,11 @@ struct keyboard_update keyboard_update(struct keyboard *kbd, } bool key_equal_char(struct key *key, uint8_t mod, uint8_t c) { - return key->bytes[0] == c && key->mod == mod; + return key->key == c && key->mod == mod; } bool key_equal(struct key *key1, struct key *key2) { - return memcmp(key1->bytes, key2->bytes, key1->nbytes) == 0 && - key1->mod == key2->mod && key1->nbytes == key2->nbytes; + return key1->key == key2->key && key1->mod == key2->mod; } void key_name(struct key *key, char *buf, size_t capacity) { @@ -107,10 +109,5 @@ void key_name(struct key *key, char *buf, size_t capacity) { break; } - uint8_t lower[6]; - for (uint32_t bytei = 0; bytei < key->nbytes; ++bytei) { - lower[bytei] = tolower(key->bytes[bytei]); - } - - snprintf(buf, capacity, "%s%.*s", mod, key->nbytes, lower); + snprintf(buf, capacity, "%s%c", mod, tolower(key->key)); } diff --git a/src/keyboard.h b/src/keyboard.h index 18630c3..b7f1940 100644 --- a/src/keyboard.h +++ b/src/keyboard.h @@ -8,9 +8,10 @@ enum modifiers { }; struct key { - uint8_t bytes[6]; - uint8_t nbytes; + uint8_t key; uint8_t mod; + uint8_t start; + uint8_t end; }; struct keyboard { @@ -22,6 +23,9 @@ struct keyboard { struct keyboard_update { struct key keys[32]; uint32_t nkeys; + + uint8_t raw[32]; + uint32_t nbytes; }; struct reactor; @@ -46,16 +46,20 @@ bool running = true; void terminate() { running = false; } -void _abort(struct command_ctx ctx, int argc, const char *argv[]) { +int32_t _abort(struct command_ctx ctx, int argc, const char *argv[]) { minibuffer_echo_timeout(4, "๐ฃ aborted"); + return 0; } -void unimplemented_command(struct command_ctx ctx, int argc, - const char *argv[]) { +int32_t unimplemented_command(struct command_ctx ctx, int argc, + const char *argv[]) { minibuffer_echo("TODO: %s is not implemented", (const char *)ctx.userdata); + return 0; } -void exit_editor(struct command_ctx ctx, int argc, const char *argv[]) { + +int32_t exit_editor(struct command_ctx ctx, int argc, const char *argv[]) { terminate(); + return 0; } static struct command GLOBAL_COMMANDS[] = { @@ -100,10 +104,10 @@ struct window { struct buffer *buffer; }; -struct buffer_update window_update_buffer(struct window *window, - uint64_t frame_time) { - return buffer_update(window->buffer, window->width, window->height, - frame_alloc, frame_time); +void window_update_buffer(struct window *window, struct command_list *commands, + uint64_t frame_time) { + buffer_update(window->buffer, window->width, window->height, commands, + frame_time); } void buffers_init(struct buffers *buffers) { buffers->nbuffers = 0; } @@ -131,7 +135,7 @@ int main(int argc, char *argv[]) { signal(SIGTERM, terminate); - frame_allocator = frame_allocator_create(1024 * 1024); + frame_allocator = frame_allocator_create(16 * 1024 * 1024); // create reactor struct reactor reactor = reactor_create(); @@ -144,7 +148,7 @@ int main(int argc, char *argv[]) { struct keyboard kbd = keyboard_create(&reactor); // commands - struct commands commands = command_list_create(32); + struct commands commands = command_registry_create(32); register_commands(&commands, GLOBAL_COMMANDS, sizeof(GLOBAL_COMMANDS) / sizeof(GLOBAL_COMMANDS[0])); register_commands(&commands, BUFFER_COMMANDS, @@ -171,19 +175,20 @@ int main(int argc, char *argv[]) { struct buffers buflist = {0}; buffers_init(&buflist); - struct buffer curbuf = buffer_create("welcome", true); + struct buffer initial_buffer = buffer_create("welcome", true); if (filename != NULL) { - curbuf = buffer_from_file(filename, &reactor); + initial_buffer = buffer_from_file(filename, &reactor); } else { const char *welcome_txt = "Welcome to the editor for datagubbar ๐ด\n"; - buffer_add_text(&curbuf, (uint8_t *)welcome_txt, strlen(welcome_txt)); + buffer_add_text(&initial_buffer, (uint8_t *)welcome_txt, + strlen(welcome_txt)); } - buffers_add(&buflist, curbuf); + buffers_add(&buflist, initial_buffer); // one main window struct window main_window = (struct window){ - .buffer = &curbuf, + .buffer = &initial_buffer, .height = display.height - 1, .width = display.width, .x = 0, @@ -208,13 +213,13 @@ int main(int argc, char *argv[]) { uint64_t frame_time = 0; - struct render_cmd_buf render_bufs[2] = {0}; - struct window *windows[2] = { &minibuffer_window, &main_window, }; + struct command_list *command_lists[2] = {0}; + // TODO: not always struct window *active_window = &main_window; @@ -224,13 +229,13 @@ int main(int argc, char *argv[]) { // update windows uint32_t dot_line = 0, dot_col = 0; - for (uint32_t windowi = 0; windowi < 2; ++windowi) { + for (uint32_t windowi = 0; windowi < sizeof(windows) / sizeof(windows[0]); + ++windowi) { struct window *win = windows[windowi]; - struct buffer_update buf_upd = window_update_buffer(win, frame_time); - render_bufs[windowi].cmds = buf_upd.cmds; - render_bufs[windowi].ncmds = buf_upd.ncmds; - render_bufs[windowi].xoffset = win->x; - render_bufs[windowi].yoffset = win->y; + // TODO: better capacity + command_lists[windowi] = + command_list_create(win->height * 2, frame_alloc, win->x, win->y); + window_update_buffer(win, command_lists[windowi], frame_time); } clock_gettime(CLOCK_MONOTONIC, &buffer_end); @@ -239,22 +244,36 @@ int main(int argc, char *argv[]) { clock_gettime(CLOCK_MONOTONIC, &display_begin); uint32_t relline, relcol; buffer_relative_dot_pos(active_window->buffer, &relline, &relcol); - if (render_bufs[0].ncmds > 0 || render_bufs[1].ncmds > 0) { - display_update(&display, render_bufs, 2, relline, relcol); - } + display_begin_render(&display); + for (uint32_t windowi = 0; windowi < sizeof(windows) / sizeof(windows[0]); + ++windowi) { + display_render(&display, command_lists[windowi]); + } + display_move_cursor(&display, relline + active_window->y, + relcol + active_window->x); + display_end_render(&display); clock_gettime(CLOCK_MONOTONIC, &display_end); + // this blocks for events, so if nothing has happened we block here. reactor_update(&reactor); clock_gettime(CLOCK_MONOTONIC, &keyboard_begin); struct keymap *local_keymaps = NULL; - uint32_t nbuffer_keymaps = buffer_keymaps(&curbuf, &local_keymaps); + uint32_t nbuffer_keymaps = buffer_keymaps(&initial_buffer, &local_keymaps); struct keyboard_update kbd_upd = keyboard_update(&kbd, &reactor); + uint32_t input_data_idx = 0; for (uint32_t ki = 0; ki < kbd_upd.nkeys; ++ki) { struct key *k = &kbd_upd.keys[ki]; + // insert any data from last key + if (k->start > input_data_idx) { + buffer_add_text(active_window->buffer, &kbd_upd.raw[input_data_idx], + k->start - input_data_idx); + } + input_data_idx = k->end; + struct lookup_result res = {.found = false}; if (current_keymap != NULL) { res = lookup_key(current_keymap, 1, k, &commands); @@ -269,7 +288,17 @@ int main(int argc, char *argv[]) { if (res.found) { switch (res.type) { case BindingType_Command: { - execute_command(res.command, active_window->buffer, 0, NULL); + if (res.command == NULL) { + minibuffer_echo_timeout( + 4, "binding found for key %s but not command", k); + } else { + int32_t ec = + execute_command(res.command, active_window->buffer, 0, NULL); + if (ec != 0) { + minibuffer_echo_timeout(4, "command %s failed with exit code %d", + res.command->name, ec); + } + } current_keymap = NULL; break; } @@ -284,10 +313,12 @@ int main(int argc, char *argv[]) { } else if (current_keymap != NULL) { minibuffer_echo_timeout(4, "key is not bound!"); current_keymap = NULL; - } else { - buffer_add_text(active_window->buffer, k->bytes, k->nbytes); } } + if (input_data_idx < kbd_upd.nbytes) { + buffer_add_text(active_window->buffer, &kbd_upd.raw[input_data_idx], + kbd_upd.nbytes - input_data_idx); + } clock_gettime(CLOCK_MONOTONIC, &keyboard_end); // calculate frame time @@ -302,7 +333,7 @@ int main(int argc, char *argv[]) { display_clear(&display); display_destroy(&display); keymap_destroy(&global_keymap); - command_list_destroy(&commands); + command_registry_destroy(&commands); return 0; } diff --git a/src/minibuffer.c b/src/minibuffer.c index 649413b..762bbe7 100644 --- a/src/minibuffer.c +++ b/src/minibuffer.c @@ -11,17 +11,22 @@ static struct minibuffer { struct timespec expires; } g_minibuffer = {0}; -void update(struct buffer *buffer) { +struct margin update(struct buffer *buffer, struct command_list *commands, + uint32_t width, uint32_t height, uint64_t frame_time, + void *userdata) { struct timespec current; + struct minibuffer *mb = (struct minibuffer *)userdata; clock_gettime(CLOCK_MONOTONIC, ¤t); - if (current.tv_sec >= g_minibuffer.expires.tv_sec) { + if (current.tv_sec >= mb->expires.tv_sec) { buffer_clear(buffer); } + + return (struct margin){0}; } void minibuffer_init(struct buffer *buffer) { g_minibuffer.buffer = buffer; - buffer_add_pre_update_hook(g_minibuffer.buffer, update); + buffer_add_update_hook(g_minibuffer.buffer, update, &g_minibuffer); } void echo(uint32_t timeout, const char *fmt, va_list args) { @@ -64,10 +64,13 @@ uint32_t charidx_to_byteidx(struct line *line, uint32_t char_idx) { if (char_idx > line->nchars) { return line->nbytes; } - return utf8_nbytes(line->data, char_idx); + return utf8_nbytes(line->data, line->nbytes, char_idx); +} + +uint32_t text_col_to_byteindex(struct text *text, uint32_t line, uint32_t col) { + return charidx_to_byteidx(&text->lines[line], col); } -// TODO: grapheme clusters // given `byte_idx` as a byte index, return the character index uint32_t byteidx_to_charidx(struct line *line, uint32_t byte_idx) { if (byte_idx > line->nbytes) { @@ -77,31 +80,6 @@ uint32_t byteidx_to_charidx(struct line *line, uint32_t byte_idx) { return utf8_nchars(line->data, byte_idx); } -uint32_t char_byte_size(struct line *line, uint32_t byte_idx) { - return utf8_nbytes(line->data + byte_idx, 1); -} - -void extend_line(struct line *line, uint32_t nbytes, uint32_t nchars) { - if (nbytes == 0) { - return; - } - - line->nbytes += nbytes; - line->nchars += nchars; - line->flags = LineChanged; - line->data = realloc(line->data, line->nbytes); -} - -void insert_at(struct line *line, uint32_t byte_index, uint8_t *text, - uint32_t len, uint32_t nchars) { - if (len == 0) { - return; - } - - extend_line(line, len, nchars); - memcpy(line->data + byte_index, text, len); -} - void insert_at_col(struct line *line, uint32_t col, uint8_t *text, uint32_t len, uint32_t nchars) { @@ -109,14 +87,17 @@ void insert_at_col(struct line *line, uint32_t col, uint8_t *text, uint32_t len, return; } - extend_line(line, len, nchars); + line->nbytes += len; + line->nchars += nchars; + line->flags = LineChanged; + line->data = realloc(line->data, line->nbytes); uint32_t bytei = charidx_to_byteidx(line, col); - // move chars out of the way - if (col + nchars < line->nchars) { - uint32_t nextcbytei = charidx_to_byteidx(line, col + nchars); - memmove(line->data + nextcbytei, line->data + bytei, - line->nbytes - nextcbytei); + + // move following bytes out of the way + if (bytei + len < line->nbytes) { + uint32_t start = bytei + len; + memmove(line->data + start, line->data + bytei, line->nbytes - start); } // insert new chars @@ -228,91 +209,61 @@ void delete_line(struct text *text, uint32_t line) { void text_append(struct text *text, uint8_t *bytes, uint32_t nbytes, uint32_t *lines_added, uint32_t *cols_added) { - - uint32_t linelen = 0, nchars_counted = 0, nlines_added = 0, ncols_added = 0; uint32_t line = text->nlines - 1; + uint32_t col = text_line_length(text, line); - for (uint32_t bytei = 0; bytei < nbytes; ++bytei) { - uint8_t byte = bytes[bytei]; - if (byte == '\n') { - insert_at(&text->lines[line], text->lines[line].nbytes, - bytes + (bytei - linelen), linelen, nchars_counted); - - new_line_at(text, line, text->lines[line].nchars); - - ++line; - ++nlines_added; - - linelen = 0; - nchars_counted = 0; - } else { - if (utf8_byte_is_ascii(byte) || utf8_byte_is_unicode_start(byte)) { - ++nchars_counted; - } - ++linelen; - } - } - - // handle remaining - if (linelen > 0) { - insert_at(&text->lines[line], text->lines[line].nbytes, - bytes + (nbytes - linelen), linelen, nchars_counted); - ncols_added = nchars_counted; - } - - *lines_added = nlines_added; - *cols_added = ncols_added; + text_insert_at(text, line, col, bytes, nbytes, lines_added, cols_added); } -void text_append_at(struct text *text, uint32_t line, uint32_t col, +void text_insert_at(struct text *text, uint32_t line, uint32_t col, uint8_t *bytes, uint32_t nbytes, uint32_t *lines_added, uint32_t *cols_added) { - uint32_t linelen = 0; - uint32_t nchars_counted = 0; - uint32_t nlines_added = 0; - uint32_t ncols_added = 0; + uint32_t linelen = 0, start_line = line; + *cols_added = 0; + for (uint32_t bytei = 0; bytei < nbytes; ++bytei) { uint8_t byte = bytes[bytei]; if (byte == '\n') { - insert_at_col(&text->lines[line], col, bytes + (bytei - linelen), linelen, - nchars_counted); + uint8_t *line_data = bytes + (bytei - linelen); + uint32_t nchars = utf8_nchars(line_data, linelen); + insert_at_col(&text->lines[line], col, line_data, linelen, nchars); - col += nchars_counted; + col += nchars; new_line_at(text, line, col); ++line; - ++nlines_added; col = text_line_length(text, line); linelen = 0; - nchars_counted = 0; } else { - if (utf8_byte_is_ascii(byte) || utf8_byte_is_unicode_start(byte)) { - ++nchars_counted; - } ++linelen; } } // handle remaining if (linelen > 0) { - insert_at_col(&text->lines[line], col, bytes + (nbytes - linelen), linelen, - nchars_counted); - ncols_added = nchars_counted; + uint8_t *line_data = bytes + (nbytes - linelen); + uint32_t nchars = utf8_nchars(line_data, linelen); + insert_at_col(&text->lines[line], col, line_data, linelen, nchars); + *cols_added = nchars; } - *lines_added = nlines_added; - *cols_added = ncols_added; + *lines_added = line - start_line; } void text_delete(struct text *text, uint32_t line, uint32_t col, uint32_t nchars) { - // delete chars from current line struct line *lp = &text->lines[line]; + if (col > lp->nchars) { + return; + } + + // delete chars from current line uint32_t chars_initial_line = col + nchars > lp->nchars ? (lp->nchars - col) : nchars; uint32_t bytei = charidx_to_byteidx(lp, col); - uint32_t nbytes = utf8_nbytes(lp->data + bytei, chars_initial_line); + uint32_t nbytes = + utf8_nbytes(lp->data + bytei, lp->nbytes - bytei, chars_initial_line); memcpy(lp->data + bytei, lp->data + bytei + nbytes, lp->nbytes - (bytei + nbytes)); @@ -347,7 +298,7 @@ void text_delete(struct text *text, uint32_t line, uint32_t col, } // delete all lines from current line + 1 to (and including) last line - for (uint32_t li = initial_line + 1; li <= line; ++li) { + for (uint32_t li = initial_line + 1; li <= line && li < text->nlines; ++li) { delete_line(text, li); } } @@ -5,7 +5,7 @@ // opaque so it is easier to change representation to gap, rope etc. struct text; -struct render_cmd; +struct render_command; struct text *text_create(uint32_t initial_capacity); void text_destroy(struct text *text); @@ -15,7 +15,7 @@ void text_destroy(struct text *text); */ void text_clear(struct text *text); -void text_append_at(struct text *text, uint32_t line, uint32_t col, +void text_insert_at(struct text *text, uint32_t line, uint32_t col, uint8_t *bytes, uint32_t nbytes, uint32_t *lines_added, uint32_t *cols_added); @@ -28,6 +28,7 @@ void text_delete(struct text *text, uint32_t line, uint32_t col, 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); struct text_chunk { uint8_t *text; @@ -9,33 +9,58 @@ bool utf8_byte_is_unicode_continuation(uint8_t byte) { bool utf8_byte_is_unicode(uint8_t byte) { return (byte & 0x80) != 0x0; } bool utf8_byte_is_ascii(uint8_t byte) { return !utf8_byte_is_unicode(byte); } +uint32_t utf8_nbytes_in_char(uint8_t byte) { + // length of char is the number of leading ones + // flip it and count number of leading zeros + uint8_t invb = ~byte; + return __builtin_clz((uint32_t)invb) - 24; +} + // TODO: grapheme clusters, this returns the number of unicode code points uint32_t utf8_nchars(uint8_t *bytes, uint32_t nbytes) { uint32_t nchars = 0; + uint32_t expected = 0; for (uint32_t bi = 0; bi < nbytes; ++bi) { - if (utf8_byte_is_ascii(bytes[bi]) || utf8_byte_is_unicode_start(bytes[bi])) + uint8_t byte = bytes[bi]; + if (utf8_byte_is_unicode(byte)) { + if (utf8_byte_is_unicode_start(byte)) { + expected = utf8_nbytes_in_char(byte) - 1; + } else { // continuation byte + --expected; + if (expected == 0) { + ++nchars; + } + } + } else { // ascii ++nchars; + } } return nchars; } // TODO: grapheme clusters, this uses the number of unicode code points -uint32_t utf8_nbytes(uint8_t *bytes, uint32_t nchars) { +uint32_t utf8_nbytes(uint8_t *bytes, uint32_t nbytes, uint32_t nchars) { + uint32_t bi = 0; uint32_t chars = 0; - while (chars < nchars) { - uint8_t byte = bytes[bi]; - if (utf8_byte_is_unicode_start(byte)) { - ++chars; + uint32_t expected = 0; - // length of char is the number of leading ones - // flip it and count number of leading zeros - uint8_t invb = ~byte; - bi += __builtin_clz((uint32_t)invb) - 24; - } else { + while (chars < nchars && bi < nbytes) { + uint8_t byte = bytes[bi]; + if (utf8_byte_is_unicode(byte)) { + if (utf8_byte_is_unicode_start(byte)) { + expected = utf8_nbytes_in_char(byte) - 1; + } else { // continuation char + --expected; + if (expected == 0) { + ++chars; + } + } + } else { // ascii ++chars; - ++bi; } + + ++bi; } return bi; @@ -8,7 +8,7 @@ uint32_t utf8_nchars(uint8_t *bytes, uint32_t nbytes); /* Return the number of bytes used to make up the next `nchars` characters */ -uint32_t utf8_nbytes(uint8_t *bytes, uint32_t nchars); +uint32_t utf8_nbytes(uint8_t *bytes, uint32_t nbytes, uint32_t nchars); /* true if `byte` is a unicode byte sequence start byte */ bool utf8_byte_is_unicode_start(uint8_t byte); diff --git a/test/text.c b/test/text.c index 43decda..459bd13 100644 --- a/test/text.c +++ b/test/text.c @@ -14,7 +14,7 @@ void test_add_text() { uint32_t lines_added, cols_added; struct text *t = text_create(10); const char *txt = "This is line 1\n"; - text_append_at(t, 0, 0, (uint8_t *)txt, strlen(txt), &lines_added, + text_insert_at(t, 0, 0, (uint8_t *)txt, strlen(txt), &lines_added, &cols_added); ASSERT(text_num_lines(t) == 2, "Expected text to have two lines after insertion"); @@ -25,7 +25,7 @@ void test_add_text() { "Expected line 1 to be line 1"); const char *txt2 = "This is line 2\n"; - text_append_at(t, 1, 0, (uint8_t *)txt2, strlen(txt2), &lines_added, + text_insert_at(t, 1, 0, (uint8_t *)txt2, strlen(txt2), &lines_added, &cols_added); ASSERT(text_num_lines(t) == 3, "Expected text to have three lines after second insertion"); @@ -34,7 +34,7 @@ void test_add_text() { // simulate indentation const char *txt3 = " "; - text_append_at(t, 0, 0, (uint8_t *)txt3, strlen(txt3), &lines_added, + text_insert_at(t, 0, 0, (uint8_t *)txt3, strlen(txt3), &lines_added, &cols_added); ASSERT(text_num_lines(t) == 3, "Expected text to have three lines after second insertion"); @@ -50,7 +50,7 @@ void test_delete_text() { uint32_t lines_added, cols_added; struct text *t = text_create(10); const char *txt = "This is line 1"; - text_append_at(t, 0, 0, (uint8_t *)txt, strlen(txt), &lines_added, + text_insert_at(t, 0, 0, (uint8_t *)txt, strlen(txt), &lines_added, &cols_added); text_delete(t, 0, 12, 2); @@ -65,7 +65,7 @@ void test_delete_text() { "Expected line to be empty after many chars removed"); const char *txt2 = "This is line 1\nThis is line 2\nThis is line 3"; - text_append_at(t, 0, 0, (uint8_t *)txt2, strlen(txt2), &lines_added, + text_insert_at(t, 0, 0, (uint8_t *)txt2, strlen(txt2), &lines_added, &cols_added); ASSERT(text_num_lines(t) == 3, "Expected to have three lines after inserting as many"); @@ -86,7 +86,7 @@ void test_delete_text() { struct text *t3 = text_create(10); const char *delete_me = "This is line๐\nQ"; - text_append_at(t3, 0, 0, (uint8_t *)delete_me, strlen(delete_me), + text_insert_at(t3, 0, 0, (uint8_t *)delete_me, strlen(delete_me), &lines_added, &cols_added); text_delete(t3, 0, 13, 1); struct text_chunk top_line = text_get_line(t3, 0); @@ -97,10 +97,19 @@ void test_delete_text() { ASSERT(text_num_lines(t3) == 1, "Expected text to have one line after deleting newline"); + struct text *t4 = text_create(10); + const char *deletable_text = "Only one line kinda"; + text_append(t4, (uint8_t *)deletable_text, strlen(deletable_text), + &lines_added, &cols_added); + text_delete(t4, 0, 19, 1); + ASSERT(text_num_lines(t4) == 1, "Expected the line to still be there"); + ASSERT(text_line_length(t4, 0) == 19, + "Expected nothing to have happened to the line"); + // test utf-8 struct text *t2 = text_create(10); const char *txt3 = "Emojis: ๐ซ๐ฎ ๐ฎ\n"; - text_append_at(t2, 0, 0, (uint8_t *)txt3, strlen(txt3), &lines_added, + text_insert_at(t2, 0, 0, (uint8_t *)txt3, strlen(txt3), &lines_added, &cols_added); // TODO: Fix when graphemes are implemented, should be 11, right now it counts diff --git a/test/utf8.c b/test/utf8.c index 5b020c3..88e6a8c 100644 --- a/test/utf8.c +++ b/test/utf8.c @@ -3,10 +3,12 @@ #include "test.h" #include "wchar.h" +#include <string.h> + void test_nchars_nbytes() { - ASSERT(utf8_nchars((uint8_t *)"๐ด", 2) == 1, + ASSERT(utf8_nchars((uint8_t *)"๐ด", strlen("๐ด")) == 1, "Expected old man emoji to be 1 char"); - ASSERT(utf8_nbytes((uint8_t *)"๐ด", 1) == 4, + ASSERT(utf8_nbytes((uint8_t *)"๐ด", strlen("๐ด"), 1) == 4, "Expected old man emoji to be 4 bytes"); } |
