diff options
| author | Albert Cervin <albert@acervin.com> | 2023-04-06 23:23:46 +0200 |
|---|---|---|
| committer | Albert Cervin <albert@acervin.com> | 2023-05-01 22:19:14 +0200 |
| commit | a123725a12e948d78badb2cb686d38548f1c633b (patch) | |
| tree | c92c46134ef5536fbbf3bf08983c4f0dea1aaf58 /src/buffer.c | |
| parent | b5ed4cf757afc50afb6ac499eee7b87a2648fa4c (diff) | |
| download | dged-a123725a12e948d78badb2cb686d38548f1c633b.tar.gz dged-a123725a12e948d78badb2cb686d38548f1c633b.tar.xz dged-a123725a12e948d78badb2cb686d38548f1c633b.zip | |
Implement window handling
Also implement searching.
fix undo boundaries
when it checked for other save point, it used && instead of == which
caused it to overwrite other types.
Fix bytes vs chars bug in text_get_region
Diffstat (limited to 'src/buffer.c')
| -rw-r--r-- | src/buffer.c | 1125 |
1 files changed, 0 insertions, 1125 deletions
diff --git a/src/buffer.c b/src/buffer.c deleted file mode 100644 index 23a8ab1..0000000 --- a/src/buffer.c +++ /dev/null @@ -1,1125 +0,0 @@ -#include "buffer.h" -#include "binding.h" -#include "display.h" -#include "errno.h" -#include "lang.h" -#include "minibuffer.h" -#include "reactor.h" -#include "settings.h" -#include "utf8.h" - -#include <fcntl.h> -#include <libgen.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <unistd.h> -#include <wchar.h> - -struct modeline { - uint8_t *buffer; -}; - -#define KILL_RING_SZ 64 -static struct kill_ring { - struct text_chunk buffer[KILL_RING_SZ]; - struct buffer_location last_paste; - bool paste_up_to_date; - uint32_t curr_idx; - uint32_t paste_idx; -} g_kill_ring = {.curr_idx = 0, - .buffer = {0}, - .last_paste = {0}, - .paste_idx = 0, - .paste_up_to_date = false}; - -struct update_hook_result buffer_linenum_hook(struct buffer *buffer, - struct command_list *commands, - uint32_t width, uint32_t height, - uint64_t frame_time, - void *userdata); - -struct update_hook_result buffer_modeline_hook(struct buffer *buffer, - struct command_list *commands, - uint32_t width, uint32_t height, - uint64_t frame_time, - void *userdata); - -struct buffer buffer_create(char *name, bool modeline) { - struct buffer b = (struct buffer){ - .filename = NULL, - .name = strdup(name), - .text = text_create(10), - .dot = {0}, - .mark = {0}, - .mark_set = false, - .modified = false, - .readonly = false, - .modeline = NULL, - .keymaps = calloc(10, sizeof(struct keymap)), - .nkeymaps = 1, - .scroll = {0}, - .update_hooks = {0}, - .nkeymaps_max = 10, - .lang = lang_from_id("fnd"), - }; - - undo_init(&b.undo, 100); - - b.keymaps[0] = keymap_create("buffer-default", 128); - struct binding bindings[] = { - BINDING(Ctrl, 'B', "backward-char"), - BINDING(LEFT, "backward-char"), - BINDING(Ctrl, 'F', "forward-char"), - BINDING(RIGHT, "forward-char"), - - BINDING(Ctrl, 'P', "backward-line"), - BINDING(UP, "backward-line"), - 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"), - - BINDING(Meta, '<', "goto-beginning"), - BINDING(Meta, '>', "goto-end"), - - BINDING(ENTER, "newline"), - BINDING(TAB, "indent"), - - BINDING(Ctrl, 'K', "kill-line"), - BINDING(DELETE, "delete-char"), - BINDING(Ctrl, 'D', "delete-char"), - BINDING(BACKSPACE, "backward-delete-char"), - - BINDING(Ctrl, '@', "set-mark"), - - BINDING(Ctrl, 'W', "cut"), - BINDING(Ctrl, 'Y', "paste"), - BINDING(Meta, 'y', "paste-older"), - BINDING(Meta, 'w', "copy"), - - BINDING(Ctrl, '_', "undo"), - }; - keymap_bind_keys(&b.keymaps[0], bindings, - sizeof(bindings) / sizeof(bindings[0])); - - if (modeline) { - b.modeline = calloc(1, sizeof(struct modeline)); - b.modeline->buffer = malloc(1024); - b.modeline->buffer[0] = '\0'; - buffer_add_update_hook(&b, buffer_modeline_hook, b.modeline); - } - - if (modeline) { - buffer_add_update_hook(&b, buffer_linenum_hook, NULL); - } - - return b; -} - -void buffer_destroy(struct buffer *buffer) { - text_destroy(buffer->text); - buffer->text = NULL; - - free(buffer->name); - buffer->name = NULL; - - free(buffer->filename); - buffer->filename = NULL; - - for (uint32_t keymapi = 0; keymapi < buffer->nkeymaps; ++keymapi) { - keymap_destroy(&buffer->keymaps[keymapi]); - } - free(buffer->keymaps); - buffer->keymaps = NULL; - - if (buffer->modeline != NULL) { - free(buffer->modeline->buffer); - free(buffer->modeline); - } - - undo_destroy(&buffer->undo); -} - -void buffer_clear(struct buffer *buffer) { - text_clear(buffer->text); - buffer->dot.col = buffer->dot.line = 0; -} - -#define BUFFER_WRAPCMD(fn) \ - static int32_t fn##_cmd(struct command_ctx ctx, int argc, \ - const char *argv[]) { \ - fn(ctx.active_window->buffer); \ - return 0; \ - } - -// commands -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); -BUFFER_WRAPCMD(buffer_beginning_of_line); -BUFFER_WRAPCMD(buffer_newline); -BUFFER_WRAPCMD(buffer_indent); -BUFFER_WRAPCMD(buffer_to_file); -BUFFER_WRAPCMD(buffer_set_mark); -BUFFER_WRAPCMD(buffer_clear_mark); -BUFFER_WRAPCMD(buffer_copy); -BUFFER_WRAPCMD(buffer_cut); -BUFFER_WRAPCMD(buffer_paste); -BUFFER_WRAPCMD(buffer_paste_older); -BUFFER_WRAPCMD(buffer_goto_beginning); -BUFFER_WRAPCMD(buffer_goto_end); -BUFFER_WRAPCMD(buffer_undo); - -void buffer_static_init(struct commands *commands) { - settings_register_setting( - "editor.tab-width", - (struct setting_value){.type = Setting_Number, .number_value = 4}); - - settings_register_setting( - "editor.show-whitespace", - (struct setting_value){.type = Setting_Bool, .bool_value = true}); - - 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 = "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}, - {.name = "beginning-of-line", .fn = buffer_beginning_of_line_cmd}, - {.name = "newline", .fn = buffer_newline_cmd}, - {.name = "indent", .fn = buffer_indent_cmd}, - {.name = "buffer-write-to-file", .fn = buffer_to_file_cmd}, - {.name = "set-mark", .fn = buffer_set_mark_cmd}, - {.name = "clear-mark", .fn = buffer_clear_mark_cmd}, - {.name = "copy", .fn = buffer_copy_cmd}, - {.name = "cut", .fn = buffer_cut_cmd}, - {.name = "paste", .fn = buffer_paste_cmd}, - {.name = "paste-older", .fn = buffer_paste_older_cmd}, - {.name = "goto-beginning", .fn = buffer_goto_beginning_cmd}, - {.name = "goto-end", .fn = buffer_goto_end_cmd}, - {.name = "undo", .fn = buffer_undo_cmd}, - }; - - register_commands(commands, buffer_commands, - sizeof(buffer_commands) / sizeof(buffer_commands[0])); -} - -void buffer_static_teardown() { - for (uint32_t i = 0; i < KILL_RING_SZ; ++i) { - if (g_kill_ring.buffer[i].allocated) { - free(g_kill_ring.buffer[i].text); - } - } -} - -bool buffer_is_empty(struct buffer *buffer) { - return text_num_lines(buffer->text) == 0; -} - -bool buffer_is_modified(struct buffer *buffer) { return buffer->modified; } - -bool buffer_is_readonly(struct buffer *buffer) { return buffer->readonly; } -void buffer_set_readonly(struct buffer *buffer, bool readonly) { - buffer->readonly = readonly; -} - -void delete_with_undo(struct buffer *buffer, struct buffer_location start, - struct buffer_location end) { - if (buffer->readonly) { - minibuffer_echo_timeout(4, "buffer is read-only"); - return; - } - - struct text_chunk txt = - text_get_region(buffer->text, start.line, start.col, end.line, end.col); - - undo_push_delete( - &buffer->undo, - (struct undo_delete){.data = txt.text, - .nbytes = txt.nbytes, - .pos = {.row = start.line, .col = start.col}}); - undo_push_boundary(&buffer->undo, - (struct undo_boundary){.save_point = false}); - - text_delete(buffer->text, start.line, start.col, end.line, end.col); - buffer->modified = true; -} - -uint32_t buffer_keymaps(struct buffer *buffer, struct keymap **keymaps_out) { - *keymaps_out = buffer->keymaps; - return buffer->nkeymaps; -} - -void buffer_add_keymap(struct buffer *buffer, struct keymap *keymap) { - if (buffer->nkeymaps == buffer->nkeymaps_max) { - buffer->nkeymaps_max *= 2; - buffer->keymaps = - realloc(buffer->keymaps, sizeof(struct keymap) * buffer->nkeymaps_max); - } - buffer->keymaps[buffer->nkeymaps] = *keymap; - ++buffer->nkeymaps; -} - -void buffer_goto_beginning(struct buffer *buffer) { - buffer->dot.col = 0; - buffer->dot.line = 0; -} - -void buffer_goto_end(struct buffer *buffer) { - buffer->dot.line = text_num_lines(buffer->text); - buffer->dot.col = 0; -} - -bool movev(struct buffer *buffer, int rowdelta) { - int64_t new_line = (int64_t)buffer->dot.line + rowdelta; - - if (new_line < 0) { - buffer->dot.line = 0; - return false; - } else if (new_line > text_num_lines(buffer->text)) { - buffer->dot.line = text_num_lines(buffer->text); - return false; - } else { - buffer->dot.line = (uint32_t)new_line; - - // make sure column stays on the line - uint32_t linelen = text_line_length(buffer->text, buffer->dot.line); - buffer->dot.col = buffer->dot.col > linelen ? linelen : buffer->dot.col; - return true; - } -} - -// move dot `coldelta` chars -bool moveh(struct buffer *buffer, int coldelta) { - int64_t new_col = (int64_t)buffer->dot.col + coldelta; - - if (new_col > (int64_t)text_line_length(buffer->text, buffer->dot.line)) { - if (movev(buffer, 1)) { - buffer->dot.col = 0; - } - } else if (new_col < 0) { - if (movev(buffer, -1)) { - buffer->dot.col = text_line_length(buffer->text, buffer->dot.line); - } else { - return false; - } - } else { - buffer->dot.col = new_col; - } - - return true; -} - -void buffer_goto(struct buffer *buffer, uint32_t line, uint32_t col) { - int64_t linedelta = (int64_t)line - (int64_t)buffer->dot.line; - movev(buffer, linedelta); - - int64_t coldelta = (int64_t)col - (int64_t)buffer->dot.col; - moveh(buffer, coldelta); -} - -struct region { - struct buffer_location begin; - struct buffer_location end; -}; - -struct region to_region(struct buffer_location dot, - struct buffer_location mark) { - struct region reg = {.begin = mark, .end = dot}; - - if (dot.line < mark.line || (dot.line == mark.line && dot.col < mark.col)) { - reg.begin = dot; - reg.end = mark; - } - - return reg; -} - -struct region buffer_get_region(struct buffer *buffer) { - return to_region(buffer->dot, buffer->mark); -} - -bool buffer_region_has_size(struct buffer *buffer) { - return buffer->mark_set && - (labs((int64_t)buffer->mark.line - (int64_t)buffer->dot.line) + - labs((int64_t)buffer->mark.col - (int64_t)buffer->dot.col)) > 0; -} - -struct text_chunk *copy_region(struct buffer *buffer, struct region region) { - struct text_chunk *curr = &g_kill_ring.buffer[g_kill_ring.curr_idx]; - g_kill_ring.curr_idx = g_kill_ring.curr_idx + 1 % KILL_RING_SZ; - - if (curr->allocated) { - free(curr->text); - } - - struct text_chunk txt = - text_get_region(buffer->text, region.begin.line, region.begin.col, - region.end.line, region.end.col); - *curr = txt; - return curr; -} - -void buffer_copy(struct buffer *buffer) { - if (buffer_region_has_size(buffer)) { - struct region reg = buffer_get_region(buffer); - struct text_chunk *curr = copy_region(buffer, reg); - buffer_clear_mark(buffer); - } -} - -void paste(struct buffer *buffer, uint32_t ring_idx) { - if (ring_idx > 0) { - struct text_chunk *curr = &g_kill_ring.buffer[ring_idx - 1]; - if (curr->text != NULL) { - g_kill_ring.last_paste = buffer->mark_set ? buffer->mark : buffer->dot; - buffer_add_text(buffer, curr->text, curr->nbytes); - g_kill_ring.paste_up_to_date = true; - } - } -} - -void buffer_paste(struct buffer *buffer) { - g_kill_ring.paste_idx = g_kill_ring.curr_idx; - paste(buffer, g_kill_ring.curr_idx); -} - -void buffer_paste_older(struct buffer *buffer) { - if (g_kill_ring.paste_up_to_date) { - - // remove previous paste - struct text_chunk *curr = &g_kill_ring.buffer[g_kill_ring.curr_idx]; - delete_with_undo(buffer, g_kill_ring.last_paste, buffer->dot); - - // place ourselves right - buffer->dot = g_kill_ring.last_paste; - - // paste older - if (g_kill_ring.paste_idx - 1 > 0) { - --g_kill_ring.paste_idx; - } else { - g_kill_ring.paste_idx = g_kill_ring.curr_idx; - } - - paste(buffer, g_kill_ring.paste_idx); - - } else { - buffer_paste(buffer); - } -} - -void buffer_cut(struct buffer *buffer) { - if (buffer_region_has_size(buffer)) { - struct region reg = buffer_get_region(buffer); - copy_region(buffer, reg); - delete_with_undo(buffer, reg.begin, reg.end); - buffer_clear_mark(buffer); - buffer->dot = reg.begin; - } -} - -bool maybe_delete_region(struct buffer *buffer) { - if (buffer_region_has_size(buffer)) { - struct region reg = buffer_get_region(buffer); - delete_with_undo(buffer, reg.begin, reg.end); - buffer_clear_mark(buffer); - buffer->dot = reg.begin; - return true; - } - - return false; -} - -void buffer_kill_line(struct buffer *buffer) { - if (text_num_lines(buffer->text) == 0) { - return; - } - - uint32_t nchars = - text_line_length(buffer->text, buffer->dot.line) - buffer->dot.col; - if (nchars == 0) { - nchars = 1; - } - - struct region reg = { - .begin = buffer->dot, - .end = - { - .line = buffer->dot.line, - .col = buffer->dot.col + nchars, - }, - }; - copy_region(buffer, reg); - delete_with_undo(buffer, buffer->dot, - (struct buffer_location){ - .line = buffer->dot.line, - .col = buffer->dot.col + nchars, - }); -} - -void buffer_forward_delete_char(struct buffer *buffer) { - if (maybe_delete_region(buffer)) { - return; - } - - delete_with_undo(buffer, buffer->dot, - (struct buffer_location){ - .line = buffer->dot.line, - .col = buffer->dot.col + 1, - }); -} - -void buffer_backward_delete_char(struct buffer *buffer) { - if (maybe_delete_region(buffer)) { - return; - } - - if (moveh(buffer, -1)) { - buffer_forward_delete_char(buffer); - } -} - -void buffer_backward_char(struct buffer *buffer) { moveh(buffer, -1); } -void buffer_forward_char(struct buffer *buffer) { moveh(buffer, 1); } - -struct buffer_location find_next(struct buffer *buffer, uint8_t chars[], - uint32_t nchars, int direction) { - struct text_chunk line = text_get_line(buffer->text, buffer->dot.line); - int64_t bytei = - text_col_to_byteindex(buffer->text, buffer->dot.line, buffer->dot.col); - while (bytei < line.nbytes && bytei > 0 && - (line.text[bytei] == ' ' || line.text[bytei] == '.')) { - bytei += direction; - } - - for (; bytei < line.nbytes && bytei > 0; bytei += direction) { - uint8_t b = line.text[bytei]; - if (b == ' ' || b == '.') { - break; - } - } - - uint32_t target_col = - text_byteindex_to_col(buffer->text, buffer->dot.line, bytei); - return (struct buffer_location){.line = buffer->dot.line, .col = target_col}; -} - -void buffer_forward_word(struct buffer *buffer) { - moveh(buffer, 1); - uint8_t chars[] = {' ', '.'}; - buffer->dot = find_next(buffer, chars, 2, 1); -} - -void buffer_backward_word(struct buffer *buffer) { - moveh(buffer, -1); - uint8_t chars[] = {' ', '.'}; - buffer->dot = find_next(buffer, chars, 2, -1); -} - -void buffer_backward_line(struct buffer *buffer) { movev(buffer, -1); } -void buffer_forward_line(struct buffer *buffer) { movev(buffer, 1); } - -void buffer_end_of_line(struct buffer *buffer) { - buffer->dot.col = text_line_length(buffer->text, buffer->dot.line); -} - -void buffer_beginning_of_line(struct buffer *buffer) { buffer->dot.col = 0; } - -struct buffer buffer_from_file(char *filename) { - struct buffer b = buffer_create(basename((char *)filename), true); - b.filename = strdup(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); - if (bytes > 0) { - uint32_t ignore; - text_append(b.text, buff, bytes, &ignore, &ignore); - } else if (bytes == 0) { - break; // EOF - } else { - minibuffer_echo("error reading from %s: %s", filename, strerror(errno)); - fclose(file); - return b; - } - } - - fclose(file); - } - - const char *ext = strrchr(b.filename, '.'); - if (ext != NULL) { - b.lang = lang_from_extension(ext + 1); - } - undo_push_boundary(&b.undo, (struct undo_boundary){.save_point = true}); - return b; -} - -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("buffer \"%s\" is not associated with a file", - buffer->name); - return; - } - - if (!buffer->modified) { - minibuffer_echo_timeout(4, "buffer already saved"); - return; - } - - 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; - - text_for_each_line(buffer->text, 0, nlines_to_write, write_line, file); - minibuffer_echo_timeout(4, "wrote %d lines to %s", nlines_to_write, - buffer->filename); - fclose(file); - - buffer->modified = false; - undo_push_boundary(&buffer->undo, (struct undo_boundary){.save_point = true}); -} - -void buffer_write_to(struct buffer *buffer, const char *filename) { - buffer->filename = strdup(filename); - buffer_to_file(buffer); -} - -int buffer_add_text(struct buffer *buffer, uint8_t *text, uint32_t nbytes) { - if (buffer->readonly) { - minibuffer_echo_timeout(4, "buffer is read-only"); - return 0; - } - - // invalidate last paste - g_kill_ring.paste_up_to_date = false; - - /* If we currently have a selection active, - * replace it with the text to insert. */ - maybe_delete_region(buffer); - - struct buffer_location initial = buffer->dot; - - uint32_t lines_added, cols_added; - text_insert_at(buffer->text, initial.line, initial.col, text, nbytes, - &lines_added, &cols_added); - - // move to after inserted text - movev(buffer, lines_added); - if (lines_added > 0) { - // does not make sense to use position from another line - buffer->dot.col = 0; - } - moveh(buffer, cols_added); - - struct buffer_location final = buffer->dot; - undo_push_add( - &buffer->undo, - (struct undo_add){.begin = {.row = initial.line, .col = initial.col}, - .end = {.row = final.line, .col = final.col}}); - - if (lines_added > 0) { - undo_push_boundary(&buffer->undo, - (struct undo_boundary){.save_point = false}); - } - - buffer->modified = true; - return lines_added; -} - -void buffer_newline(struct buffer *buffer) { - buffer_add_text(buffer, (uint8_t *)"\n", 1); -} - -void buffer_indent(struct buffer *buffer) { - uint32_t tab_width = buffer->lang.tab_width; - buffer_add_text(buffer, (uint8_t *)" ", - tab_width > 16 ? 16 : tab_width); -} - -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; - - ++buffer->update_hooks.nhooks; - - // TODO: cant really have this if we actually want to remove a hook - return buffer->update_hooks.nhooks - 1; -} - -void buffer_set_mark(struct buffer *buffer) { - buffer->mark_set - ? buffer_clear_mark(buffer) - : buffer_set_mark_at(buffer, buffer->dot.line, buffer->dot.col); -} - -void buffer_clear_mark(struct buffer *buffer) { - buffer->mark_set = false; - minibuffer_echo_timeout(2, "mark cleared"); -} - -void buffer_set_mark_at(struct buffer *buffer, uint32_t line, uint32_t col) { - buffer->mark_set = true; - buffer->mark.line = line; - buffer->mark.col = col; - minibuffer_echo_timeout(2, "mark set"); -} - -void buffer_undo(struct buffer *buffer) { - struct undo_stack *undo = &buffer->undo; - undo_begin(undo); - - // fetch and handle records - struct undo_record *records = NULL; - uint32_t nrecords = 0; - - if (undo_current_position(undo) == INVALID_TOP) { - minibuffer_echo_timeout(4, - "no more undo information, starting from top..."); - } - - undo_next(undo, &records, &nrecords); - - undo_push_boundary(undo, (struct undo_boundary){.save_point = false}); - for (uint32_t reci = 0; reci < nrecords; ++reci) { - struct undo_record *rec = &records[reci]; - switch (rec->type) { - case Undo_Boundary: { - struct undo_boundary *b = &rec->boundary; - if (b->save_point) { - buffer->modified = false; - } - break; - } - case Undo_Add: { - struct undo_add *add = &rec->add; - - delete_with_undo(buffer, - (struct buffer_location){ - .line = add->begin.row, - .col = add->begin.col, - }, - (struct buffer_location){ - .line = add->end.row, - .col = add->end.col, - }); - - buffer_goto(buffer, add->begin.row, add->begin.col); - break; - } - case Undo_Delete: { - struct undo_delete *del = &rec->delete; - buffer_goto(buffer, del->pos.row, del->pos.col); - buffer_add_text(buffer, del->data, del->nbytes); - break; - } - } - } - undo_push_boundary(undo, (struct undo_boundary){.save_point = false}); - - free(records); - undo_end(undo); -} - -struct cmdbuf { - struct command_list *cmds; - struct buffer_location scroll; - uint32_t line_offset; - uint32_t left_margin; - uint32_t width; - - struct region region; - bool mark_set; - - bool show_ws; - - struct line_render_hook *line_render_hooks; - uint32_t nlinerender_hooks; -}; - -void render_line(struct text_chunk *line, void *userdata) { - struct cmdbuf *cmdbuf = (struct cmdbuf *)userdata; - uint32_t visual_line = line->line - cmdbuf->scroll.line + cmdbuf->line_offset; - - for (uint32_t hooki = 0; hooki < cmdbuf->nlinerender_hooks; ++hooki) { - struct line_render_hook *hook = &cmdbuf->line_render_hooks[hooki]; - hook->callback(line, visual_line, cmdbuf->cmds, hook->userdata); - } - - uint32_t scroll_bytes = - utf8_nbytes(line->text, line->nbytes, cmdbuf->scroll.col); - uint8_t *text = line->text + scroll_bytes; - uint32_t text_nbytes = - scroll_bytes > line->nbytes ? 0 : line->nbytes - scroll_bytes; - uint32_t text_nchars = - scroll_bytes > line->nchars ? 0 : line->nchars - cmdbuf->scroll.col; - - command_list_set_show_whitespace(cmdbuf->cmds, cmdbuf->show_ws); - struct buffer_location *begin = &cmdbuf->region.begin, - *end = &cmdbuf->region.end; - - // should we draw region - if (cmdbuf->mark_set && line->line >= begin->line && - line->line <= end->line) { - uint32_t byte_offset = 0; - uint32_t col_offset = 0; - - // draw any text on the line that should not be part of region - if (begin->line == line->line) { - if (begin->col > cmdbuf->scroll.col) { - uint32_t nbytes = - utf8_nbytes(text, text_nbytes, begin->col - cmdbuf->scroll.col); - command_list_draw_text(cmdbuf->cmds, cmdbuf->left_margin, visual_line, - text, nbytes); - - byte_offset += nbytes; - } - - col_offset = begin->col - cmdbuf->scroll.col; - } - - // activate region color - command_list_set_index_color_bg(cmdbuf->cmds, 5); - - // draw any text on line that should be part of region - if (end->line == line->line) { - if (end->col > cmdbuf->scroll.col) { - uint32_t nbytes = - utf8_nbytes(text + byte_offset, text_nbytes - byte_offset, - end->col - col_offset - cmdbuf->scroll.col); - command_list_draw_text(cmdbuf->cmds, cmdbuf->left_margin + col_offset, - visual_line, text + byte_offset, nbytes); - byte_offset += nbytes; - } - - col_offset = end->col - cmdbuf->scroll.col; - command_list_reset_color(cmdbuf->cmds); - } - - // draw rest of line - if (text_nbytes - byte_offset > 0) { - command_list_draw_text(cmdbuf->cmds, cmdbuf->left_margin + col_offset, - visual_line, text + byte_offset, - text_nbytes - byte_offset); - } - - // done rendering region - command_list_reset_color(cmdbuf->cmds); - - } else { - command_list_draw_text(cmdbuf->cmds, cmdbuf->left_margin, visual_line, text, - text_nbytes); - } - - command_list_set_show_whitespace(cmdbuf->cmds, false); - - uint32_t col = text_nchars + cmdbuf->left_margin; - for (uint32_t bytei = scroll_bytes; bytei < line->nbytes; ++bytei) { - if (line->text[bytei] == '\t') { - col += 3; - } else if (utf8_byte_is_unicode_start(line->text[bytei])) { - wchar_t wc; - if (mbrtowc(&wc, (char *)line->text + bytei, 6, NULL) >= 0) { - col += wcwidth(wc) - 1; - } - } - } - - if (col < cmdbuf->width) { - command_list_draw_repeated(cmdbuf->cmds, col, visual_line, ' ', - cmdbuf->width - col); - } -} - -void scroll(struct buffer *buffer, int line_delta, int col_delta) { - uint32_t nlines = text_num_lines(buffer->text); - int64_t new_line = (int64_t)buffer->scroll.line + line_delta; - if (new_line >= 0 && new_line < nlines) { - buffer->scroll.line = (uint32_t)new_line; - } else if (new_line < 0) { - buffer->scroll.line = 0; - } - - int64_t new_col = (int64_t)buffer->scroll.col + col_delta; - if (new_col >= 0 && - new_col < text_line_length(buffer->text, buffer->dot.line)) { - buffer->scroll.col = (uint32_t)new_col; - } else if (new_col < 0) { - buffer->scroll.col = 0; - } -} - -void to_relative(struct buffer *buffer, uint32_t line, uint32_t col, - int64_t *rel_line, int64_t *rel_col) { - *rel_col = (int64_t)col - (int64_t)buffer->scroll.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; - } else if (utf8_byte_is_unicode_start(line.text[bytei])) { - wchar_t wc; - if (mbrtowc(&wc, (char *)line.text + bytei, 6, NULL) >= 0) { - visual_dot_col += wcwidth(wc) - 1; - } - } - } - - return visual_dot_col; -} - -struct update_hook_result 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]; - - snprintf(left, 128, " %c%c %-16s (%d, %d) (%s)", - buffer->modified ? '*' : '-', buffer->readonly ? '%' : '-', - buffer->name, buffer->dot.line + 1, - visual_dot_col(buffer, buffer->dot.col), buffer->lang.name); - 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 update_hook_result res = {0}; - res.margins.bottom = 1; - return res; -} - -struct linenumdata { - uint32_t longest_nchars; - uint32_t dot_line; -} linenum_data; - -void linenum_render_hook(struct text_chunk *line_data, uint32_t line, - struct command_list *commands, void *userdata) { - struct linenumdata *data = (struct linenumdata *)userdata; - static char buf[16]; - command_list_set_index_color_bg(commands, 236); - command_list_set_index_color_fg( - commands, line_data->line == data->dot_line ? 253 : 244); - uint32_t chars = - snprintf(buf, 16, "%*d", data->longest_nchars + 1, line_data->line + 1); - command_list_draw_text_copy(commands, 0, line, (uint8_t *)buf, chars); - command_list_reset_color(commands); - command_list_draw_text(commands, data->longest_nchars + 1, line, - (uint8_t *)" ", 1); -} - -void clear_empty_linenum_lines(uint32_t line, struct command_list *commands, - void *userdata) { - struct linenumdata *data = (struct linenumdata *)userdata; - uint32_t longest_nchars = data->longest_nchars; - command_list_draw_repeated(commands, 0, line, ' ', longest_nchars + 2); -} - -struct update_hook_result buffer_linenum_hook(struct buffer *buffer, - struct command_list *commands, - uint32_t width, uint32_t height, - uint64_t frame_time, - void *userdata) { - uint32_t total_lines = text_num_lines(buffer->text); - uint32_t longest_nchars = 10; - if (total_lines < 10) { - longest_nchars = 1; - } else if (total_lines < 100) { - longest_nchars = 2; - } else if (total_lines < 1000) { - longest_nchars = 3; - } else if (total_lines < 10000) { - longest_nchars = 4; - } else if (total_lines < 100000) { - longest_nchars = 5; - } else if (total_lines < 1000000) { - longest_nchars = 6; - } else if (total_lines < 10000000) { - longest_nchars = 7; - } else if (total_lines < 100000000) { - longest_nchars = 8; - } else if (total_lines < 1000000000) { - longest_nchars = 9; - } - - linenum_data.longest_nchars = longest_nchars; - linenum_data.dot_line = buffer->dot.line; - struct update_hook_result res = {0}; - res.margins.left = longest_nchars + 2; - res.line_render_hook.callback = linenum_render_hook; - res.line_render_hook.empty_callback = clear_empty_linenum_lines; - res.line_render_hook.userdata = &linenum_data; - - return res; -} - -void buffer_update(struct buffer *buffer, uint32_t width, uint32_t height, - struct command_list *commands, uint64_t frame_time, - uint32_t *relline, uint32_t *relcol) { - if (width == 0 || height == 0) { - return; - } - - uint32_t total_width = width, total_height = height; - struct margin total_margins = {0}; - struct line_render_hook line_hooks[16]; - uint32_t nlinehooks = 0; - for (uint32_t hooki = 0; hooki < buffer->update_hooks.nhooks; ++hooki) { - struct update_hook *h = &buffer->update_hooks.hooks[hooki]; - struct update_hook_result res = - h->callback(buffer, commands, width, height, frame_time, h->userdata); - - struct margin margins = res.margins; - - if (res.line_render_hook.callback != NULL) { - line_hooks[nlinehooks] = res.line_render_hook; - ++nlinehooks; - } - - total_margins.left += margins.left; - total_margins.right += margins.right; - total_margins.top += margins.top; - total_margins.bottom += margins.bottom; - - height -= margins.top + margins.bottom; - width -= margins.left + margins.right; - } - - int64_t rel_line, rel_col; - to_relative(buffer, buffer->dot.line, buffer->dot.col, &rel_line, &rel_col); - int line_delta = 0, col_delta = 0; - if (rel_line < 0) { - line_delta = rel_line - ((int)height / 2); - } else if (rel_line >= height) { - line_delta = (rel_line - height) + height / 2; - } - - if (rel_col < 0) { - col_delta = rel_col - ((int)width / 2); - } else if (rel_col > width) { - col_delta = (rel_col - width) + width / 2; - } - - scroll(buffer, line_delta, col_delta); - - struct setting *show_ws = settings_get("editor.show-whitespace"); - - struct cmdbuf cmdbuf = (struct cmdbuf){ - .cmds = commands, - .scroll = buffer->scroll, - .left_margin = total_margins.left, - .width = total_width, - .line_offset = total_margins.top, - .line_render_hooks = line_hooks, - .nlinerender_hooks = nlinehooks, - .mark_set = buffer->mark_set, - .region = to_region(buffer->dot, buffer->mark), - .show_ws = show_ws != NULL ? show_ws->value.bool_value : true, - }; - text_for_each_line(buffer->text, buffer->scroll.line, height, render_line, - &cmdbuf); - - // draw empty lines - uint32_t nlines = text_num_lines(buffer->text); - for (uint32_t linei = nlines - buffer->scroll.line + total_margins.top; - linei < height; ++linei) { - - for (uint32_t hooki = 0; hooki < nlinehooks; ++hooki) { - struct line_render_hook *hook = &line_hooks[hooki]; - hook->empty_callback(linei, commands, hook->userdata); - } - - command_list_draw_repeated(commands, total_margins.left, linei, ' ', - total_width - total_margins.left); - } - - // update the visual cursor position - 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 + total_margins.top; - *relcol = rel_col < 0 ? 0 : (uint32_t)rel_col + total_margins.left; -} - -struct text_chunk buffer_get_line(struct buffer *buffer, uint32_t line) { - return text_get_line(buffer->text, line); -} |
