From 54c9b4b533210b77be998f458ff96bdc54272f64 Mon Sep 17 00:00:00 2001 From: Albert Cervin Date: Wed, 12 Jul 2023 16:20:50 +0200 Subject: big buffer/buffer_view rework A buffer is only the text and the corresponding operation. A buffer view holds information about scroll, dot and mark positions. One way to think about it is that a buffer is stateless whereas a buffer view is stateful. --- src/dged/buffer.c | 1294 +++++++++++++++------------------------------ src/dged/buffer.h | 550 ++++++++++++------- src/dged/buffer_view.c | 418 +++++++++++++++ src/dged/buffer_view.h | 100 ++++ src/dged/display.c | 91 ++-- src/dged/display.h | 7 +- src/dged/location.c | 68 +++ src/dged/location.h | 81 +++ src/dged/minibuffer.c | 40 +- src/dged/minibuffer.h | 4 + src/dged/text.c | 40 ++ src/dged/text.h | 36 +- src/dged/window.c | 216 ++++++-- src/dged/window.h | 17 +- src/main/cmds.c | 241 ++++----- src/main/main.c | 26 +- src/main/search-replace.c | 105 ++-- 17 files changed, 1991 insertions(+), 1343 deletions(-) create mode 100644 src/dged/buffer_view.c create mode 100644 src/dged/buffer_view.h create mode 100644 src/dged/location.c create mode 100644 src/dged/location.h (limited to 'src') diff --git a/src/dged/buffer.c b/src/dged/buffer.c index c826401..117fab5 100644 --- a/src/dged/buffer.c +++ b/src/dged/buffer.c @@ -29,7 +29,7 @@ struct modeline { #define KILL_RING_SZ 64 static struct kill_ring { struct text_chunk buffer[KILL_RING_SZ]; - struct buffer_location last_paste; + struct location last_paste; bool paste_up_to_date; uint32_t curr_idx; uint32_t paste_idx; @@ -46,67 +46,6 @@ static struct create_hook { } g_create_hooks[MAX_CREATE_HOOKS]; static uint32_t g_num_create_hooks = 0; -struct update_hook_result buffer_linenum_hook(struct buffer_view *view, - 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_view *view, - struct command_list *commands, - uint32_t width, uint32_t height, - uint64_t frame_time, - void *userdata); - -struct buffer_view buffer_view_create(struct buffer *buffer, bool modeline, - bool line_numbers) { - struct buffer_view view = { - .dot = {0}, - .mark = {0}, - .mark_set = false, - .scroll = {0}, - .buffer = buffer, - .modeline = NULL, - .line_numbers = line_numbers, - }; - - if (modeline) { - view.modeline = calloc(1, sizeof(struct modeline)); - view.modeline->buffer = malloc(1024); - view.modeline->sz = 1024; - view.modeline->buffer[0] = '\0'; - } - - return view; -} - -struct buffer_view buffer_view_clone(struct buffer_view *view) { - struct buffer_view c = { - .dot = view->dot, - .mark = view->mark, - .mark_set = view->mark_set, - .scroll = view->scroll, - .buffer = view->buffer, - .modeline = NULL, - .line_numbers = view->line_numbers, - }; - - if (view->modeline) { - c.modeline = calloc(1, sizeof(struct modeline)); - c.modeline->buffer = malloc(view->modeline->sz); - memcpy(c.modeline->buffer, view->modeline->buffer, view->modeline->sz); - } - - return c; -} - -void buffer_view_destroy(struct buffer_view *view) { - if (view->modeline != NULL) { - free(view->modeline->buffer); - free(view->modeline); - } -} - uint32_t buffer_add_create_hook(create_hook_cb hook, void *userdata) { if (g_num_create_hooks < MAX_CREATE_HOOKS) { g_create_hooks[g_num_create_hooks] = (struct create_hook){ @@ -119,56 +58,6 @@ uint32_t buffer_add_create_hook(create_hook_cb hook, void *userdata) { return g_num_create_hooks - 1; } -struct buffer create_internal(char *name, char *filename) { - struct buffer b = (struct buffer){ - .filename = filename, - .name = strdup(name), - .text = text_create(10), - .modified = false, - .readonly = false, - .lang = - filename != NULL ? lang_from_filename(filename) : lang_from_id("fnd"), - .last_write = {0}, - }; - - VEC_INIT(&b.text_properties, 32); - - undo_init(&b.undo, 100); - - return b; -} - -struct buffer buffer_create(char *name) { - - struct buffer b = create_internal(name, NULL); - - for (uint32_t hooki = 0; hooki < g_num_create_hooks; ++hooki) { - g_create_hooks[hooki].callback(&b, g_create_hooks[hooki].userdata); - } - - return b; -} - -void buffer_destroy(struct buffer *buffer) { - VEC_DESTROY(&buffer->text_properties); - text_destroy(buffer->text); - buffer->text = NULL; - - free(buffer->name); - buffer->name = NULL; - - free(buffer->filename); - buffer->filename = NULL; - - undo_destroy(&buffer->undo); -} - -void buffer_clear(struct buffer_view *view) { - text_clear(view->buffer->text); - view->dot.col = view->dot.line = 0; - view->scroll.col = view->scroll.line = 0; -} - void buffer_static_init() { settings_register_setting( "editor.tab-width", @@ -187,335 +76,95 @@ void buffer_static_teardown() { } } -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; -} - -bool buffer_is_backed(struct buffer *buffer) { - return buffer->filename != NULL; -} - -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}); +static struct buffer create_internal(const char *name, char *filename) { + struct buffer b = (struct buffer){ + .filename = filename, + .name = strdup(name), + .text = text_create(10), + .modified = false, + .readonly = false, + .lang = + filename != NULL ? lang_from_filename(filename) : lang_from_id("fnd"), + .last_write = {0}, + }; - text_delete(buffer->text, start.line, start.col, end.line, end.col); - buffer->modified = true; -} + VEC_INIT(&b.update_hooks, 32); -void buffer_goto_beginning(struct buffer_view *view) { - view->dot.col = 0; - view->dot.line = 0; -} + undo_init(&b.undo, 100); -void buffer_goto_end(struct buffer_view *view) { - view->dot.line = text_num_lines(view->buffer->text); - view->dot.col = 0; + return b; } -bool movev(struct buffer_view *view, int rowdelta) { - int64_t new_line = (int64_t)view->dot.line + rowdelta; - +static bool movev(struct buffer *buffer, int64_t linedelta, + struct location *location) { + int64_t new_line = (int64_t)location->line + linedelta; if (new_line < 0) { - view->dot.line = 0; + location->line = 0; return false; - } else if (new_line > text_num_lines(view->buffer->text)) { - view->dot.line = text_num_lines(view->buffer->text); + } else if (new_line > text_num_lines(buffer->text)) { + // allow addition of an extra line by going past the bottom + location->line = text_num_lines(buffer->text); return false; } else { - view->dot.line = (uint32_t)new_line; + location->line = (uint32_t)new_line; // make sure column stays on the line - uint32_t linelen = text_line_length(view->buffer->text, view->dot.line); - view->dot.col = view->dot.col > linelen ? linelen : view->dot.col; + uint32_t linelen = text_line_length(buffer->text, location->line); + location->col = location->col > linelen ? linelen : location->col; return true; } } // move dot `coldelta` chars -bool moveh(struct buffer_view *view, int coldelta) { - int64_t new_col = (int64_t)view->dot.col + coldelta; - - if (new_col > (int64_t)text_line_length(view->buffer->text, view->dot.line)) { - if (movev(view, 1)) { - view->dot.col = 0; +static bool moveh(struct buffer *buffer, int64_t coldelta, + struct location *location) { + int64_t new_col = (int64_t)location->col + coldelta; + if (new_col > (int64_t)text_line_length(buffer->text, location->line)) { + if (movev(buffer, 1, location)) { + location->col = 0; } } else if (new_col < 0) { - if (movev(view, -1)) { - view->dot.col = text_line_length(view->buffer->text, view->dot.line); + if (movev(buffer, -1, location)) { + location->col = text_line_length(buffer->text, location->line); } else { return false; } } else { - view->dot.col = new_col; + location->col = new_col; } return true; } -void buffer_goto(struct buffer_view *view, uint32_t line, uint32_t col) { - int64_t linedelta = (int64_t)line - (int64_t)view->dot.line; - movev(view, linedelta); - - int64_t coldelta = (int64_t)col - (int64_t)view->dot.col; - moveh(view, 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_view *view) { - return to_region(view->dot, view->mark); -} - -bool buffer_region_has_size(struct buffer_view *view) { - return view->mark_set && - (labs((int64_t)view->mark.line - (int64_t)view->dot.line) + - labs((int64_t)view->mark.col - (int64_t)view->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_view *view) { - if (buffer_region_has_size(view)) { - struct region reg = buffer_get_region(view); - struct text_chunk *curr = copy_region(view->buffer, reg); - buffer_clear_mark(view); - } -} - -void paste(struct buffer_view *view, 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 = view->mark_set ? view->mark : view->dot; - buffer_add_text(view, curr->text, curr->nbytes); - g_kill_ring.paste_up_to_date = true; - } - } -} - -void buffer_paste(struct buffer_view *view) { - g_kill_ring.paste_idx = g_kill_ring.curr_idx; - paste(view, g_kill_ring.curr_idx); -} - -void buffer_paste_older(struct buffer_view *view) { - 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(view->buffer, g_kill_ring.last_paste, view->dot); - - // place ourselves right - view->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(view, g_kill_ring.paste_idx); - - } else { - buffer_paste(view); - } -} - -void buffer_cut(struct buffer_view *view) { - if (buffer_region_has_size(view)) { - struct region reg = buffer_get_region(view); - copy_region(view->buffer, reg); - delete_with_undo(view->buffer, reg.begin, reg.end); - buffer_clear_mark(view); - view->dot = reg.begin; - } -} - -bool maybe_delete_region(struct buffer_view *view) { - if (buffer_region_has_size(view)) { - struct region reg = buffer_get_region(view); - delete_with_undo(view->buffer, reg.begin, reg.end); - buffer_clear_mark(view); - view->dot = reg.begin; - return true; - } - - return false; -} - -void buffer_kill_line(struct buffer_view *view) { - uint32_t nchars = - text_line_length(view->buffer->text, view->dot.line) - view->dot.col; - if (nchars == 0) { - nchars = 1; - } - - struct region reg = { - .begin = view->dot, - .end = - { - .line = view->dot.line, - .col = view->dot.col + nchars, - }, - }; - - copy_region(view->buffer, reg); - delete_with_undo(view->buffer, view->dot, - (struct buffer_location){ - .line = view->dot.line, - .col = view->dot.col + nchars, - }); -} - -void buffer_forward_delete_char(struct buffer_view *view) { - if (maybe_delete_region(view)) { - return; - } - - delete_with_undo(view->buffer, view->dot, - (struct buffer_location){ - .line = view->dot.line, - .col = view->dot.col + 1, - }); -} - -void buffer_backward_delete_char(struct buffer_view *view) { - if (maybe_delete_region(view)) { - return; - } - - if (moveh(view, -1)) { - buffer_forward_delete_char(view); - } -} - -void buffer_forward_delete_word(struct buffer_view *view) { - if (maybe_delete_region(view)) { - return; - } - - struct buffer_location start = view->dot; - buffer_forward_word(view); - struct buffer_location end = view->dot; - - buffer_goto(view, start.line, start.col); - - delete_with_undo(view->buffer, start, end); -} - -void buffer_backward_delete_word(struct buffer_view *view) { - if (maybe_delete_region(view)) { +static void delete_with_undo(struct buffer *buffer, struct location start, + struct location end) { + if (buffer->readonly) { + minibuffer_echo_timeout(4, "buffer is read-only"); return; } - struct buffer_location end = view->dot; - buffer_backward_word(view); - struct buffer_location start = view->dot; + struct text_chunk txt = + text_get_region(buffer->text, start.line, start.col, end.line, end.col); - buffer_goto(view, start.line, start.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}); - delete_with_undo(view->buffer, start, end); + text_delete(buffer->text, start.line, start.col, end.line, end.col); + buffer->modified = true; } -void buffer_backward_char(struct buffer_view *view) { moveh(view, -1); } -void buffer_forward_char(struct buffer_view *view) { moveh(view, 1); } - -struct buffer_location find_next(struct buffer_view *view, uint8_t chars[], - uint32_t nchars, int direction) { - struct text_chunk line = text_get_line(view->buffer->text, view->dot.line); - int64_t bytei = - text_col_to_byteindex(view->buffer->text, view->dot.line, view->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; - } +static void maybe_delete_region(struct buffer *buffer, struct region region) { + if (region_has_size(region)) { + delete_with_undo(buffer, region.begin, region.end); } - - uint32_t target_col = - text_byteindex_to_col(view->buffer->text, view->dot.line, bytei); - return (struct buffer_location){.line = view->dot.line, .col = target_col}; -} - -void buffer_forward_word(struct buffer_view *view) { - moveh(view, 1); - uint8_t chars[] = {' ', '.'}; - view->dot = find_next(view, chars, 2, 1); -} - -void buffer_backward_word(struct buffer_view *view) { - moveh(view, -1); - uint8_t chars[] = {' ', '.'}; - view->dot = find_next(view, chars, 2, -1); -} - -void buffer_backward_line(struct buffer_view *view) { movev(view, -1); } -void buffer_forward_line(struct buffer_view *view) { movev(view, 1); } - -void buffer_end_of_line(struct buffer_view *view) { - view->dot.col = text_line_length(view->buffer->text, view->dot.line); } -void buffer_beginning_of_line(struct buffer_view *view) { view->dot.col = 0; } - -void buffer_read_from_file(struct buffer *b) { +static void buffer_read_from_file(struct buffer *b) { struct stat sb; char *fullname = to_abspath(b->filename); if (stat(fullname, &sb) == 0) { @@ -554,10 +203,56 @@ void buffer_read_from_file(struct buffer *b) { undo_push_boundary(&b->undo, (struct undo_boundary){.save_point = true}); } -struct buffer buffer_from_file(char *filename) { - char *full_filename = to_abspath(filename); - struct buffer b = create_internal(basename((char *)filename), full_filename); - buffer_read_from_file(&b); +static 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); +} + +static struct location find_next(struct buffer *buffer, struct location from, + uint8_t chars[], uint32_t nchars, + int direction) { + struct text_chunk line = text_get_line(buffer->text, from.line); + int64_t bytei = text_col_to_byteindex(buffer->text, from.line, from.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, from.line, bytei); + return (struct location){.line = from.line, .col = target_col}; +} + +static 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; +} + +/* --------------------- buffer methods -------------------- */ + +struct buffer buffer_create(const char *name) { + + struct buffer b = create_internal(name, NULL); for (uint32_t hooki = 0; hooki < g_num_create_hooks; ++hooki) { g_create_hooks[hooki].callback(&b, g_create_hooks[hooki].userdata); @@ -566,12 +261,16 @@ struct buffer buffer_from_file(char *filename) { return b; } -void write_line(struct text_chunk *chunk, void *userdata) { - FILE *file = (FILE *)userdata; - fwrite(chunk->text, 1, chunk->nbytes, file); +struct buffer buffer_from_file(const char *path) { + char *full_path = to_abspath(path); + struct buffer b = create_internal(basename((char *)path), full_path); + buffer_read_from_file(&b); - // final newline is not optional! - fputc('\n', file); + for (uint32_t hooki = 0; hooki < g_num_create_hooks; ++hooki) { + g_create_hooks[hooki].callback(&b, g_create_hooks[hooki].userdata); + } + + return b; } void buffer_to_file(struct buffer *buffer) { @@ -609,167 +308,177 @@ void buffer_to_file(struct buffer *buffer) { undo_push_boundary(&buffer->undo, (struct undo_boundary){.save_point = true}); } -void buffer_write_to(struct buffer *buffer, const char *filename) { +void buffer_set_filename(struct buffer *buffer, const char *filename) { buffer->filename = to_abspath(filename); buffer->modified = true; - buffer_to_file(buffer); } void buffer_reload(struct buffer *buffer) { if (buffer->filename == NULL) { return; } - - // check if we actually need to reload - struct stat sb; - if (stat(buffer->filename, &sb) < 0) { - minibuffer_echo_timeout(4, "failed to run stat on %s", buffer->filename); - return; - } - - if (sb.st_mtim.tv_sec != buffer->last_write.tv_sec) { - text_clear(buffer->text); - buffer_read_from_file(buffer); - } else { - minibuffer_echo_timeout(2, "buffer %s not changed", buffer->filename); - } -} - -struct search_data { - VEC(struct match) matches; - const char *pattern; -}; - -// TODO: maybe should live in text -void search_line(struct text_chunk *chunk, void *userdata) { - struct search_data *data = (struct search_data *)userdata; - size_t pattern_len = strlen(data->pattern); - uint32_t pattern_nchars = utf8_nchars((uint8_t *)data->pattern, pattern_len); - - char *line = malloc(chunk->nbytes + 1); - strncpy(line, chunk->text, chunk->nbytes); - line[chunk->nbytes] = '\0'; - char *hit = NULL; - uint32_t byteidx = 0; - while ((hit = strstr(line + byteidx, data->pattern)) != NULL) { - byteidx = hit - line; - uint32_t begin = utf8_nchars(chunk->text, byteidx); - struct match match = (struct match){ - .begin = {.col = begin, .line = chunk->line}, - .end = {.col = begin + pattern_nchars - 1, .line = chunk->line}, - }; - - VEC_PUSH(&data->matches, match); - - // proceed to after match - byteidx += pattern_len; + + // check if we actually need to reload + struct stat sb; + if (stat(buffer->filename, &sb) < 0) { + minibuffer_echo_timeout(4, "failed to run stat on %s", buffer->filename); + return; } - free(line); + if (sb.st_mtim.tv_sec != buffer->last_write.tv_sec) { + text_clear(buffer->text); + buffer_read_from_file(buffer); + } else { + minibuffer_echo_timeout(2, "buffer %s not changed", buffer->filename); + } } -void buffer_find(struct buffer *buffer, const char *pattern, - struct match **matches, uint32_t *nmatches) { +void buffer_destroy(struct buffer *buffer) { + text_destroy(buffer->text); + buffer->text = NULL; - struct search_data data = (struct search_data){.pattern = pattern}; - VEC_INIT(&data.matches, 16); - text_for_each_line(buffer->text, 0, text_num_lines(buffer->text), search_line, - &data); + free(buffer->name); + buffer->name = NULL; - *matches = VEC_ENTRIES(&data.matches); - *nmatches = VEC_SIZE(&data.matches); -} + free(buffer->filename); + buffer->filename = NULL; -void buffer_set_text(struct buffer *buffer, uint8_t *text, uint32_t nbytes) { - text_clear(buffer->text); - uint32_t lines, cols; - text_append(buffer->text, text, nbytes, &lines, &cols); + undo_destroy(&buffer->undo); } -int buffer_add_text(struct buffer_view *view, uint8_t *text, uint32_t nbytes) { - if (view->buffer->readonly) { +struct location buffer_add(struct buffer *buffer, struct location at, + uint8_t *text, uint32_t nbytes) { + if (buffer->readonly) { minibuffer_echo_timeout(4, "buffer is read-only"); - return 0; + return at; } // 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(view); - - struct buffer_location initial = view->dot; + struct location initial = at; + struct location final = at; uint32_t lines_added, cols_added; - text_insert_at(view->buffer->text, initial.line, initial.col, text, nbytes, + text_insert_at(buffer->text, initial.line, initial.col, text, nbytes, &lines_added, &cols_added); // move to after inserted text - movev(view, lines_added); + movev(buffer, lines_added, &final); if (lines_added > 0) { // does not make sense to use position from another line - view->dot.col = 0; + final.col = 0; } - moveh(view, cols_added); + moveh(buffer, cols_added, &final); - struct buffer_location final = view->dot; undo_push_add( - &view->buffer->undo, + &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(&view->buffer->undo, + undo_push_boundary(&buffer->undo, (struct undo_boundary){.save_point = false}); } - view->buffer->modified = true; - return lines_added; + buffer->modified = true; + return final; } -void buffer_newline(struct buffer_view *view) { - buffer_add_text(view, (uint8_t *)"\n", 1); +struct location buffer_set_text(struct buffer *buffer, uint8_t *text, + uint32_t nbytes) { + uint32_t lines, cols; + + text_clear(buffer->text); + text_append(buffer->text, text, nbytes, &lines, &cols); + return buffer_clamp(buffer, lines, cols); } -void buffer_indent(struct buffer_view *view) { - uint32_t tab_width = view->buffer->lang.tab_width; - buffer_add_text(view, (uint8_t *)" ", - tab_width > 16 ? 16 : tab_width); +void buffer_clear(struct buffer *buffer) { text_clear(buffer->text); } + +bool buffer_is_empty(struct buffer *buffer) { + return text_num_lines(buffer->text) == 0; } -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; +bool buffer_is_modified(struct buffer *buffer) { return buffer->modified; } +bool buffer_is_readonly(struct buffer *buffer) { return buffer->readonly; } - ++buffer->update_hooks.nhooks; +void buffer_set_readonly(struct buffer *buffer, bool readonly) { + buffer->readonly = readonly; +} - // TODO: cant really have this if we actually want to remove a hook - return buffer->update_hooks.nhooks - 1; +bool buffer_is_backed(struct buffer *buffer) { + return buffer->filename != NULL; +} + +struct location buffer_previous_char(struct buffer *buffer, + struct location dot) { + moveh(buffer, -1, &dot); + return dot; +} + +struct location buffer_previous_word(struct buffer *buffer, + struct location dot) { + moveh(buffer, -1, &dot); + uint8_t chars[] = {' ', '.'}; + return find_next(buffer, dot, chars, 2, -1); +} + +struct location buffer_previous_line(struct buffer *buffer, + struct location dot) { + movev(buffer, -1, &dot); + return dot; +} + +struct location buffer_next_char(struct buffer *buffer, struct location dot) { + moveh(buffer, 1, &dot); + return dot; } -void buffer_set_mark(struct buffer_view *view) { - view->mark_set ? buffer_clear_mark(view) - : buffer_set_mark_at(view, view->dot.line, view->dot.col); +struct location buffer_next_word(struct buffer *buffer, struct location dot) { + moveh(buffer, 1, &dot); + uint8_t chars[] = {' ', '.'}; + return find_next(buffer, dot, chars, 2, 1); +} + +struct location buffer_next_line(struct buffer *buffer, struct location dot) { + movev(buffer, 1, &dot); + return dot; +} + +struct location buffer_clamp(struct buffer *buffer, int64_t line, int64_t col) { + struct location location = {.line = 0, .col = 0}; + movev(buffer, line, &location); + moveh(buffer, col, &location); + + return location; +} + +struct location buffer_end(struct buffer *buffer) { + uint32_t nlines = buffer_num_lines(buffer); + return (struct location){nlines, buffer_num_chars(buffer, nlines)}; } -void buffer_clear_mark(struct buffer_view *view) { - view->mark_set = false; - minibuffer_echo_timeout(2, "mark cleared"); +uint32_t buffer_num_lines(struct buffer *buffer) { + return text_num_lines(buffer->text); } -void buffer_set_mark_at(struct buffer_view *view, uint32_t line, uint32_t col) { - view->mark_set = true; - view->mark.line = line; - view->mark.col = col; - minibuffer_echo_timeout(2, "mark set"); +uint32_t buffer_num_chars(struct buffer *buffer, uint32_t line) { + return text_line_length(buffer->text, line); } -void buffer_undo(struct buffer_view *view) { - struct undo_stack *undo = &view->buffer->undo; +struct location buffer_newline(struct buffer *buffer, struct location at) { + return buffer_add(buffer, at, (uint8_t *)"\n", 1); +} + +struct location buffer_indent(struct buffer *buffer, struct location at) { + uint32_t tab_width = buffer->lang.tab_width; + buffer_add(buffer, at, (uint8_t *)" ", + tab_width > 16 ? 16 : tab_width); +} + +struct location buffer_undo(struct buffer *buffer, struct location dot) { + struct undo_stack *undo = &buffer->undo; undo_begin(undo); // fetch and handle records @@ -783,62 +492,213 @@ void buffer_undo(struct buffer_view *view) { undo_next(undo, &records, &nrecords); + struct location pos = dot; 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) { - view->buffer->modified = false; + buffer->modified = false; } break; } + case Undo_Add: { struct undo_add *add = &rec->add; - delete_with_undo(view->buffer, - (struct buffer_location){ - .line = add->begin.row, - .col = add->begin.col, - }, - (struct buffer_location){ - .line = add->end.row, - .col = add->end.col, - }); + pos = + buffer_delete(buffer, (struct region){.begin = + (struct location){ + .line = add->begin.row, + .col = add->begin.col, + }, + .end = (struct location){ + .line = add->end.row, + .col = add->end.col, + }}); - buffer_goto(view, add->begin.row, add->begin.col); break; } + case Undo_Delete: { struct undo_delete *del = &rec->delete; - buffer_goto(view, del->pos.row, del->pos.col); - buffer_add_text(view, del->data, del->nbytes); + pos = buffer_add(buffer, + (struct location){ + .line = del->pos.row, + .col = del->pos.col, + }, + del->data, del->nbytes); break; } } } + undo_push_boundary(undo, (struct undo_boundary){.save_point = false}); free(records); undo_end(undo); + + return pos; +} + +/* --------------- searching and supporting types ---------------- */ +struct search_data { + VEC(struct region) matches; + const char *pattern; +}; + +// TODO: maybe should live in text +static void search_line(struct text_chunk *chunk, void *userdata) { + struct search_data *data = (struct search_data *)userdata; + size_t pattern_len = strlen(data->pattern); + uint32_t pattern_nchars = utf8_nchars((uint8_t *)data->pattern, pattern_len); + + char *line = malloc(chunk->nbytes + 1); + strncpy(line, chunk->text, chunk->nbytes); + line[chunk->nbytes] = '\0'; + char *hit = NULL; + uint32_t byteidx = 0; + while ((hit = strstr(line + byteidx, data->pattern)) != NULL) { + byteidx = hit - line; + uint32_t begin = utf8_nchars(chunk->text, byteidx); + struct region match = + region_new((struct location){.col = begin, .line = chunk->line}, + (struct location){.col = begin + pattern_nchars - 1, + .line = chunk->line}); + VEC_PUSH(&data->matches, match); + + // proceed to after match + byteidx += pattern_len; + } + + free(line); +} + +void buffer_find(struct buffer *buffer, const char *pattern, + struct region **matches, uint32_t *nmatches) { + + struct search_data data = (struct search_data){.pattern = pattern}; + VEC_INIT(&data.matches, 16); + text_for_each_line(buffer->text, 0, text_num_lines(buffer->text), search_line, + &data); + + *matches = VEC_ENTRIES(&data.matches); + *nmatches = VEC_SIZE(&data.matches); +} + +struct location buffer_copy(struct buffer *buffer, struct region region) { + if (region_has_size(region)) { + struct text_chunk *curr = copy_region(buffer, region); + } + + return region.begin; +} + +struct location buffer_cut(struct buffer *buffer, struct region region) { + if (region_has_size(region)) { + copy_region(buffer, region); + buffer_delete(buffer, region); + } + + return region.begin; +} + +struct location buffer_delete(struct buffer *buffer, struct region region) { + if (buffer->readonly) { + minibuffer_echo_timeout(4, "buffer is read-only"); + return region.end; + } + + if (!region_has_size(region)) { + return region.begin; + } + + struct text_chunk txt = + text_get_region(buffer->text, region.begin.line, region.begin.col, + region.end.line, region.end.col); + + undo_push_delete(&buffer->undo, + (struct undo_delete){.data = txt.text, + .nbytes = txt.nbytes, + .pos = {.row = region.begin.line, + .col = region.begin.col}}); + undo_push_boundary(&buffer->undo, + (struct undo_boundary){.save_point = false}); + + text_delete(buffer->text, region.begin.line, region.begin.col, + region.end.line, region.end.col); + buffer->modified = true; + + return region.begin; +} + +static struct location paste(struct buffer *buffer, struct location at, + uint32_t ring_idx) { + struct location new_loc = at; + if (ring_idx > 0) { + struct text_chunk *curr = &g_kill_ring.buffer[ring_idx - 1]; + if (curr->text != NULL) { + g_kill_ring.last_paste = at; + new_loc = buffer_add(buffer, at, curr->text, curr->nbytes); + g_kill_ring.paste_up_to_date = true; + } + } + + return new_loc; +} + +struct location buffer_paste(struct buffer *buffer, struct location at) { + g_kill_ring.paste_idx = g_kill_ring.curr_idx; + return paste(buffer, at, g_kill_ring.curr_idx); +} + +struct location buffer_paste_older(struct buffer *buffer, struct location at) { + 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, at); + + // 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.last_paste, g_kill_ring.paste_idx); + + } else { + buffer_paste(buffer, at); + } +} + +struct text_chunk buffer_line(struct buffer *buffer, uint32_t line) { + return text_get_line(buffer->text, line); +} + +uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook, + void *userdata) { + VEC_APPEND(&buffer->update_hooks, struct update_hook_entry * e); + struct update_hook *h = &e->hook; + h->callback = hook; + h->userdata = userdata; + + // TODO: cant really have this if we actually want to remove a hook + return VEC_SIZE(&buffer->update_hooks) - 1; } struct cmdbuf { struct command_list *cmds; - struct buffer_location scroll; - uint32_t line_offset; - uint32_t left_margin; + struct location origin; uint32_t width; - - struct region region; - bool mark_set; + uint32_t height; bool show_ws; - struct line_render_hook *line_render_hooks; - uint32_t nlinerender_hooks; - struct buffer *buffer; }; @@ -865,33 +725,26 @@ static uint32_t visual_string_width(uint8_t *txt, uint32_t len, 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 visual_line = line->line - cmdbuf->origin.line; command_list_set_show_whitespace(cmdbuf->cmds, cmdbuf->show_ws); - struct buffer_location *begin = &cmdbuf->region.begin, - *end = &cmdbuf->region.end; // calculate scroll offsets uint32_t scroll_bytes = - utf8_nbytes(line->text, line->nbytes, cmdbuf->scroll.col); + utf8_nbytes(line->text, line->nbytes, cmdbuf->origin.col); uint32_t text_nbytes_scroll = scroll_bytes > line->nbytes ? 0 : line->nbytes - scroll_bytes; uint8_t *text = line->text + scroll_bytes; - uint32_t visual_col_start = cmdbuf->left_margin; - uint32_t cur_visual_col = visual_col_start; + uint32_t visual_col_start = 0; + uint32_t cur_visual_col = 0; uint32_t start_byte = 0, text_nbytes = 0; struct text_property *properties[16] = {0}; struct text_property *prev_properties[16] = {0}; uint32_t prev_nproperties; for (uint32_t cur_byte = start_byte, coli = 0; cur_byte < text_nbytes_scroll && cur_visual_col < cmdbuf->width && - coli < line->nchars - cmdbuf->scroll.col; + coli < line->nchars - cmdbuf->origin.col; ++coli) { uint32_t bytes_remaining = text_nbytes_scroll - cur_byte; @@ -900,10 +753,9 @@ void render_line(struct text_chunk *line, void *userdata) { // calculate character properties uint32_t nproperties = 0; - buffer_get_text_properties( - cmdbuf->buffer, - (struct buffer_location){.line = line->line, .col = coli}, properties, - 16, &nproperties); + text_get_properties(cmdbuf->buffer->text, + (struct location){.line = line->line, .col = coli + cmdbuf->origin.col}, + properties, 16, &nproperties); // handle changes to properties uint32_t nnew_props = 0; @@ -967,327 +819,55 @@ void render_line(struct text_chunk *line, void *userdata) { } } -void scroll(struct buffer_view *view, int line_delta, int col_delta) { - uint32_t nlines = text_num_lines(view->buffer->text); - int64_t new_line = (int64_t)view->scroll.line + line_delta; - if (new_line >= 0 && new_line < nlines) { - view->scroll.line = (uint32_t)new_line; - } else if (new_line < 0) { - view->scroll.line = 0; - } - - int64_t new_col = (int64_t)view->scroll.col + col_delta; - if (new_col >= 0 && - new_col < text_line_length(view->buffer->text, view->dot.line)) { - view->scroll.col = (uint32_t)new_col; - } else if (new_col < 0) { - view->scroll.col = 0; - } -} - -void to_relative(struct buffer_view *view, uint32_t line, uint32_t col, - int64_t *rel_line, int64_t *rel_col) { - *rel_col = (int64_t)col - (int64_t)view->scroll.col; - *rel_line = (int64_t)line - (int64_t)view->scroll.line; -} - -uint32_t visual_dot_col(struct buffer_view *view, uint32_t dot_col) { - struct text_chunk line = text_get_line(view->buffer->text, view->dot.line); - return visual_string_width(line.text, line.nbytes, view->scroll.col, dot_col); -} - -void render_modeline(struct modeline *modeline, struct buffer_view *view, - struct command_list *commands, uint32_t window_id, - uint32_t width, uint32_t height, 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; - - time_t now = time(NULL); - struct tm *lt = localtime(&now); - char left[128], right[128]; - - snprintf(left, 128, " %c%c %d:%-16s (%d, %d) (%s)", - view->buffer->modified ? '*' : '-', - view->buffer->readonly ? '%' : '-', window_id, view->buffer->name, - view->dot.line + 1, visual_dot_col(view, view->dot.col), - view->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); - - if (strcmp(buf, (char *)modeline->buffer) != 0) { - modeline->buffer = realloc(modeline->buffer, width * 4); - modeline->sz = 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 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, 8); - command_list_set_index_color_fg(commands, - line_data->line == data->dot_line ? 15 : 7); - 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); -} - -uint32_t longest_linenum(struct buffer *buffer) { - 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; - } - - return longest_nchars; -} - -void buffer_update(struct buffer_view *view, uint32_t window_id, 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) { +void buffer_update(struct buffer *buffer, struct buffer_update_params *params) { + if (params->width == 0 || params->height == 0) { return; } - uint32_t total_width = width, total_height = height; - struct margin total_margins = {0}; - struct line_render_hook line_hooks[16 + 1]; - uint32_t nlinehooks = 0; - for (uint32_t hooki = 0; hooki < view->buffer->update_hooks.nhooks; ++hooki) { - struct update_hook *h = &view->buffer->update_hooks.hooks[hooki]; - struct update_hook_result res = - h->callback(view, commands, width, height, frame_time, h->userdata); - - if (res.line_render_hook.callback != NULL) { - line_hooks[nlinehooks] = res.line_render_hook; - ++nlinehooks; - } - - total_margins.left += res.margins.left; - total_margins.right += res.margins.right; - total_margins.bottom += res.margins.bottom; - total_margins.top += res.margins.top; - - height -= total_margins.top + total_margins.bottom; - width -= total_margins.left + total_margins.right; - } - - if (view->line_numbers) { - linenum_data.longest_nchars = longest_linenum(view->buffer); - linenum_data.dot_line = view->dot.line; - line_hooks[nlinehooks].callback = linenum_render_hook; - line_hooks[nlinehooks].empty_callback = clear_empty_linenum_lines; - line_hooks[nlinehooks].userdata = &linenum_data; - ++nlinehooks; - - total_margins.left += linenum_data.longest_nchars + 2; - } - - if (view->modeline != NULL) { - render_modeline(view->modeline, view, commands, window_id, width, height, - frame_time); - total_margins.bottom += 1; - } - - height -= total_margins.top + total_margins.bottom; - width -= total_margins.left + total_margins.right; - - int64_t rel_line, rel_col; - to_relative(view, view->dot.line, view->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; + VEC_FOR_EACH(&buffer->update_hooks, struct update_hook_entry * entry) { + struct update_hook *h = &entry->hook; + h->callback(buffer, params->width, params->height, h->userdata); } - scroll(view, line_delta, col_delta); - struct setting *show_ws = settings_get("editor.show-whitespace"); - if (buffer_region_has_size(view)) { - struct region reg = to_region(view->dot, view->mark); - buffer_add_text_property(view->buffer, reg.begin, reg.end, - (struct text_property){ - .type = TextProperty_Colors, - .colors = - (struct text_property_colors){ - .set_bg = true, - .bg = 5, - .set_fg = false, - }, - }); - } - struct cmdbuf cmdbuf = (struct cmdbuf){ - .cmds = commands, - .scroll = view->scroll, - .left_margin = total_margins.left, - .width = total_width, - .line_offset = total_margins.top, - .line_render_hooks = line_hooks, - .nlinerender_hooks = nlinehooks, - .mark_set = view->mark_set, - .region = to_region(view->dot, view->mark), + .cmds = params->commands, + .origin = params->origin, + .width = params->width, + .height = params->height, .show_ws = show_ws != NULL ? show_ws->value.bool_value : true, - .buffer = view->buffer, + .buffer = buffer, }; - text_for_each_line(view->buffer->text, view->scroll.line, height, render_line, - &cmdbuf); + text_for_each_line(buffer->text, params->origin.line, params->height, + render_line, &cmdbuf); // draw empty lines - uint32_t nlines = text_num_lines(view->buffer->text); - for (uint32_t linei = nlines - view->scroll.line + total_margins.top; - linei < height; ++linei) { - - for (uint32_t hooki = 0; hooki < nlinehooks; ++hooki) { - struct line_render_hook *hook = &line_hooks[hooki]; - if (hook->empty_callback != NULL) { - hook->empty_callback(linei, commands, hook->userdata); - } - } - - command_list_draw_repeated(commands, total_margins.left, linei, ' ', - total_width - total_margins.left); + uint32_t nlines = text_num_lines(buffer->text); + for (uint32_t linei = nlines - params->origin.line; linei < params->height; + ++linei) { + command_list_draw_repeated(params->commands, 0, linei, ' ', params->width); } - - // update the visual cursor position - to_relative(view, view->dot.line, view->dot.col, &rel_line, &rel_col); - uint32_t visual_col = visual_dot_col(view, view->dot.col); - - // TODO: fix this shit, should not need to add scroll_col back here - // only to subtract it in the function - to_relative(view, view->dot.line, visual_col + view->scroll.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); -} - -void buffer_view_scroll_down(struct buffer_view *view, uint32_t height) { - buffer_goto(view, view->dot.line + height, view->dot.col); - scroll(view, height, 0); } -void buffer_view_scroll_up(struct buffer_view *view, uint32_t height) { - buffer_goto(view, view->dot.line - height, view->dot.col); - scroll(view, -height, 0); -} - -void buffer_clear_text_properties(struct buffer *buffer) { - VEC_CLEAR(&buffer->text_properties); -} - -void buffer_add_text_property(struct buffer *buffer, - struct buffer_location start, - struct buffer_location end, +void buffer_add_text_property(struct buffer *buffer, struct location start, + struct location end, struct text_property property) { - struct text_property_entry entry = { - .start = start, - .end = end, - .property = property, - }; - VEC_PUSH(&buffer->text_properties, entry); -} - -bool buffer_location_is_between(struct buffer_location location, - struct buffer_location start, - struct buffer_location end) { - if (location.line >= start.line && location.line <= end.line) { - if (location.line == end.line && location.col <= end.col && - location.line == start.line && location.col >= start.col) { - // only one line - return true; - } else if (location.line == start.line && location.line != end.line && - location.col >= start.col) { - // we are on the first line - return true; - } else if (location.line == end.line && location.line != start.line && - location.col <= end.col) { - // we are on the last line - return true; - } else if (location.line != end.line && location.line != start.line) { - // we are on lines in between - return true; - } - } - return false; + text_add_property( + buffer->text, (struct location){.line = start.line, .col = start.col}, + (struct location){.line = end.line, .col = end.col}, property); } -void buffer_get_text_properties(struct buffer *buffer, - struct buffer_location location, +void buffer_get_text_properties(struct buffer *buffer, struct location location, struct text_property **properties, uint32_t max_nproperties, uint32_t *nproperties) { - uint32_t nres = 0; - VEC_FOR_EACH(&buffer->text_properties, struct text_property_entry * prop) { - if (buffer_location_is_between(location, prop->start, prop->end)) { - properties[nres] = &prop->property; - ++nres; + text_get_properties( + buffer->text, + (struct location){.line = location.line, .col = location.col}, properties, + max_nproperties, nproperties); +} - if (nres == max_nproperties) { - break; - } - } - } - *nproperties = nres; +void buffer_clear_text_properties(struct buffer *buffer) { + text_clear_properties(buffer->text); } diff --git a/src/dged/buffer.h b/src/dged/buffer.h index 28d9797..e29e3e1 100644 --- a/src/dged/buffer.h +++ b/src/dged/buffer.h @@ -1,3 +1,6 @@ +#ifndef _BUFFER_H +#define _BUFFER_H + #include #include #include @@ -5,81 +8,22 @@ #include "command.h" #include "lang.h" +#include "location.h" #include "text.h" #include "undo.h" #include "window.h" -struct keymap; struct command_list; -enum text_property_type { - TextProperty_Colors, -}; - -struct text_property_colors { - bool set_fg; - uint32_t fg; - bool set_bg; - uint32_t bg; -}; - -struct text_property { - enum text_property_type type; - union { - struct text_property_colors colors; - }; -}; - -/** - * Margins where buffer text should not be - */ -struct margin { - uint32_t left; - uint32_t right; - uint32_t top; - 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); - -typedef void (*line_render_empty_cb)(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; - line_render_empty_cb empty_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_view *view, struct command_list *commands, uint32_t width, - uint32_t height, uint64_t frame_time, void *userdata); +typedef void (*update_hook_cb)(struct buffer *buffer, uint32_t width, + uint32_t height, 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. + * minibuffer. */ struct update_hook { /** Callback function */ @@ -89,67 +33,9 @@ struct update_hook { void *userdata; }; -typedef void (*create_hook_cb)(struct buffer *buffer, void *userdata); - -/** - * A set of update hooks - */ -struct update_hooks { - /** The update hooks */ - struct update_hook hooks[32]; - - /** The number of update hooks */ - uint32_t nhooks; -}; - -struct buffer_location { - uint32_t line; - uint32_t col; -}; - -bool buffer_location_is_between(struct buffer_location location, - struct buffer_location start, - struct buffer_location end); - -struct match { - struct buffer_location begin; - struct buffer_location end; -}; - -struct buffer_view { - /** Location of dot (cursor) */ - struct buffer_location dot; - - /** Location of mark (where a selection starts) */ - struct buffer_location mark; - - /** Current buffer scroll position */ - struct buffer_location scroll; - - /** True if the start of a selection has been set */ - bool mark_set; - - /** Modeline buffer (may be NULL) */ - struct modeline *modeline; - - bool line_numbers; - - struct buffer *buffer; -}; - -struct buffer_view buffer_view_create(struct buffer *buffer, bool modeline, - bool line_numbers); -struct buffer_view buffer_view_clone(struct buffer_view *view); - -void buffer_view_scroll_down(struct buffer_view *view, uint32_t height); -void buffer_view_scroll_up(struct buffer_view *view, uint32_t height); - -void buffer_view_destroy(struct buffer_view *view); - -struct text_property_entry { - struct buffer_location start; - struct buffer_location end; - struct text_property property; +struct update_hook_entry { + uint32_t id; + struct update_hook hook; }; /** @@ -166,103 +52,411 @@ struct buffer { /** Associated filename, this is where the buffer will be saved to */ char *filename; + /** Time when buffer was last written to disk */ struct timespec last_write; /** Text data structure */ struct text *text; /** Buffer update hooks */ - struct update_hooks update_hooks; + VEC(struct update_hook_entry) update_hooks; /** Buffer undo stack */ struct undo_stack undo; + /** Buffer programming language */ + struct language lang; + /** Has this buffer been modified from when it was last saved */ bool modified; /** Can this buffer be changed */ bool readonly; +}; - /** Buffer programming language */ - struct language lang; +void buffer_static_init(); +void buffer_static_teardown(); - VEC(struct text_property_entry) text_properties; -}; +/** + * Create a new buffer. + * + * @param [in] name The buffer name. + * @returns A new buffer + */ +struct buffer buffer_create(const char *name); + +/** + * Create a new buffer from a file path. + * + * @param [in] path Path to the file to load into the new buffer. + * @returns A new buffer with @ref path loaded. + */ +struct buffer buffer_from_file(const char *path); + +/** + * Save buffer to the backing file. + * + * @param [in] buffer Buffer to save. + */ +void buffer_to_file(struct buffer *buffer); -struct buffer buffer_create(char *name); +/** + * Set path to backing file for buffer. + * + * The backing file is used when writing the buffer to a file. + * @param [in] buffer The buffer to set filename for. + * @param [in] filename The filename to use. It is required that this is a full + * path. + */ +void buffer_set_filename(struct buffer *buffer, const char *filename); + +/** + * Reload the buffer from disk. + * + * Reload the buffer from the backing file. + * @param [in] buffer The buffer to reload. + */ +void buffer_reload(struct buffer *buffer); + +/** + * Destroy the buffer. + * + * Destroy the buffer, releasing all associated resources. + * @param [in] buffer The buffer to destroy. + */ void buffer_destroy(struct buffer *buffer); -void buffer_static_init(); -void buffer_static_teardown(); +/** + * Add text to the buffer at the specified location. + * + * @param [in] buffer The buffer to add text to. + * @param [in] at The location to add text at. + * @param [in] text Pointer to the text bytes, not NULL-terminated. + * @param [in] nbytes Number of bytes in @ref text. + * + * @returns The location at the end of the inserted text. + */ +struct location buffer_add(struct buffer *buffer, struct location at, + uint8_t *text, uint32_t nbytes); + +/** + * Set the entire text contents of the buffer. + * + * @param [in] buffer The buffer to set text for. + * @param [in] text Pointer to the text bytes, not NULL-terminated. + * @param [in] nbytes Number of bytes in @ref text. + * + * @returns The location at the end of the inserted text + */ +struct location buffer_set_text(struct buffer *buffer, uint8_t *text, + uint32_t nbytes); + +/** + * Clear all text in the buffer + * + * @param [in] buffer The buffer to clear. + */ +void buffer_clear(struct buffer *buffer); -int buffer_add_text(struct buffer_view *view, uint8_t *text, uint32_t nbytes); -void buffer_set_text(struct buffer *buffer, uint8_t *text, uint32_t nbytes); -void buffer_clear(struct buffer_view *view); +/** + * Does buffer contain any text? + * + * @param [in] buffer The buffer to check. + * @returns True if the buffer is empty (has no text in it), false otherwise. + */ bool buffer_is_empty(struct buffer *buffer); + +/** + * Has the buffer been modified since it was last retrieved from/saved to disk? + * + * @param [in] buffer The buffer to examine. + * @returns True if the buffer has been modified, false otherwise. + */ bool buffer_is_modified(struct buffer *buffer); + +/** + * Is this buffer read-only? + * + * @param [in] buffer The buffer to examine. + * @returns True if the buffer is read-only (cannot be modified), false + * otherwise. + */ bool buffer_is_readonly(struct buffer *buffer); + +/** + * Set the read-only status for the buffer. + * + * @param [in] buffer The buffer to set read-only for. + * @param [in] readonly If true, the buffer is set to read-only, otherwise it is + * set to writable. + */ void buffer_set_readonly(struct buffer *buffer, bool readonly); + +/** + * Is the buffer backed by a file on disk? + * + * @param [in] buffer The buffer to examine. + * @returns True if the buffer has a path to a file on disk to use as backing + * file, false otherwise. Note that this function returns true even if the + * buffer has never been written to the backing file. + */ bool buffer_is_backed(struct buffer *buffer); -void buffer_kill_line(struct buffer_view *view); -void buffer_forward_delete_char(struct buffer_view *view); -void buffer_forward_delete_word(struct buffer_view *view); -void buffer_backward_delete_char(struct buffer_view *view); -void buffer_backward_delete_word(struct buffer_view *view); -void buffer_backward_char(struct buffer_view *view); -void buffer_backward_word(struct buffer_view *view); -void buffer_forward_char(struct buffer_view *view); -void buffer_forward_word(struct buffer_view *view); -void buffer_backward_line(struct buffer_view *view); -void buffer_forward_line(struct buffer_view *view); -void buffer_end_of_line(struct buffer_view *view); -void buffer_beginning_of_line(struct buffer_view *view); -void buffer_newline(struct buffer_view *view); -void buffer_indent(struct buffer_view *view); - -void buffer_undo(struct buffer_view *view); - -void buffer_goto_beginning(struct buffer_view *view); -void buffer_goto_end(struct buffer_view *view); -void buffer_goto(struct buffer_view *view, uint32_t line, uint32_t col); +/** + * Get location of previous character in buffer. + * + * @param [in] buffer The buffer to use. + * @param [in] dot The location to start from. + * @returns The location in front of the previous char given @ref dot. + */ +struct location buffer_previous_char(struct buffer *buffer, + struct location dot); + +/** + * Get location of previous word in buffer. + * + * @param [in] buffer The buffer to look in. + * @param [in] dot The location to start from. + * @returns The location at the start of the previous word, given @ref dot. + */ +struct location buffer_previous_word(struct buffer *buffer, + struct location dot); + +/** + * Get location of previous line. + * + * @param [in] buffer The buffer to look in. + * @param [in] dot The location to start from. + * @returns The location at the start of the line above the current one (the one + * @ref dot is on). If @ref dot is on the first line, the location (0, 0) is + * returned. + */ +struct location buffer_previous_line(struct buffer *buffer, + struct location dot); + +/** + * Get location of next character in buffer. + * + * @param [in] buffer The buffer to use. + * @param [in] dot The location to start from. + * @returns The location in front of the next char given @ref dot. + */ +struct location buffer_next_char(struct buffer *buffer, struct location dot); + +/** + * Get location of next word in buffer. + * + * @param [in] buffer The buffer to look in. + * @param [in] dot The location to start from. + * @returns The location at the start of the next word, given @ref dot. + */ +struct location buffer_next_word(struct buffer *buffer, struct location dot); + +/** + * Get location of next line. + * + * @param [in] buffer The buffer to look in. + * @param [in] dot The location to start from. + * @returns The location at the start of the line above the current one (the one + * @ref dot is on). If @ref dot is on the last line, the last location in the + * buffer is returned. + */ +struct location buffer_next_line(struct buffer *buffer, struct location dot); + +/** + * Clamp a buffer position to the boundaries of the buffer. + * + * Note that both @ref line and @ref col can be negative or bigger than the + * buffer. + * + * @param [in] buffer The buffer to use for clamping. + * @param [in] line The line position to clamp. + * @param [in] col The column position to clamp. + * @returns The closest position inside the buffer matching (line, col). + */ +struct location buffer_clamp(struct buffer *buffer, int64_t line, int64_t col); + +/** + * Get location of buffer end. + * + * @param [in] buffer The buffer to use. + * @returns the position after the last character in @ref buffer. + */ +struct location buffer_end(struct buffer *buffer); + +/** + * Get the number of lines in the buffer + * + * @param [in] buffer The buffer to use. + * @returns The number of lines in @ref buffer. + */ +uint32_t buffer_num_lines(struct buffer *buffer); + +/** + * Get the number of chars in a given line in buffer. + * + * @param [in] buffer The buffer to use. + * @param [in] line The line to get number of chars for. + * @returns The number of chars in @ref line. + */ +uint32_t buffer_num_chars(struct buffer *buffer, uint32_t line); + +/** + * Insert a newline in the buffer. + * + * @param [in] buffer The buffer to insert in. + * @param [in] at The point to insert at. + * @returns The location at the start of the new line. + */ +struct location buffer_newline(struct buffer *buffer, struct location at); + +/** + * Insert indentation in the buffer. + * + * @param [in] buffer The buffer to indent in. + * @param [in] at The location to insert indentation at. + * @returns The position after indenting. + */ +struct location buffer_indent(struct buffer *buffer, struct location at); + +/** + * Undo the last operation in the buffer. + * + * @param [in] buffer The buffer to undo in. + * @param [in] dot The point to undo at. + * @returns The position in the buffer after undo. + */ +struct location buffer_undo(struct buffer *buffer, struct location dot); +/** + * Search for a substring in the buffer. + * + * @param [in] buffer The buffer to search in. + * @param [in] pattern The substring to search for. + * @param [out] matches The pointer passed in is modified to point at the + * resulting matches. This pointer should be freed using @ref free. + * @param [nmatches] nmatches The pointer passed in is modified to point at the + * number of resulting matches. + */ void buffer_find(struct buffer *buffer, const char *pattern, - struct match **matches, uint32_t *nmatches); + struct region **matches, uint32_t *nmatches); -void buffer_set_mark(struct buffer_view *view); -void buffer_clear_mark(struct buffer_view *view); -void buffer_set_mark_at(struct buffer_view *view, uint32_t line, uint32_t col); +/** + * Copy a region in the buffer into the kill ring. + * + * @param [in] buffer The buffer to copy from. + * @param [in] region The region to copy. + * @returns The position in the buffer after the copy. + */ +struct location buffer_copy(struct buffer *buffer, struct region region); -void buffer_copy(struct buffer_view *view); -void buffer_paste(struct buffer_view *view); -void buffer_paste_older(struct buffer_view *view); -void buffer_cut(struct buffer_view *view); +/** + * Cut a region in the buffer into the kill ring. + * + * @param [in] buffer The buffer to cut from. + * @param [in] region The region to cut. + * @returns The position in the buffer after the cut. + */ +struct location buffer_cut(struct buffer *buffer, struct region region); -void buffer_clear_text_properties(struct buffer *buffer); +/** + * Delete a region in the buffer without putting it into the kill ring. + * + * @param [in] buffer The buffer to delete from. + * @param [in] region The region to delete. + * @returns The position in the buffer after the delete. + */ +struct location buffer_delete(struct buffer *buffer, struct region region); + +/** + * Paste from the kill ring into the buffer. + * + * @param [in] buffer Buffer to paste in. + * @param [in] at Location to paste at. + * @returns The location in the buffer after the paste. + */ +struct location buffer_paste(struct buffer *buffer, struct location at); + +/** + * Paste the next older entry from the kill ring into the buffer. + * + * @param [in] buffer Buffer to paste in. + * @param [in] at Location to paste at. + * @returns The location in the buffer after the paste. + */ +struct location buffer_paste_older(struct buffer *buffer, struct location at); -void buffer_add_text_property(struct buffer *buffer, - struct buffer_location start, - struct buffer_location end, +/** + * Get one line from the buffer. + * + * @param buffer The buffer to get the line from. + * @param line Line number in the buffer. + * @returns A text chunk describing the line. Note that if the line number is + * greater than the number of lines, the @ref text_chunk will be empty. + */ +struct text_chunk buffer_line(struct buffer *buffer, uint32_t line); + +void buffer_add_text_property(struct buffer *buffer, struct location start, + struct location end, struct text_property property); -void buffer_get_text_properties(struct buffer *buffer, - struct buffer_location location, +void buffer_get_text_properties(struct buffer *buffer, struct location location, struct text_property **properties, uint32_t max_nproperties, uint32_t *nproperties); -struct text_chunk buffer_get_line(struct buffer *buffer, uint32_t line); +void buffer_clear_text_properties(struct buffer *buffer); +/** + * Add a buffer update hook. + * + * @param [in] buffer The buffer to add the hook to. + * @param [in] hook The update hook callback. + * @param [in] userdata Data that is passed unmodified to the update hook. + * @returns The hook id. + */ uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook, void *userdata); +/** Buffer create hook callback function */ +typedef void (*create_hook_cb)(struct buffer *buffer, void *userdata); + +/** + * Add a buffer create hook. + * + * @param [in] hook Create hook callback. + * @param [in] userdata Pointer to data that is passed unmodified to the update + * hook. + * @returns The hook id. + */ uint32_t buffer_add_create_hook(create_hook_cb hook, void *userdata); -struct buffer buffer_from_file(char *filename); -void buffer_to_file(struct buffer *buffer); -void buffer_write_to(struct buffer *buffer, const char *filename); -void buffer_reload(struct buffer *buffer); +/** + * Parameters for updating a buffer. + */ +struct buffer_update_params { + + /** Command list to add rendering commands for the buffer */ + struct command_list *commands; + + /** Where is the upper left corner of the buffer */ + struct location origin; + + /** Window width for this buffer, -1 if it is not in a window */ + uint32_t width; + + /** Window height for this buffer, -1 if it is not in a window */ + uint32_t height; +}; + +/** + * Update a buffer. + * + * @param [in] buffer The buffer to update. + * @param [inout] params The parameters for the update. The @ref commands field + * in @ref params will be modified with the rendering commands needed for this + * buffer. + */ +void buffer_update(struct buffer *buffer, struct buffer_update_params *params); -void buffer_update(struct buffer_view *view, uint32_t window_id, uint32_t width, - uint32_t height, struct command_list *commands, - uint64_t frame_time, uint32_t *relline, uint32_t *relcol); +#endif diff --git a/src/dged/buffer_view.c b/src/dged/buffer_view.c new file mode 100644 index 0000000..2c69161 --- /dev/null +++ b/src/dged/buffer_view.c @@ -0,0 +1,418 @@ +#include + +#include "buffer.h" +#include "buffer_view.h" +#include "display.h" + +struct modeline { + uint8_t *buffer; + uint32_t sz; +}; + +static bool maybe_delete_region(struct buffer_view *view) { + struct region reg = region_new(view->dot, view->mark); + if (view->mark_set && region_has_size(reg)) { + buffer_delete(view->buffer, reg); + buffer_view_clear_mark(view); + view->dot = reg.begin; + return true; + } + + return false; +} + +struct buffer_view buffer_view_create(struct buffer *buffer, bool modeline, + bool line_numbers) { + struct buffer_view v = (struct buffer_view){ + .dot = (struct location){.line = 0, .col = 0}, + .mark = (struct location){.line = 0, .col = 0}, + .mark_set = false, + .scroll = (struct location){.line = 0, .col = 0}, + .buffer = buffer, + .modeline = NULL, + .line_numbers = line_numbers, + .fringe_width = 0, + }; + + if (modeline) { + v.modeline = calloc(1, sizeof(struct modeline)); + v.modeline->buffer = malloc(1024); + v.modeline->sz = 1024; + v.modeline->buffer[0] = '\0'; + } + + return v; +} + +struct buffer_view buffer_view_clone(const struct buffer_view *view) { + struct buffer_view c = { + .dot = view->dot, + .mark = view->mark, + .mark_set = view->mark_set, + .scroll = view->scroll, + .buffer = view->buffer, + .modeline = NULL, + .line_numbers = view->line_numbers, + }; + + if (view->modeline) { + c.modeline = calloc(1, sizeof(struct modeline)); + c.modeline->buffer = malloc(view->modeline->sz); + memcpy(c.modeline->buffer, view->modeline->buffer, view->modeline->sz); + } + + return c; +} + +void buffer_view_destroy(struct buffer_view *view) { + if (view->modeline != NULL) { + free(view->modeline->buffer); + free(view->modeline); + } +} + +void buffer_view_add(struct buffer_view *view, uint8_t *txt, uint32_t nbytes) { + maybe_delete_region(view); + view->dot = buffer_add(view->buffer, view->dot, txt, nbytes); +} + +void buffer_view_goto_beginning(struct buffer_view *view) { + view->dot = (struct location){.line = 1, .col = 0}; +} + +void buffer_view_goto_end(struct buffer_view *view) { + view->dot = buffer_end(view->buffer); +} + +void buffer_view_goto(struct buffer_view *view, struct location to) { + view->dot = buffer_clamp(view->buffer, (int64_t)to.line, (int64_t)to.col); +} + +void buffer_view_forward_char(struct buffer_view *view) { + view->dot = buffer_next_char(view->buffer, view->dot); +} + +void buffer_view_backward_char(struct buffer_view *view) { + view->dot = buffer_previous_char(view->buffer, view->dot); +} + +void buffer_view_forward_word(struct buffer_view *view) { + view->dot = buffer_next_word(view->buffer, view->dot); +} + +void buffer_view_backward_word(struct buffer_view *view) { + view->dot = buffer_previous_word(view->buffer, view->dot); +} + +void buffer_view_forward_line(struct buffer_view *view) { + view->dot = buffer_next_line(view->buffer, view->dot); +} + +void buffer_view_backward_line(struct buffer_view *view) { + view->dot = buffer_previous_line(view->buffer, view->dot); +} + +void buffer_view_forward_nlines(struct buffer_view *view, uint32_t nlines) { + view->dot = buffer_clamp(view->buffer, (int64_t)view->dot.line + nlines, + (int64_t)view->dot.col); +} + +void buffer_view_backward_nlines(struct buffer_view *view, uint32_t nlines) { + view->dot = buffer_clamp(view->buffer, (int64_t)view->dot.line - nlines, + (int64_t)view->dot.col); +} + +void buffer_view_goto_end_of_line(struct buffer_view *view) { + view->dot.col = buffer_num_chars(view->buffer, view->dot.line); +} + +void buffer_view_goto_beginning_of_line(struct buffer_view *view) { + view->dot.col = 0; +} + +void buffer_view_newline(struct buffer_view *view) { + view->dot = buffer_newline(view->buffer, view->dot); +} + +void buffer_view_indent(struct buffer_view *view) { + view->dot = buffer_indent(view->buffer, view->dot); +} + +void buffer_view_copy(struct buffer_view *view) { + if (!view->mark_set) { + return; + } + + view->dot = buffer_copy(view->buffer, region_new(view->dot, view->mark)); + buffer_view_clear_mark(view); +} + +void buffer_view_cut(struct buffer_view *view) { + if (!view->mark_set) { + return; + } + + view->dot = buffer_cut(view->buffer, region_new(view->dot, view->mark)); + buffer_view_clear_mark(view); +} + +void buffer_view_paste(struct buffer_view *view) { + maybe_delete_region(view); + view->dot = buffer_paste(view->buffer, view->dot); +} + +void buffer_view_paste_older(struct buffer_view *view) { + view->dot = buffer_paste_older(view->buffer, view->dot); +} + +void buffer_view_forward_delete_char(struct buffer_view *view) { + if (maybe_delete_region(view)) { + return; + } + + view->dot = buffer_delete( + view->buffer, + region_new(view->dot, buffer_next_char(view->buffer, view->dot))); +} + +void buffer_view_backward_delete_char(struct buffer_view *view) { + if (maybe_delete_region(view)) { + return; + } + + view->dot = buffer_delete( + view->buffer, + region_new(buffer_previous_char(view->buffer, view->dot), view->dot)); +} + +void buffer_view_forward_delete_word(struct buffer_view *view) { + if (maybe_delete_region(view)) { + return; + } + + view->dot = buffer_delete( + view->buffer, + region_new(view->dot, buffer_next_word(view->buffer, view->dot))); +} + +void buffer_view_backward_delete_word(struct buffer_view *view) { + if (maybe_delete_region(view)) { + return; + } + + view->dot = buffer_delete( + view->buffer, + region_new(buffer_previous_word(view->buffer, view->dot), view->dot)); +} + +void buffer_view_kill_line(struct buffer_view *view) { + uint32_t nchars = + buffer_num_chars(view->buffer, view->dot.line) - view->dot.col; + if (nchars == 0) { + nchars = 1; + } + + struct region reg = region_new(view->dot, (struct location){ + .line = view->dot.line, + .col = view->dot.col + nchars, + }); + + buffer_cut(view->buffer, reg); +} + +void buffer_view_set_mark(struct buffer_view *view) { + buffer_view_set_mark_at(view, view->dot); +} + +void buffer_view_clear_mark(struct buffer_view *view) { + view->mark_set = false; +} + +void buffer_view_set_mark_at(struct buffer_view *view, struct location mark) { + view->mark = mark; + view->mark_set = true; +} + +struct location buffer_view_dot_to_relative(struct buffer_view *view) { + return (struct location){ + .line = view->dot.line - view->scroll.line, + .col = view->dot.col - view->scroll.col + view->fringe_width, + }; +} + +void buffer_view_undo(struct buffer_view *view) { + view->dot = buffer_undo(view->buffer, view->dot); +} + +static uint32_t longest_linenum(struct buffer_view *view) { + uint32_t total_lines = buffer_num_lines(view->buffer); + 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; + } + + return longest_nchars; +} + +static uint32_t render_line_numbers(struct buffer_view *view, + struct command_list *commands, + uint32_t height) { + uint32_t longest_nchars = longest_linenum(view); + static char buf[16]; + + uint32_t nlines_buf = buffer_num_lines(view->buffer); + uint32_t line = view->scroll.line; + uint32_t relline = 0; + + for (; relline < height && line < nlines_buf; ++line, ++relline) { + command_list_set_index_color_bg(commands, 8); + command_list_set_index_color_fg(commands, line == view->dot.line ? 15 : 7); + uint32_t chars = snprintf(buf, 16, "%*d", longest_nchars + 1, line + 1); + command_list_draw_text_copy(commands, 0, relline, (uint8_t *)buf, chars); + command_list_reset_color(commands); + command_list_draw_repeated(commands, longest_nchars + 1, relline, ' ', 1); + } + + for (; relline < height; ++relline) { + command_list_set_index_color_bg(commands, 8); + command_list_set_index_color_fg(commands, 7); + command_list_draw_repeated(commands, 0, relline, ' ', longest_nchars + 1); + command_list_reset_color(commands); + command_list_draw_repeated(commands, longest_nchars + 1, relline, ' ', 1); + } + + return longest_nchars + 2; +} + +static void render_modeline(struct modeline *modeline, struct buffer_view *view, + struct command_list *commands, uint32_t window_id, + uint32_t width, uint32_t height, + 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; + + time_t now = time(NULL); + struct tm *lt = localtime(&now); + char left[128], right[128]; + + snprintf(left, 128, " %c%c %d:%-16s (%d, %d) (%s)", + view->buffer->modified ? '*' : '-', + view->buffer->readonly ? '%' : '-', window_id, view->buffer->name, + view->dot.line + 1, view->dot.col, view->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); + + if (strcmp(buf, (char *)modeline->buffer) != 0) { + modeline->buffer = realloc(modeline->buffer, width * 4); + modeline->sz = 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); +} + +void buffer_view_update(struct buffer_view *view, + struct buffer_view_update_params *params) { + + uint32_t height = params->height; + uint32_t width = params->width; + + // render modeline + uint32_t modeline_height = 0; + if (view->modeline != NULL) { + modeline_height = 1; + render_modeline(view->modeline, view, params->commands, params->window_id, + params->width, params->height, params->frame_time); + } + + height -= modeline_height; + + // render line numbers + uint32_t linum_width = 0; + if (view->line_numbers) { + linum_width = render_line_numbers(view, params->commands, height); + } + + width -= linum_width; + view->fringe_width = linum_width; + + /* Make sure the dot is always inside buffer limits. + * It can be outside for example if the text is changed elsewhere. */ + view->dot = buffer_clamp(view->buffer, (int64_t)view->dot.line, + (int64_t)view->dot.col); + + // update scroll position if needed + if (view->dot.line >= view->scroll.line + height || view->dot.line < view->scroll.line) { + // put dot in the middle, height-wise + view->scroll.line = buffer_clamp(view->buffer, (int64_t)view->dot.line - params->height / 2, + 0).line; + } + + if (view->dot.col >= view->scroll.col + width || view->dot.col < view->scroll.col) { + view->scroll.col = buffer_clamp(view->buffer, view->dot.line, view->dot.col).col; + } + + // color region + if (view->mark_set) { + struct region reg = region_new(view->dot, view->mark); + if (region_has_size(reg)) { + buffer_add_text_property(view->buffer, reg.begin, reg.end, + (struct text_property){ + .type = TextProperty_Colors, + .colors = + (struct text_property_colors){ + .set_bg = true, + .bg = 5, + .set_fg = false, + }, + }); + } + } + + // update buffer + struct command_list *buf_cmds = command_list_create( + width * height, params->frame_alloc, params->window_x + linum_width, + params->window_y, view->buffer->name); + struct buffer_update_params bufparams = { + .commands = buf_cmds, + .origin = view->scroll, + .width = width, + .height = height, + }; + buffer_update(view->buffer, &bufparams); + + // draw buffer commands nested inside this command list + command_list_draw_command_list(params->commands, buf_cmds); + buffer_clear_text_properties(view->buffer); +} diff --git a/src/dged/buffer_view.h b/src/dged/buffer_view.h new file mode 100644 index 0000000..0002b95 --- /dev/null +++ b/src/dged/buffer_view.h @@ -0,0 +1,100 @@ +#ifndef _BUFFER_VIEW_H +#define _BUFFER_VIEW_H + +#include + +#include "location.h" + +struct buffer; + +/** + * A view of a buffer. + * + * This contains the mark and dot as well as the scroll position for a buffer. + */ +struct buffer_view { + /** Location of dot (cursor) */ + struct location dot; + + /** Location of mark (where a selection starts) */ + struct location mark; + + /** Current buffer scroll position */ + struct location scroll; + + /** Pointer to the actual buffer */ + struct buffer *buffer; + + /** Modeline buffer (may be NULL) */ + struct modeline *modeline; + + /** Current left fringe size */ + uint32_t fringe_width; + + /** Does the buffer show line numbers */ + bool line_numbers; + + /** True if the start of a selection has been set */ + bool mark_set; +}; + +struct buffer_view buffer_view_create(struct buffer *buffer, bool modeline, + bool line_numbers); +struct buffer_view buffer_view_clone(const struct buffer_view *view); +void buffer_view_destroy(struct buffer_view *view); + +void buffer_view_add(struct buffer_view *view, uint8_t *txt, uint32_t nbytes); + +void buffer_view_goto_beginning(struct buffer_view *view); +void buffer_view_goto_end(struct buffer_view *view); +void buffer_view_goto(struct buffer_view *view, struct location to); +void buffer_view_goto_end_of_line(struct buffer_view *view); +void buffer_view_goto_beginning_of_line(struct buffer_view *view); + +void buffer_view_forward_char(struct buffer_view *view); +void buffer_view_backward_char(struct buffer_view *view); +void buffer_view_forward_word(struct buffer_view *view); +void buffer_view_backward_word(struct buffer_view *view); +void buffer_view_forward_line(struct buffer_view *view); +void buffer_view_backward_line(struct buffer_view *view); +void buffer_view_forward_nlines(struct buffer_view *view, uint32_t nlines); +void buffer_view_backward_nlines(struct buffer_view *view, uint32_t nlines); + +void buffer_view_forward_delete_char(struct buffer_view *view); +void buffer_view_backward_delete_char(struct buffer_view *view); +void buffer_view_forward_delete_word(struct buffer_view *view); +void buffer_view_backward_delete_word(struct buffer_view *view); + +void buffer_view_kill_line(struct buffer_view *view); + +void buffer_view_newline(struct buffer_view *view); +void buffer_view_indent(struct buffer_view *view); + +void buffer_view_copy(struct buffer_view *view); +void buffer_view_cut(struct buffer_view *view); +void buffer_view_paste(struct buffer_view *view); +void buffer_view_paste_older(struct buffer_view *view); + +void buffer_view_set_mark(struct buffer_view *view); +void buffer_view_clear_mark(struct buffer_view *view); +void buffer_view_set_mark_at(struct buffer_view *view, struct location mark); + +struct location buffer_view_dot_to_relative(struct buffer_view *view); + +void buffer_view_undo(struct buffer_view *view); + +struct buffer_view_update_params { + struct command_list *commands; + void *(*frame_alloc)(size_t); + uint32_t window_id; + int64_t frame_time; + uint32_t width; + uint32_t height; + uint32_t window_x; + uint32_t window_y; +}; + +void buffer_view_update(struct buffer_view *view, + struct buffer_view_update_params *params); + +#endif diff --git a/src/dged/display.c b/src/dged/display.c index cf2e5d5..b779d5f 100644 --- a/src/dged/display.c +++ b/src/dged/display.c @@ -2,6 +2,7 @@ #include "display.h" #include "buffer.h" +#include "utf8.h" #include #include @@ -27,6 +28,7 @@ enum render_cmd_type { RenderCommand_Repeat = 2, RenderCommand_ClearFormat = 3, RenderCommand_SetShowWhitespace = 4, + RenderCommand_DrawList = 5, }; struct render_command { @@ -36,6 +38,7 @@ struct render_command { struct push_fmt_cmd *push_fmt; struct repeat_cmd *repeat; struct show_ws_cmd *show_ws; + struct draw_list_cmd *draw_list; }; }; @@ -55,7 +58,7 @@ struct push_fmt_cmd { struct repeat_cmd { uint32_t col; uint32_t row; - uint8_t c; + int32_t c; uint32_t nrepeat; }; @@ -63,6 +66,10 @@ struct show_ws_cmd { bool show; }; +struct draw_list_cmd { + struct command_list *list; +}; + struct command_list { struct render_command *cmds; uint64_t ncmds; @@ -122,26 +129,25 @@ void display_destroy(struct display *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) { +void putch(uint8_t c) { if (c != '\r') { putc(c, stdout); } } -void putbyte_ws(uint8_t c, bool show_whitespace) { +void putch_ws(uint8_t c, bool show_whitespace) { if (show_whitespace && c == '\t') { fputs("\x1b[90m → \x1b[39m", stdout); } else if (show_whitespace && c == ' ') { fputs("\x1b[90m·\x1b[39m", stdout); } else { - putbyte(c); + putch(c); } } void putbytes(uint8_t *line_bytes, uint32_t line_length, bool show_whitespace) { for (uint32_t bytei = 0; bytei < line_length; ++bytei) { - uint8_t byte = line_bytes[bytei]; - putbyte_ws(byte, show_whitespace); + putch_ws(line_bytes[bytei], show_whitespace); } } @@ -150,20 +156,20 @@ void put_ansiparm(int n) { if (q != 0) { int r = q / 10; if (r != 0) { - putbyte((r % 10) + '0'); + putch((r % 10) + '0'); } - putbyte((q % 10) + '0'); + putch((q % 10) + '0'); } - putbyte((n % 10) + '0'); + putch((n % 10) + '0'); } void display_move_cursor(struct display *display, uint32_t row, uint32_t col) { - putbyte(ESC); - putbyte('['); + putch(ESC); + putch('['); put_ansiparm(row + 1); - putbyte(';'); + putch(';'); put_ansiparm(col + 1); - putbyte('H'); + putch('H'); } void display_clear(struct display *display) { @@ -226,6 +232,11 @@ struct render_command *add_command(struct command_list *list, break; case RenderCommand_ClearFormat: break; + case RenderCommand_DrawList: + cmd->draw_list = l->allocator(sizeof(struct draw_list_cmd)); + break; + default: + assert(false); } ++l->ncmds; @@ -251,7 +262,7 @@ void command_list_draw_text_copy(struct command_list *list, uint32_t col, } void command_list_draw_repeated(struct command_list *list, uint32_t col, - uint32_t row, uint8_t c, uint32_t nrepeat) { + uint32_t row, int32_t c, uint32_t nrepeat) { struct repeat_cmd *cmd = add_command(list, RenderCommand_Repeat)->repeat; cmd->col = col; cmd->row = row; @@ -259,6 +270,13 @@ void command_list_draw_repeated(struct command_list *list, uint32_t col, cmd->nrepeat = nrepeat; } +void command_list_draw_command_list(struct command_list *list, + struct command_list *to_draw) { + struct draw_list_cmd *cmd = + add_command(list, RenderCommand_DrawList)->draw_list; + cmd->list = to_draw; +} + void command_list_set_index_color_fg(struct command_list *list, uint8_t color_idx) { struct push_fmt_cmd *cmd = @@ -329,7 +347,7 @@ void display_render(struct display *display, display_move_cursor(display, txt_cmd->row + cl->yoffset, txt_cmd->col + cl->xoffset); putbytes(fmt_stack, fmt_stack_len, false); - putbyte('m'); + putch('m'); putbytes(txt_cmd->data, txt_cmd->len, show_whitespace_state); break; } @@ -339,17 +357,10 @@ void display_render(struct display *display, display_move_cursor(display, repeat_cmd->row + cl->yoffset, repeat_cmd->col + cl->xoffset); putbytes(fmt_stack, fmt_stack_len, false); - putbyte('m'); - if (show_whitespace_state) { - for (uint32_t i = 0; i < repeat_cmd->nrepeat; ++i) { - putbyte_ws(repeat_cmd->c, show_whitespace_state); - } - } else { - char *buf = malloc(repeat_cmd->nrepeat + 1); - memset(buf, repeat_cmd->c, repeat_cmd->nrepeat); - buf[repeat_cmd->nrepeat] = '\0'; - fputs(buf, stdout); - free(buf); + putch('m'); + uint32_t nbytes = utf8_nbytes((uint8_t *)&repeat_cmd->c, 4, 1); + for (uint32_t i = 0; i < repeat_cmd->nrepeat; ++i) { + putbytes((uint8_t *)&repeat_cmd->c, nbytes, show_whitespace_state); } break; } @@ -372,6 +383,10 @@ void display_render(struct display *display, case RenderCommand_SetShowWhitespace: show_whitespace_state = cmd->show_ws->show; break; + + case RenderCommand_DrawList: + display_render(display, cmd->draw_list->list); + break; } } cl = cl->next_list; @@ -379,21 +394,21 @@ void display_render(struct display *display, } void hide_cursor() { - putbyte(ESC); - putbyte('['); - putbyte('?'); - putbyte('2'); - putbyte('5'); - putbyte('l'); + putch(ESC); + putch('['); + putch('?'); + putch('2'); + putch('5'); + putch('l'); } void show_cursor() { - putbyte(ESC); - putbyte('['); - putbyte('?'); - putbyte('2'); - putbyte('5'); - putbyte('h'); + putch(ESC); + putch('['); + putch('?'); + putch('2'); + putch('5'); + putch('h'); } void display_begin_render(struct display *display) { hide_cursor(); } diff --git a/src/dged/display.h b/src/dged/display.h index 14dd246..2fc807b 100644 --- a/src/dged/display.h +++ b/src/dged/display.h @@ -205,8 +205,11 @@ void command_list_draw_text_copy(struct command_list *list, uint32_t col, * @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 c Character 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); + uint32_t row, int32_t c, uint32_t nrepeat); + +void command_list_draw_command_list(struct command_list *list, + struct command_list *to_draw); diff --git a/src/dged/location.c b/src/dged/location.c new file mode 100644 index 0000000..cac0333 --- /dev/null +++ b/src/dged/location.c @@ -0,0 +1,68 @@ +#include "location.h" + +bool location_is_between(struct location location, struct location start, + struct location end) { + if (location.line >= start.line && location.line <= end.line) { + if (location.line == end.line && location.col <= end.col && + location.line == start.line && location.col >= start.col) { + // only one line + return true; + } else if (location.line == start.line && location.line != end.line && + location.col >= start.col) { + // we are on the first line + return true; + } else if (location.line == end.line && location.line != start.line && + location.col <= end.col) { + // we are on the last line + return true; + } else if (location.line != end.line && location.line != start.line) { + // we are on lines in between + return true; + } + } + return false; +} + +int location_compare(struct location l1, struct location l2) { + if (l1.line < l2.line) { + return -1; + } else if (l1.line > l2.line) { + return 1; + } else { + if (l1.col < l2.col) { + return -1; + } else if (l1.col > l2.col) { + return 1; + } else { + return 0; + } + } +} + +struct region region_new(struct location begin, struct location end) { + struct region reg = {.begin = begin, .end = end}; + + if (end.line < begin.line || + (end.line == begin.line && end.col < begin.col)) { + reg.begin = end; + reg.end = begin; + } + + return reg; +} + +bool region_has_size(struct region region) { + return region.end.line != region.begin.line || + (region.end.line == region.begin.line && + region.begin.col != region.end.col); +} + +bool region_is_inside(struct region region, struct location location) { + return location_is_between(location, region.begin, region.end); +} + +bool region_is_inside_rect(struct region region, struct location location) { + return location.line >= region.begin.line && + location.line <= region.end.line && location.col >= region.begin.col && + location.col <= region.end.col; +} diff --git a/src/dged/location.h b/src/dged/location.h new file mode 100644 index 0000000..d11dd94 --- /dev/null +++ b/src/dged/location.h @@ -0,0 +1,81 @@ +#ifndef _LOCATION_H +#define _LOCATION_H + +#include +#include + +/** + * A location inside text. + */ +struct location { + /** The line in the text (0..) */ + uint32_t line; + + /** The column in the text (0..) */ + uint32_t col; +}; + +/** + * Is the location between two other locations. + * + * @param [in] location The location to test. + * @param [in] l1 The first location. + * @param [in] l2 The other location. + * @returns True if @ref location is between @ref l1 and @ref l2. + */ +bool location_is_between(struct location location, struct location l1, + struct location l2); + +/** + * Compare two locations. + * + * @param [in] l1 The first location. + * @param [in] l2 The second location. + * + * @returns -1 if @ref l1 is before @ref l2, 0 if @ref l1 is equal to @ref l2 + * and +1 if @ref l1 is after @ref l2. + */ +int location_compare(struct location l1, struct location l2); + +/** + * A region (area) in text. + */ +struct region { + /** The top left corner of the region. */ + struct location begin; + + /** The bottom right corner of the region. */ + struct location end; +}; + +/** + * Create a new region. + * + * Note that if begin is after end, their order will be reversed. + * + * @param [in] begin The point in the text where this region begins. + * @param [in] end The point in the text where this region ends. + * @returns a new region. + */ +struct region region_new(struct location begin, struct location end); + +/** + * Is this region covering anything? + * + * @param [in] region The region to check. + * @returns True if the region has a size > 0. + */ +bool region_has_size(struct region region); + +/** + * Is the location inside the region? + * + * @param [in] region The region to test. + * @param [in] location The location to test. + * @returns True if @ref location is inside @ref region. + */ +bool region_is_inside(struct region region, struct location location); + +bool region_is_inside_rect(struct region region, struct location location); + +#endif diff --git a/src/dged/minibuffer.c b/src/dged/minibuffer.c index 3c5a291..d31d634 100644 --- a/src/dged/minibuffer.c +++ b/src/dged/minibuffer.c @@ -17,17 +17,24 @@ static struct minibuffer { struct command_ctx prompt_command_ctx; bool prompt_active; bool clear; + struct window *prev_window; void (*update_callback)(void *); void *update_callback_userdata; } g_minibuffer = {0}; -void draw_prompt(struct command_list *commands, void *userdata) { +uint32_t minibuffer_draw_prompt(struct command_list *commands) { + if (!g_minibuffer.prompt_active) { + return 0; + } + uint32_t len = strlen(g_minibuffer.prompt); command_list_set_index_color_fg(commands, 4); command_list_draw_text(commands, 0, 0, (uint8_t *)g_minibuffer.prompt, len); command_list_reset_color(commands); + + return len; } int32_t minibuffer_execute() { @@ -69,30 +76,20 @@ int32_t minibuffer_execute() { } } -struct update_hook_result update(struct buffer_view *view, - struct command_list *commands, uint32_t width, - uint32_t height, uint64_t frame_time, - void *userdata) { +void update(struct buffer *buffer, uint32_t width, uint32_t height, + void *userdata) { struct timespec current; struct minibuffer *mb = (struct minibuffer *)userdata; clock_gettime(CLOCK_MONOTONIC, ¤t); if ((!mb->prompt_active && current.tv_sec >= mb->expires.tv_sec) || mb->clear) { - buffer_clear(view); + buffer_clear(buffer); mb->clear = false; } - struct update_hook_result res = {0}; - if (mb->prompt_active) { - res.margins.left = strlen(mb->prompt); - draw_prompt(commands, NULL); - } - if (mb->update_callback != NULL) { mb->update_callback(mb->update_callback_userdata); } - - return res; } void minibuffer_init(struct buffer *buffer) { @@ -128,7 +125,7 @@ void minibuffer_destroy() { } struct text_chunk minibuffer_content() { - return buffer_get_line(g_minibuffer.buffer, 0); + return buffer_line(g_minibuffer.buffer, 0); } struct buffer *minibuffer_buffer() { @@ -174,6 +171,11 @@ int32_t minibuffer_prompt_internal(struct command_ctx command_ctx, g_minibuffer.update_callback_userdata = userdata; } + if (windows_get_active() != minibuffer_window()) { + g_minibuffer.prev_window = windows_get_active(); + windows_set_active(minibuffer_window()); + } + return 0; } @@ -210,6 +212,10 @@ void minibuffer_abort_prompt() { minibuffer_clear(); g_minibuffer.update_callback = NULL; g_minibuffer.prompt_active = false; + + if (g_minibuffer.prev_window != NULL) { + windows_set_active(g_minibuffer.prev_window); + } } bool minibuffer_empty() { return !minibuffer_displaying(); } @@ -224,3 +230,7 @@ void minibuffer_clear() { } bool minibuffer_focused() { return g_minibuffer.prompt_active; } + +struct window *minibuffer_target_window() { + return g_minibuffer.prev_window; +} diff --git a/src/dged/minibuffer.h b/src/dged/minibuffer.h index 98a4db8..fb6eae4 100644 --- a/src/dged/minibuffer.h +++ b/src/dged/minibuffer.h @@ -5,6 +5,7 @@ struct buffer; struct command_ctx; +struct command_list; struct keymap; /** @@ -63,6 +64,7 @@ int32_t minibuffer_prompt_interactive(struct command_ctx command_ctx, void *userdata, const char *fmt, ...); void minibuffer_set_prompt(const char *fmt, ...); +uint32_t minibuffer_draw_prompt(struct command_list *commands); /** * Evaluate the current contents of the minibuffer @@ -106,3 +108,5 @@ bool minibuffer_displaying(); * @returns True if the minibuffer is currently focused, receiving user input. */ bool minibuffer_focused(); + +struct window *minibuffer_target_window(); diff --git a/src/dged/text.c b/src/dged/text.c index f8ba72d..b3eb4ad 100644 --- a/src/dged/text.c +++ b/src/dged/text.c @@ -8,6 +8,7 @@ #include "display.h" #include "signal.h" #include "utf8.h" +#include "vec.h" enum flags { LineChanged = 1 << 0, @@ -20,11 +21,18 @@ struct line { uint32_t nchars; }; +struct text_property_entry { + struct location start; + struct location end; + struct text_property property; +}; + struct text { // raw bytes without any null terminators struct line *lines; uint32_t nlines; uint32_t capacity; + VEC(struct text_property_entry) properties; }; struct text *text_create(uint32_t initial_capacity) { @@ -33,6 +41,8 @@ struct text *text_create(uint32_t initial_capacity) { txt->capacity = initial_capacity; txt->nlines = 0; + VEC_INIT(&txt->properties, 32); + return txt; } @@ -60,6 +70,7 @@ void text_clear(struct text *text) { } text->nlines = 0; + text_clear_properties(text); } // given `char_idx` as a character index, return the byte index @@ -494,3 +505,32 @@ struct text_chunk text_get_region(struct text *text, uint32_t start_line, bool text_line_contains_unicode(struct text *text, uint32_t line) { return text->lines[line].nbytes != text->lines[line].nchars; } + +void text_add_property(struct text *text, struct location start, + struct location end, struct text_property property) { + struct text_property_entry entry = { + .start = start, + .end = end, + .property = property, + }; + VEC_PUSH(&text->properties, entry); +} + +void text_get_properties(struct text *text, struct location location, + struct text_property **properties, + uint32_t max_nproperties, uint32_t *nproperties) { + uint32_t nres = 0; + VEC_FOR_EACH(&text->properties, struct text_property_entry * prop) { + if (location_is_between(location, prop->start, prop->end)) { + properties[nres] = &prop->property; + ++nres; + + if (nres == max_nproperties) { + break; + } + } + } + *nproperties = nres; +} + +void text_clear_properties(struct text *text) { VEC_CLEAR(&text->properties); } diff --git a/src/dged/text.h b/src/dged/text.h index fbee89b..e3bb3e4 100644 --- a/src/dged/text.h +++ b/src/dged/text.h @@ -1,10 +1,13 @@ +#ifndef _TEXT_H +#define _TEXT_H + #include #include #include -// opaque so it is easier to change representation to gap, rope etc. -struct text; +#include "location.h" +struct text; struct render_command; struct text *text_create(uint32_t initial_capacity); @@ -52,3 +55,32 @@ struct text_chunk text_get_region(struct text *text, uint32_t start_line, uint32_t end_col); bool text_line_contains_unicode(struct text *text, uint32_t line); + +enum text_property_type { + TextProperty_Colors, +}; + +struct text_property_colors { + bool set_fg; + uint32_t fg; + bool set_bg; + uint32_t bg; +}; + +struct text_property { + enum text_property_type type; + union { + struct text_property_colors colors; + }; +}; + +void text_add_property(struct text *text, struct location start, + struct location end, struct text_property property); + +void text_get_properties(struct text *text, struct location location, + struct text_property **properties, + uint32_t max_nproperties, uint32_t *nproperties); + +void text_clear_properties(struct text *text); + +#endif diff --git a/src/dged/window.c b/src/dged/window.c index e928e42..91b1735 100644 --- a/src/dged/window.c +++ b/src/dged/window.c @@ -1,6 +1,7 @@ #include "binding.h" #include "btree.h" #include "buffer.h" +#include "buffer_view.h" #include "command.h" #include "display.h" #include "minibuffer.h" @@ -30,7 +31,7 @@ BINTREE_ENTRY_TYPE(window_node, struct window); static struct windows { BINTREE(window_node) windows; - struct window_node *active; + struct window *active; struct keymap keymap; } g_windows; @@ -61,7 +62,7 @@ void windows_init(uint32_t height, uint32_t width, .y = 0, }; BINTREE_SET_ROOT(&g_windows.windows, root_window); - g_windows.active = BINTREE_ROOT(&g_windows.windows); + g_windows.active = &BINTREE_VALUE(BINTREE_ROOT(&g_windows.windows)); } static void window_tree_clear_sub(struct window_node *root_node) { @@ -156,54 +157,160 @@ void windows_resize(uint32_t height, uint32_t width) { void windows_update(void *(*frame_alloc)(size_t), uint64_t frame_time) { struct window *w = &g_minibuffer_window; - w->commands = command_list_create(w->height * w->width, frame_alloc, w->x, - w->y, w->buffer_view.buffer->name); - buffer_update(&w->buffer_view, -1, w->width, w->height, w->commands, - frame_time, &w->relline, &w->relcol); + w->x = 0; + w->commands = command_list_create(10, frame_alloc, w->x, w->y, "mb-prompt"); + + // draw the prompt here to make it off-limits for the buffer/buffer view + uint32_t prompt_len = minibuffer_draw_prompt(w->commands); + w->x += prompt_len; + uint32_t width = prompt_len < w->width ? w->width - prompt_len : 1; + + struct command_list *inner_commands = command_list_create( + w->height * width, frame_alloc, w->x, w->y, "bufview-mb"); + + struct buffer_view_update_params p = { + .commands = inner_commands, + .window_id = -1, + .frame_time = frame_time, + .width = width, + .height = w->height, + .window_x = w->x, + .window_y = w->y, + .frame_alloc = frame_alloc, + }; + buffer_view_update(&w->buffer_view, &p); + command_list_draw_command_list(w->commands, inner_commands); if (g_popup_visible) { w = &g_popup_window; - w->commands = command_list_create(w->height * w->width, frame_alloc, w->x, - w->y, w->buffer_view.buffer->name); - buffer_update(&w->buffer_view, -1, w->width, w->height, w->commands, - frame_time, &w->relline, &w->relcol); - } - struct window_node *n = BINTREE_ROOT(&g_windows.windows); - BINTREE_FIRST(n); - uint32_t window_id = 0; - while (n != NULL) { - struct window *w = &BINTREE_VALUE(n); - if (w->type == Window_Buffer) { - w->commands = command_list_create(w->height * w->width, frame_alloc, w->x, - w->y, w->buffer_view.buffer->name); + const uint32_t hpadding = 1; + const uint32_t border_width = 1; + const uint32_t shadow_width = 1; - buffer_update(&w->buffer_view, window_id, w->width, w->height, - w->commands, frame_time, &w->relline, &w->relcol); + bool draw_padding = false, draw_borders = false; - ++window_id; + // first, make sure window fits + struct window *rw = root_window(); + uint32_t w_x = w->x; + uint32_t w_y = w->y; + uint32_t width = w_x + w->width > rw->width ? rw->width - w_x : w->width; + uint32_t height = + w_y + w->height > rw->height ? rw->height - w_y : w->height; + + // is there space for padding? + if (w_x > 1 && w_x + width + hpadding <= rw->width) { + draw_padding = true; + + --w_x; + width += hpadding * 2; } - BINTREE_NEXT(n); + // is there space for borders? + if (w_y > 1 && w_y + height <= rw->height && w_x > 1 && + w_x + width + border_width <= rw->width) { + draw_borders = true; + + w_x -= border_width; + // shift text upward + w_y -= border_width * 2; + height += border_width * 2; + width += border_width * 2; + } + + w->commands = command_list_create(height * width, frame_alloc, w_x, w_y, + "popup-decor"); + uint32_t x = 0, y = 0; + if (draw_borders) { + // top + command_list_draw_repeated(w->commands, x, y, 0x8c94e2, 1); + command_list_draw_repeated(w->commands, x + 1, y, 0x8094e2, + width - (border_width * 2)); + command_list_draw_repeated(w->commands, x + width - 1, y, 0x9094e2, 1); + + // sides + for (uint32_t line = y + 1; line < (height + y - border_width); ++line) { + command_list_draw_repeated(w->commands, x, line, 0x8294e2, + border_width); + command_list_draw_repeated(w->commands, x + width - border_width, line, + 0x8294e2, border_width); + } + + // bottom + command_list_draw_repeated(w->commands, x, y + height - border_width, + 0x9494e2, 1); + command_list_draw_repeated(w->commands, x + 1, y + height - border_width, + 0x8094e2, width - (border_width * 2)); + command_list_draw_repeated(w->commands, x + width - 1, + y + height - border_width, 0x9894e2, 1); + + x += border_width; + y += border_width; + } + + if (draw_padding) { + for (uint32_t line = y; line < w->height + y; ++line) { + command_list_draw_repeated(w->commands, x, line, ' ', hpadding); + command_list_draw_repeated(w->commands, w->width + x + 1, line, ' ', + hpadding); + } + x += border_width; + } + + // shadow + /*command_list_set_index_color_bg(w->commands, 236); + command_list_draw_repeated(w->commands, 1, w->height + vmargins, ' ', + w->width + margins * 2); for (uint32_t line = 1; line < w->height; ++line) { + command_list_draw_repeated(w->commands, w->width + margins * 2, line, ' ', + shadow_width); + }*/ + + struct command_list *inner = command_list_create( + w->height * w->width, frame_alloc, w_x + x, w_y + y, "bufview-popup"); + + struct buffer_view_update_params p = { + .commands = inner, + .window_id = -1, + .frame_time = frame_time, + .width = w->width, + .height = w->height, + .window_x = w_x + x, + .window_y = w_y + y, + .frame_alloc = frame_alloc, + }; + + buffer_view_update(&w->buffer_view, &p); + command_list_draw_command_list(w->commands, inner); } - // clear text props for next frame - n = BINTREE_ROOT(&g_windows.windows); + struct window_node *n = BINTREE_ROOT(&g_windows.windows); BINTREE_FIRST(n); - + uint32_t window_id = 0; while (n != NULL) { struct window *w = &BINTREE_VALUE(n); if (w->type == Window_Buffer) { - buffer_clear_text_properties(w->buffer_view.buffer); + char name[16]; + snprintf(name, 16, "bufview-%s", w->buffer_view.buffer->name); + w->commands = command_list_create(w->height * w->width, frame_alloc, w->x, + w->y, name); + + struct buffer_view_update_params p = { + .commands = w->commands, + .window_id = window_id, + .frame_time = frame_time, + .width = w->width, + .height = w->height, + .window_x = w->x, + .window_y = w->y, + .frame_alloc = frame_alloc, + }; + + buffer_view_update(&w->buffer_view, &p); + ++window_id; } BINTREE_NEXT(n); } - - buffer_clear_text_properties(g_minibuffer_window.buffer_view.buffer); - if (g_popup_visible) { - buffer_clear_text_properties(g_popup_window.buffer_view.buffer); - } } void windows_render(struct display *display) { @@ -238,13 +345,22 @@ struct window_node *find_window(struct window *window) { } void windows_set_active(struct window *window) { + if (window == minibuffer_window()) { + g_windows.active = minibuffer_window(); + return; + } + struct window_node *n = find_window(window); if (n != NULL) { - g_windows.active = n; + g_windows.active = &BINTREE_VALUE(n); } } struct window *window_find_by_buffer(struct buffer *b) { + if (b == window_buffer(minibuffer_window())) { + return minibuffer_window(); + } + struct window_node *n = BINTREE_ROOT(&g_windows.windows); BINTREE_FIRST(n); while (n != NULL) { @@ -259,7 +375,7 @@ struct window *window_find_by_buffer(struct buffer *b) { } struct window *windows_get_active() { - return &BINTREE_VALUE(g_windows.active); + return g_windows.active; } void window_set_buffer(struct window *window, struct buffer *buffer) { @@ -291,19 +407,6 @@ bool window_has_prev_buffer(struct window *window) { return window->prev_buffer != NULL; } -struct buffer_location window_cursor_location(struct window *window) { - return (struct buffer_location){ - .col = window->relcol, - .line = window->relline, - }; -} -struct buffer_location window_absolute_cursor_location(struct window *window) { - return (struct buffer_location){ - .col = window->x + window->relcol, - .line = window->y + window->relline, - }; -} - void window_close(struct window *window) { // do not want to delete last window if (window == root_window()) { @@ -397,8 +500,9 @@ void window_hsplit(struct window *window, struct window **new_window_a, new_window.type = Window_Buffer; new_window.buffer_view = buffer_view_create(w.buffer_view.buffer, true, true); - buffer_goto(&new_window.buffer_view, w.buffer_view.dot.line, - w.buffer_view.dot.col); + buffer_view_goto(&new_window.buffer_view, + (struct location){.line = w.buffer_view.dot.line, + .col = w.buffer_view.dot.col}); new_window.prev_buffer = w.prev_buffer; new_window.x = w.x; new_window.y = w.y + w.height; @@ -439,8 +543,10 @@ void window_vsplit(struct window *window, struct window **new_window_a, new_window.type = Window_Buffer; new_window.buffer_view = buffer_view_create(w.buffer_view.buffer, true, true); - buffer_goto(&new_window.buffer_view, w.buffer_view.dot.line, - w.buffer_view.dot.col); + buffer_view_goto(&new_window.buffer_view, + (struct location){.line = w.buffer_view.dot.line, + .col = w.buffer_view.dot.col}); + new_window.prev_buffer = w.prev_buffer; new_window.x = w.x + w.width; new_window.y = w.y; @@ -507,9 +613,13 @@ struct window *windows_focus(uint32_t id) { return NULL; } -uint32_t window_width(struct window *window) { return window->width; } +uint32_t window_width(const struct window *window) { return window->width; } + +uint32_t window_height(const struct window *window) { return window->height; } -uint32_t window_height(struct window *window) { return window->height; } +struct window_position window_position(const struct window *window) { + return (struct window_position){.x = window->x, .y = window->y}; +} void windows_show_popup(uint32_t row, uint32_t col, uint32_t width, uint32_t height) { diff --git a/src/dged/window.h b/src/dged/window.h index 30c1061..e9f90aa 100644 --- a/src/dged/window.h +++ b/src/dged/window.h @@ -1,3 +1,6 @@ +#ifndef _WINDOW_H +#define _WINDOW_H + #include #include @@ -12,6 +15,11 @@ struct buffer; struct window; struct windows; +struct window_position { + uint32_t x; + uint32_t y; +}; + void windows_init(uint32_t height, uint32_t width, struct buffer *initial_buffer, struct buffer *minibuffer); @@ -38,10 +46,9 @@ struct buffer *window_buffer(struct window *window); struct buffer_view *window_buffer_view(struct window *window); struct buffer *window_prev_buffer(struct window *window); bool window_has_prev_buffer(struct window *window); -struct buffer_location window_cursor_location(struct window *window); -struct buffer_location window_absolute_cursor_location(struct window *window); -uint32_t window_width(struct window *window); -uint32_t window_height(struct window *window); +uint32_t window_width(const struct window *window); +uint32_t window_height(const struct window *window); +struct window_position window_position(const struct window *window); void window_close(struct window *window); void window_close_others(struct window *window); @@ -55,3 +62,5 @@ void window_vsplit(struct window *window, struct window **new_window_a, void windows_show_popup(uint32_t row, uint32_t col, uint32_t width, uint32_t height); void windows_close_popup(); + +#endif diff --git a/src/main/cmds.c b/src/main/cmds.c index ecce343..26e2628 100644 --- a/src/main/cmds.c +++ b/src/main/cmds.c @@ -9,6 +9,7 @@ #include "dged/binding.h" #include "dged/buffer.h" +#include "dged/buffer_view.h" #include "dged/buffers.h" #include "dged/command.h" #include "dged/display.h" @@ -25,7 +26,7 @@ int32_t _abort(struct command_ctx ctx, int argc, const char *argv[]) { abort_replace(); abort_completion(); minibuffer_abort_prompt(); - buffer_clear_mark(window_buffer_view(ctx.active_window)); + buffer_view_clear_mark(window_buffer_view(ctx.active_window)); reset_minibuffer_keys(minibuffer_buffer()); minibuffer_echo_timeout(4, "💣 aborted"); return 0; @@ -52,11 +53,7 @@ uint32_t g_ncompletions = 0; struct completion g_completions[50] = {0}; static void abort_completion() { - if (!minibuffer_focused()) { - reset_buffer_keys(window_buffer(windows_get_active())); - } else { - reset_minibuffer_keys(minibuffer_buffer()); - } + reset_minibuffer_keys(minibuffer_buffer()); windows_close_popup(); for (uint32_t compi = 0; compi < g_ncompletions; ++compi) { @@ -83,6 +80,8 @@ static void complete_path(const char *path, struct completion results[], size_t len = strlen(p1); char *p2 = strdup(p1); + size_t inlen = strlen(path); + if (len == 0) { goto done; } @@ -94,7 +93,9 @@ static void complete_path(const char *path, struct completion results[], const char *dir = p1; const char *file = ""; - if (dir[len - 1] != '/') { + // check the input path here since + // to_abspath removes trailing slashes + if (path[inlen - 1] != '/') { dir = dirname(p1); file = basename(p2); } @@ -143,20 +144,13 @@ done: *nresults = n; } -void render_completion_line(struct text_chunk *line_data, uint32_t line, - struct command_list *commands, void *userdata) { - command_list_set_show_whitespace(commands, false); - command_list_draw_repeated(commands, 0, line, ' ', 1); -} - -struct update_hook_result -update_completion_buffer(struct buffer_view *view, - struct command_list *commands, uint32_t width, - uint32_t height, uint64_t frame_time, void *userdata) { - struct text_chunk line = buffer_get_line(view->buffer, view->dot.line); +void update_completion_buffer(struct buffer *buffer, uint32_t width, + uint32_t height, void *userdata) { + struct buffer_view *view = (struct buffer_view *)userdata; + struct text_chunk line = buffer_line(buffer, view->dot.line); buffer_add_text_property( - view->buffer, (struct buffer_location){.line = view->dot.line, .col = 0}, - (struct buffer_location){.line = view->dot.line, .col = line.nchars}, + view->buffer, (struct location){.line = view->dot.line, .col = 0}, + (struct location){.line = view->dot.line, .col = line.nchars}, (struct text_property){.type = TextProperty_Colors, .colors = (struct text_property_colors){ .set_bg = false, @@ -168,17 +162,6 @@ update_completion_buffer(struct buffer_view *view, if (line.allocated) { free(line.text); } - - struct update_hook_result res = {0}; - res.margins.left = 1; - res.margins.right = 1; - res.line_render_hook = (struct line_render_hook){ - .callback = render_completion_line, - .empty_callback = NULL, - .userdata = NULL, - }; - - return res; } static int32_t goto_completion(struct command_ctx ctx, int argc, @@ -193,7 +176,7 @@ static int32_t goto_completion(struct command_ctx ctx, int argc, movement_fn(v); if (v->dot.line >= text_num_lines(b->text)) { - buffer_backward_line(v); + buffer_view_backward_line(v); } } @@ -211,15 +194,13 @@ static int32_t insert_completion(struct command_ctx ctx, int argc, char *ins = (char *)g_completions[cv->dot.line].insert; bool complete = g_completions[cv->dot.line].complete; size_t inslen = strlen(ins); - if (minibuffer_focused()) { - buffer_add_text(window_buffer_view(minibuffer_window()), ins, inslen); - } else { - buffer_add_text(window_buffer_view(windows_get_active()), ins, inslen); - } + buffer_view_add(window_buffer_view(windows_get_active()), ins, inslen); - if (complete) { + if (minibuffer_focused() && complete) { minibuffer_execute(); } + + abort_completion(); } } @@ -227,9 +208,9 @@ static int32_t insert_completion(struct command_ctx ctx, int argc, } COMMAND_FN("next-completion", next_completion, goto_completion, - buffer_forward_line); + buffer_view_forward_line); COMMAND_FN("prev-completion", prev_completion, goto_completion, - buffer_backward_line); + buffer_view_backward_line); COMMAND_FN("insert-completion", insert_completion, insert_completion, NULL); static void on_find_file_input(void *userdata) { @@ -237,12 +218,14 @@ static void on_find_file_input(void *userdata) { struct text_chunk txt = minibuffer_content(); struct window *mb = minibuffer_window(); - struct buffer_location mb_dot = window_absolute_cursor_location(mb); + struct location mb_dot = window_buffer_view(mb)->dot; + struct window_position mbpos = window_position(mb); struct buffer *b = buffers_find(buffers, "*completions*"); if (b == NULL) { b = buffers_add(buffers, buffer_create("*completions*")); - buffer_add_update_hook(b, update_completion_buffer, NULL); + buffer_add_update_hook(b, update_completion_buffer, + (void *)window_buffer_view(popup_window())); window_set_buffer_e(popup_window(), b, false, false); } @@ -261,9 +244,10 @@ static void on_find_file_input(void *userdata) { complete_path(path, g_completions, 50, &g_ncompletions); size_t max_width = 0; - struct buffer_location prev_dot = v->dot; + struct location prev_dot = v->dot; - buffer_clear(v); + buffer_clear(v->buffer); + buffer_view_goto(v, (struct location){.line = 0, .col = 0}); if (g_ncompletions > 0) { for (uint32_t compi = 0; compi < g_ncompletions; ++compi) { const char *disp = g_completions[compi].display; @@ -271,17 +255,18 @@ static void on_find_file_input(void *userdata) { if (width > max_width) { max_width = width; } - buffer_add_text(v, (uint8_t *)disp, width); + buffer_view_add(v, (uint8_t *)disp, width); // the extra newline feels weird in navigation if (compi != g_ncompletions - 1) { - buffer_add_text(v, (uint8_t *)"\n", 1); + buffer_view_add(v, (uint8_t *)"\n", 1); } } - buffer_goto(v, prev_dot.line, prev_dot.col); + buffer_view_goto( + v, (struct location){.line = prev_dot.line, .col = prev_dot.col}); if (prev_dot.line >= text_num_lines(b->text)) { - buffer_backward_line(v); + buffer_view_backward_line(v); } if (!popup_window_visible()) { @@ -294,11 +279,12 @@ static void on_find_file_input(void *userdata) { sizeof(bindings) / sizeof(bindings[0])); } - uint32_t width = max_width > 2 ? max_width + 2 : 4, + uint32_t width = max_width > 2 ? max_width : 4, height = g_ncompletions > 10 ? 10 : g_ncompletions; - windows_show_popup(mb_dot.line - height, mb_dot.col, width, height); + windows_show_popup(mbpos.y + mb_dot.line - height, mbpos.x + mb_dot.col, + width, height); } else { - windows_close_popup(); + abort_completion(); } if (txt.allocated) { @@ -313,8 +299,6 @@ int32_t find_file(struct command_ctx ctx, int argc, const char *argv[]) { "find file: "); } - abort_completion(); - pth = argv[0]; struct stat sb = {0}; if (stat(pth, &sb) < 0 && errno != ENOENT) { @@ -351,7 +335,8 @@ int32_t write_file(struct command_ctx ctx, int argc, const char *argv[]) { } pth = argv[0]; - buffer_write_to(window_buffer(ctx.active_window), pth); + buffer_set_filename(window_buffer(ctx.active_window), pth); + buffer_to_file(window_buffer(ctx.active_window)); return 0; } @@ -432,7 +417,7 @@ void buffer_to_list_line(struct buffer *buffer, void *userdata) { buffer->filename != NULL ? buffer->filename : ""); if (written > 0) { - buffer_add_text(listbuf, (uint8_t *)buf, written); + buffer_view_add(listbuf, (uint8_t *)buf, written); } } @@ -441,7 +426,7 @@ int32_t buflist_visit_cmd(struct command_ctx ctx, int argc, struct window *w = ctx.active_window; struct buffer_view *bv = window_buffer_view(w); - struct text_chunk text = buffer_get_line(bv->buffer, bv->dot.line); + struct text_chunk text = buffer_line(bv->buffer, bv->dot.line); char *end = (char *)memchr(text.text, ' ', text.nbytes); @@ -468,9 +453,9 @@ int32_t buflist_close_cmd(struct command_ctx ctx, int argc, void buflist_refresh(struct buffers *buffers, struct buffer_view *target) { buffer_set_readonly(target->buffer, false); - buffer_clear(target); + buffer_clear(target->buffer); buffers_for_each(buffers, buffer_to_list_line, target); - buffer_goto_beginning(target); + buffer_view_goto_beginning(target); buffer_set_readonly(target->buffer, true); } @@ -535,102 +520,102 @@ void register_global_commands(struct commands *commands, register_search_replace_commands(commands); } -#define BUFFER_WRAPCMD_POS(fn) \ +#define BUFFER_VIEW_WRAPCMD(fn) \ static int32_t fn##_cmd(struct command_ctx ctx, int argc, \ const char *argv[]) { \ - fn(window_buffer_view(ctx.active_window)); \ + buffer_view_##fn(window_buffer_view(ctx.active_window)); \ return 0; \ } #define BUFFER_WRAPCMD(fn) \ static int32_t fn##_cmd(struct command_ctx ctx, int argc, \ const char *argv[]) { \ - fn(window_buffer(ctx.active_window)); \ + buffer_##fn(window_buffer(ctx.active_window)); \ return 0; \ } -BUFFER_WRAPCMD_POS(buffer_kill_line); -BUFFER_WRAPCMD_POS(buffer_forward_delete_char); -BUFFER_WRAPCMD_POS(buffer_backward_delete_char); -BUFFER_WRAPCMD_POS(buffer_forward_delete_word); -BUFFER_WRAPCMD_POS(buffer_backward_delete_word); -BUFFER_WRAPCMD_POS(buffer_backward_char); -BUFFER_WRAPCMD_POS(buffer_backward_word); -BUFFER_WRAPCMD_POS(buffer_forward_char); -BUFFER_WRAPCMD_POS(buffer_forward_word); -BUFFER_WRAPCMD_POS(buffer_backward_line); -BUFFER_WRAPCMD_POS(buffer_forward_line); -BUFFER_WRAPCMD_POS(buffer_end_of_line); -BUFFER_WRAPCMD_POS(buffer_beginning_of_line); -BUFFER_WRAPCMD_POS(buffer_newline); -BUFFER_WRAPCMD_POS(buffer_indent); -BUFFER_WRAPCMD(buffer_to_file); -BUFFER_WRAPCMD(buffer_reload); -BUFFER_WRAPCMD_POS(buffer_set_mark); -BUFFER_WRAPCMD_POS(buffer_clear_mark); -BUFFER_WRAPCMD_POS(buffer_copy); -BUFFER_WRAPCMD_POS(buffer_cut); -BUFFER_WRAPCMD_POS(buffer_paste); -BUFFER_WRAPCMD_POS(buffer_paste_older); -BUFFER_WRAPCMD_POS(buffer_goto_beginning); -BUFFER_WRAPCMD_POS(buffer_goto_end); -BUFFER_WRAPCMD_POS(buffer_undo); - -static int32_t buffer_view_scroll_up_cmd(struct command_ctx ctx, int argc, - const char *argv[]) { - buffer_view_scroll_up(window_buffer_view(ctx.active_window), - window_height(ctx.active_window)); +BUFFER_WRAPCMD(to_file); +BUFFER_WRAPCMD(reload); +BUFFER_VIEW_WRAPCMD(kill_line); +BUFFER_VIEW_WRAPCMD(forward_delete_char); +BUFFER_VIEW_WRAPCMD(backward_delete_char); +BUFFER_VIEW_WRAPCMD(forward_delete_word); +BUFFER_VIEW_WRAPCMD(backward_delete_word); +BUFFER_VIEW_WRAPCMD(backward_char); +BUFFER_VIEW_WRAPCMD(backward_word); +BUFFER_VIEW_WRAPCMD(forward_char); +BUFFER_VIEW_WRAPCMD(forward_word); +BUFFER_VIEW_WRAPCMD(backward_line); +BUFFER_VIEW_WRAPCMD(forward_line); +BUFFER_VIEW_WRAPCMD(goto_end_of_line); +BUFFER_VIEW_WRAPCMD(goto_beginning_of_line); +BUFFER_VIEW_WRAPCMD(newline); +BUFFER_VIEW_WRAPCMD(indent); +BUFFER_VIEW_WRAPCMD(set_mark); +BUFFER_VIEW_WRAPCMD(clear_mark); +BUFFER_VIEW_WRAPCMD(copy); +BUFFER_VIEW_WRAPCMD(cut); +BUFFER_VIEW_WRAPCMD(paste); +BUFFER_VIEW_WRAPCMD(paste_older); +BUFFER_VIEW_WRAPCMD(goto_beginning); +BUFFER_VIEW_WRAPCMD(goto_end); +BUFFER_VIEW_WRAPCMD(undo); + +static int32_t scroll_up_cmd(struct command_ctx ctx, int argc, + const char *argv[]) { + buffer_view_backward_nlines(window_buffer_view(ctx.active_window), + window_height(ctx.active_window) - 1); return 0; }; -static int32_t buffer_view_scroll_down_cmd(struct command_ctx ctx, int argc, - const char *argv[]) { - buffer_view_scroll_down(window_buffer_view(ctx.active_window), - window_height(ctx.active_window)); +static int32_t scroll_down_cmd(struct command_ctx ctx, int argc, + const char *argv[]) { + buffer_view_forward_nlines(window_buffer_view(ctx.active_window), + window_height(ctx.active_window) - 1); return 0; }; -static int32_t buffer_goto_line(struct command_ctx ctx, int argc, - const char *argv[]) { +static int32_t goto_line(struct command_ctx ctx, int argc, const char *argv[]) { if (argc == 0) { return minibuffer_prompt(ctx, "line: "); } uint32_t line = atoi(argv[0]); - buffer_goto(window_buffer_view(ctx.active_window), line - 1, 0); + buffer_view_goto(window_buffer_view(ctx.active_window), + (struct location){.line = line, .col = 0}); } void register_buffer_commands(struct commands *commands) { static struct command buffer_commands[] = { - {.name = "kill-line", .fn = buffer_kill_line_cmd}, - {.name = "delete-word", .fn = buffer_forward_delete_word_cmd}, - {.name = "backward-delete-word", .fn = buffer_backward_delete_word_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}, - {.name = "scroll-down", .fn = buffer_view_scroll_down_cmd}, - {.name = "scroll-up", .fn = buffer_view_scroll_up_cmd}, - {.name = "reload", .fn = buffer_reload_cmd}, - {.name = "goto-line", .fn = buffer_goto_line}, + {.name = "kill-line", .fn = kill_line_cmd}, + {.name = "delete-word", .fn = forward_delete_word_cmd}, + {.name = "backward-delete-word", .fn = backward_delete_word_cmd}, + {.name = "delete-char", .fn = forward_delete_char_cmd}, + {.name = "backward-delete-char", .fn = backward_delete_char_cmd}, + {.name = "backward-char", .fn = backward_char_cmd}, + {.name = "backward-word", .fn = backward_word_cmd}, + {.name = "forward-char", .fn = forward_char_cmd}, + {.name = "forward-word", .fn = forward_word_cmd}, + {.name = "backward-line", .fn = backward_line_cmd}, + {.name = "forward-line", .fn = forward_line_cmd}, + {.name = "end-of-line", .fn = goto_end_of_line_cmd}, + {.name = "beginning-of-line", .fn = goto_beginning_of_line_cmd}, + {.name = "newline", .fn = newline_cmd}, + {.name = "indent", .fn = indent_cmd}, + {.name = "buffer-write-to-file", .fn = to_file_cmd}, + {.name = "set-mark", .fn = set_mark_cmd}, + {.name = "clear-mark", .fn = clear_mark_cmd}, + {.name = "copy", .fn = copy_cmd}, + {.name = "cut", .fn = cut_cmd}, + {.name = "paste", .fn = paste_cmd}, + {.name = "paste-older", .fn = paste_older_cmd}, + {.name = "goto-beginning", .fn = goto_beginning_cmd}, + {.name = "goto-end", .fn = goto_end_cmd}, + {.name = "undo", .fn = undo_cmd}, + {.name = "scroll-down", .fn = scroll_down_cmd}, + {.name = "scroll-up", .fn = scroll_up_cmd}, + {.name = "reload", .fn = reload_cmd}, + {.name = "goto-line", .fn = goto_line}, }; register_commands(commands, buffer_commands, diff --git a/src/main/main.c b/src/main/main.c index b6ee3c0..fd69cff 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -11,6 +11,7 @@ #include "dged/allocator.h" #include "dged/binding.h" #include "dged/buffer.h" +#include "dged/buffer_view.h" #include "dged/buffers.h" #include "dged/display.h" #include "dged/lang.h" @@ -127,7 +128,7 @@ int main(int argc, char *argv[]) { {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}}; - char *filename = NULL; + const char *filename = NULL; uint32_t jumpline = 1; bool goto_end = false; char ch; @@ -239,9 +240,13 @@ int main(int argc, char *argv[]) { &minibuffer); struct window *active = windows_get_active(); if (goto_end) { - buffer_goto_end(window_buffer_view(active)); + buffer_view_goto_end(window_buffer_view(active)); } else { - buffer_goto(window_buffer_view(active), jumpline > 0 ? jumpline - 1 : 0, 0); + struct location to = { + .line = jumpline > 0 ? jumpline - 1 : 0, + .col = 0, + }; + buffer_view_goto(window_buffer_view(active), to); } DECLARE_TIMER(buffer); @@ -253,7 +258,6 @@ int main(int argc, char *argv[]) { static uint32_t nkeychars = 0; while (running) { - if (display_resized) { windows_resize(display_height(display), display_width(display)); display_resized = false; @@ -265,9 +269,6 @@ int main(int argc, char *argv[]) { TIMED_SCOPE_END(buffer); struct window *active_window = windows_get_active(); - if (minibuffer_focused()) { - active_window = minibuffer_window(); - } /* Update the screen by flushing command lists collected from updating the * buffers. @@ -275,14 +276,15 @@ int main(int argc, char *argv[]) { TIMED_SCOPE_BEGIN(display); display_begin_render(display); windows_render(display); - struct buffer_location cursor = - window_absolute_cursor_location(active_window); - display_move_cursor(display, cursor.line, cursor.col); + struct buffer_view *view = window_buffer_view(active_window); + struct location cursor = buffer_view_dot_to_relative(view); + struct window_position winpos = window_position(active_window); + display_move_cursor(display, winpos.y + cursor.line, winpos.x + cursor.col); display_end_render(display); TIMED_SCOPE_END(display); /* This blocks for events, so if nothing has happened we block here and let - * the CPU do something more useful than updating this narcissistic editor. + * the CPU do something more useful than updating this editor for no reason. * This is also the reason that there is no timed scope around this, it * simply makes no sense. */ @@ -343,7 +345,7 @@ int main(int argc, char *argv[]) { } } } else if (k->mod == 0) { - buffer_add_text(window_buffer_view(active_window), + buffer_view_add(window_buffer_view(active_window), &kbd_upd.raw[k->start], k->end - k->start); } else { char keyname[16]; diff --git a/src/main/search-replace.c b/src/main/search-replace.c index 828ce32..cd12d5d 100644 --- a/src/main/search-replace.c +++ b/src/main/search-replace.c @@ -4,6 +4,7 @@ #include "dged/binding.h" #include "dged/buffer.h" +#include "dged/buffer_view.h" #include "dged/command.h" #include "dged/minibuffer.h" #include "dged/window.h" @@ -13,7 +14,7 @@ static struct replace { char *replace; - struct match *matches; + struct region *matches; uint32_t nmatches; uint32_t current_match; } g_current_replace = {0}; @@ -28,8 +29,8 @@ void abort_replace() { minibuffer_abort_prompt(); } -uint64_t matchdist(struct match *match, struct buffer_location loc) { - struct buffer_location begin = match->begin; +uint64_t matchdist(struct region *match, struct location loc) { + struct location begin = match->begin; int64_t linedist = (int64_t)begin.line - (int64_t)loc.line; int64_t coldist = (int64_t)begin.col - (int64_t)loc.col; @@ -38,26 +39,10 @@ uint64_t matchdist(struct match *match, struct buffer_location loc) { return (linedist * linedist) * 1e6 + coldist * coldist; } -int buffer_loc_cmp(struct buffer_location loc1, struct buffer_location loc2) { - if (loc1.line < loc2.line) { - return -1; - } else if (loc1.line > loc2.line) { - return 1; - } else { - if (loc1.col < loc2.col) { - return -1; - } else if (loc1.col > loc2.col) { - return 1; - } else { - return 0; - } - } -} - -static void highlight_matches(struct buffer *buffer, struct match *matches, +static void highlight_matches(struct buffer *buffer, struct region *matches, uint32_t nmatches, uint32_t current) { for (uint32_t matchi = 0; matchi < nmatches; ++matchi) { - struct match *m = &matches[matchi]; + struct region *m = &matches[matchi]; if (matchi == current) { buffer_add_text_property( buffer, m->begin, m->end, @@ -88,10 +73,12 @@ static int32_t replace_next(struct command_ctx ctx, int argc, struct replace *state = &g_current_replace; struct buffer_view *buffer_view = window_buffer_view(windows_get_active()); - struct match *m = &state->matches[state->current_match]; - buffer_set_mark_at(buffer_view, m->begin.line, m->begin.col); - buffer_goto(buffer_view, m->end.line, m->end.col + 1); - buffer_add_text(buffer_view, state->replace, strlen(state->replace)); + struct region *m = &state->matches[state->current_match]; + buffer_view_set_mark_at(buffer_view, (struct location){.line = m->begin.line, + .col = m->begin.col}); + buffer_view_goto(buffer_view, (struct location){.line = m->end.line, + .col = m->end.col + 1}); + buffer_view_add(buffer_view, state->replace, strlen(state->replace)); ++state->current_match; @@ -99,7 +86,8 @@ static int32_t replace_next(struct command_ctx ctx, int argc, abort_replace(); } else { m = &state->matches[state->current_match]; - buffer_goto(buffer_view, m->begin.line, m->begin.col); + buffer_view_goto(buffer_view, (struct location){.line = m->begin.line, + .col = m->begin.col}); highlight_matches(buffer_view->buffer, state->matches, state->nmatches, state->current_match); } @@ -111,8 +99,9 @@ static int32_t skip_next(struct command_ctx ctx, int argc, const char *argv[]) { struct replace *state = &g_current_replace; struct buffer_view *buffer_view = window_buffer_view(windows_get_active()); - struct match *m = &state->matches[state->current_match]; - buffer_goto(buffer_view, m->end.line, m->end.col + 1); + struct region *m = &state->matches[state->current_match]; + buffer_view_goto(buffer_view, (struct location){.line = m->end.line, + .col = m->end.col + 1}); ++state->current_match; @@ -120,7 +109,8 @@ static int32_t skip_next(struct command_ctx ctx, int argc, const char *argv[]) { abort_replace(); } else { m = &state->matches[state->current_match]; - buffer_goto(buffer_view, m->begin.line, m->begin.col); + buffer_view_goto(buffer_view, (struct location){.line = m->begin.line, + .col = m->begin.col}); highlight_matches(buffer_view->buffer, state->matches, state->nmatches, state->current_match); } @@ -132,14 +122,14 @@ COMMAND_FN("replace-next", replace_next, replace_next, NULL); COMMAND_FN("skip-next", skip_next, skip_next, NULL); static int cmp_matches(const void *m1, const void *m2) { - struct match *match1 = (struct match *)m1; - struct match *match2 = (struct match *)m2; - struct buffer_location dot = window_buffer_view(windows_get_active())->dot; + struct region *match1 = (struct region *)m1; + struct region *match2 = (struct region *)m2; + struct location dot = window_buffer_view(windows_get_active())->dot; uint64_t dist1 = matchdist(match1, dot); uint64_t dist2 = matchdist(match2, dot); - int loc1 = buffer_loc_cmp(match1->begin, dot); - int loc2 = buffer_loc_cmp(match2->begin, dot); + int loc1 = location_compare(match1->begin, dot); + int loc2 = location_compare(match2->begin, dot); int64_t score1 = dist1 * loc1; int64_t score2 = dist2 * loc2; @@ -166,7 +156,7 @@ static int32_t replace(struct command_ctx ctx, int argc, const char *argv[]) { } struct buffer_view *buffer_view = window_buffer_view(windows_get_active()); - struct match *matches = NULL; + struct region *matches = NULL; uint32_t nmatches = 0; buffer_find(buffer_view->buffer, argv[0], &matches, &nmatches); @@ -177,7 +167,7 @@ static int32_t replace(struct command_ctx ctx, int argc, const char *argv[]) { } // sort matches - qsort(matches, nmatches, sizeof(struct match), cmp_matches); + qsort(matches, nmatches, sizeof(struct region), cmp_matches); g_current_replace = (struct replace){ .replace = strdup(argv[1]), @@ -186,8 +176,9 @@ static int32_t replace(struct command_ctx ctx, int argc, const char *argv[]) { .current_match = 0, }; - struct match *m = &g_current_replace.matches[0]; - buffer_goto(buffer_view, m->begin.line, m->begin.col); + struct region *m = &g_current_replace.matches[0]; + buffer_view_goto(buffer_view, (struct location){.line = m->begin.line, + .col = m->begin.col}); highlight_matches(buffer_view->buffer, g_current_replace.matches, g_current_replace.nmatches, 0); @@ -203,6 +194,7 @@ static int32_t replace(struct command_ctx ctx, int argc, const char *argv[]) { } static char *g_last_search = NULL; +static bool g_last_search_interactive = false; const char *search_prompt(bool reverse) { const char *txt = "search (down): "; @@ -215,13 +207,13 @@ const char *search_prompt(bool reverse) { struct closest_match { bool found; - struct match closest; + struct region closest; }; static struct closest_match find_closest(struct buffer_view *view, const char *pattern, bool highlight, bool reverse) { - struct match *matches = NULL; + struct region *matches = NULL; uint32_t nmatches = 0; struct closest_match res = { .found = false, @@ -233,10 +225,10 @@ static struct closest_match find_closest(struct buffer_view *view, // find the "nearest" match if (nmatches > 0) { res.found = true; - struct match *closest = &matches[0]; + struct region *closest = &matches[0]; int64_t closest_dist = INT64_MAX; for (uint32_t matchi = 0; matchi < nmatches; ++matchi) { - struct match *m = &matches[matchi]; + struct region *m = &matches[matchi]; if (highlight) { buffer_add_text_property( @@ -249,7 +241,7 @@ static struct closest_match find_closest(struct buffer_view *view, .fg = 0, }}); } - int res = buffer_loc_cmp(m->begin, view->dot); + int res = location_compare(m->begin, view->dot); uint64_t dist = matchdist(m, view->dot); if (((res < 0 && reverse) || (res > 0 && !reverse)) && dist < closest_dist) { @@ -279,12 +271,13 @@ static struct closest_match find_closest(struct buffer_view *view, void do_search(struct buffer_view *view, const char *pattern, bool reverse) { g_last_search = strdup(pattern); - struct buffer_view *buffer_view = window_buffer_view(windows_get_active()); - struct closest_match m = find_closest(buffer_view, pattern, true, reverse); + struct closest_match m = find_closest(view, pattern, true, reverse); // find the "nearest" match if (m.found) { - buffer_goto(buffer_view, m.closest.begin.line, m.closest.begin.col); + buffer_view_goto(view, + (struct location){.line = m.closest.begin.line, + .col = m.closest.begin.col}); } else { minibuffer_echo_timeout(4, "%s not found", pattern); } @@ -292,13 +285,13 @@ void do_search(struct buffer_view *view, const char *pattern, bool reverse) { int32_t search_interactive(struct command_ctx ctx, int argc, const char *argv[]) { + g_last_search_interactive = true; const char *pattern = NULL; if (minibuffer_content().nbytes == 0) { // recall the last search, if any if (g_last_search != NULL) { struct buffer_view *view = window_buffer_view(minibuffer_window()); - buffer_clear(view); - buffer_add_text(view, (uint8_t *)g_last_search, strlen(g_last_search)); + buffer_set_text(view->buffer, (uint8_t *)g_last_search, strlen(g_last_search)); pattern = g_last_search; } } else { @@ -309,11 +302,10 @@ int32_t search_interactive(struct command_ctx ctx, int argc, pattern = p; } - minibuffer_set_prompt(search_prompt(*(bool *)ctx.userdata)); + minibuffer_set_prompt(search_prompt(*(bool*)ctx.userdata)); if (pattern != NULL) { - // ctx.active_window would be the minibuffer window - do_search(window_buffer_view(windows_get_active()), pattern, + do_search(window_buffer_view(minibuffer_target_window()), pattern, *(bool *)ctx.userdata); free((char *)pattern); } @@ -329,7 +321,7 @@ COMMAND_FN("search-backward", search_backward, search_interactive, &search_dir_backward); int32_t find(struct command_ctx ctx, int argc, const char *argv[]) { - bool reverse = strcmp((char *)ctx.userdata, "backward") == 0; + bool reverse = *(bool *)ctx.userdata; if (argc == 0) { struct binding bindings[] = { ANONYMOUS_BINDING(Ctrl, 'S', &search_forward_command), @@ -341,6 +333,11 @@ int32_t find(struct command_ctx ctx, int argc, const char *argv[]) { } reset_minibuffer_keys(minibuffer_buffer()); + if (g_last_search_interactive) { + g_last_search_interactive = false; + return 0; + } + struct text_chunk content = minibuffer_content(); char *pattern = malloc(content.nbytes + 1); strncpy(pattern, content.text, content.nbytes); @@ -354,8 +351,8 @@ int32_t find(struct command_ctx ctx, int argc, const char *argv[]) { void register_search_replace_commands(struct commands *commands) { struct command search_replace_commands[] = { - {.name = "find-next", .fn = find, .userdata = "forward"}, - {.name = "find-prev", .fn = find, .userdata = "backward"}, + {.name = "find-next", .fn = find, .userdata = &search_dir_forward}, + {.name = "find-prev", .fn = find, .userdata = &search_dir_backward}, {.name = "replace", .fn = replace}, }; -- cgit v1.2.3