diff options
Diffstat (limited to 'src/buffer.c')
| -rw-r--r-- | src/buffer.c | 218 |
1 files changed, 163 insertions, 55 deletions
diff --git a/src/buffer.c b/src/buffer.c index 8a86e69..6af7329 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -23,8 +23,14 @@ struct modeline { #define KILL_RING_SZ 64 static struct kill_ring { struct text_chunk buffer[KILL_RING_SZ]; + struct buffer_location last_paste; + bool paste_up_to_date; uint32_t curr_idx; -} g_kill_ring = {.curr_idx = 0}; + uint32_t paste_idx; +} g_kill_ring = {.curr_idx = 0, + .last_paste = {0}, + .paste_idx = 0, + .paste_up_to_date = false}; struct update_hook_result buffer_linenum_hook(struct buffer *buffer, struct command_list *commands, @@ -48,8 +54,7 @@ struct buffer buffer_create(char *name, bool modeline) { .mark_set = false, .keymaps = calloc(10, sizeof(struct keymap)), .nkeymaps = 1, - .scroll_col = 0, - .scroll_line = 0, + .scroll = {0}, .update_hooks = {0}, .nkeymaps_max = 10, }; @@ -72,6 +77,9 @@ struct buffer buffer_create(char *name, bool modeline) { BINDING(Ctrl, 'A', "beginning-of-line"), BINDING(Ctrl, 'E', "end-of-line"), + BINDING(Meta, '<', "goto-beginning"), + BINDING(Meta, '>', "goto-end"), + BINDING(ENTER, "newline"), BINDING(TAB, "indent"), @@ -84,6 +92,7 @@ struct buffer buffer_create(char *name, bool modeline) { BINDING(Ctrl, 'W', "cut"), BINDING(Ctrl, 'Y', "paste"), + BINDING(Meta, 'y', "paste-older"), BINDING(Meta, 'w', "copy"), }; keymap_bind_keys(&b.keymaps[0], bindings, @@ -138,14 +147,24 @@ void buffer_add_keymap(struct buffer *buffer, struct keymap *keymap) { ++buffer->nkeymaps; } +void buffer_goto_beginning(struct buffer *buffer) { + buffer->dot.col = 0; + buffer->dot.line = 0; +} + +void buffer_goto_end(struct buffer *buffer) { + buffer->dot.line = text_num_lines(buffer->text); + buffer->dot.col = 0; +} + bool movev(struct buffer *buffer, int rowdelta) { int64_t new_line = (int64_t)buffer->dot.line + rowdelta; if (new_line < 0) { buffer->dot.line = 0; return false; - } else if (new_line > text_num_lines(buffer->text) - 1) { - buffer->dot.line = text_num_lines(buffer->text) - 1; + } else if (new_line > text_num_lines(buffer->text)) { + buffer->dot.line = text_num_lines(buffer->text); return false; } else { buffer->dot.line = (uint32_t)new_line; @@ -191,15 +210,20 @@ struct region to_region(struct buffer_location dot, return reg; } -bool region_has_size(struct buffer *buffer) { +struct region buffer_get_region(struct buffer *buffer) { + return to_region(buffer->dot, buffer->mark); +} + +bool buffer_region_has_size(struct buffer *buffer) { return buffer->mark_set && (labs((int64_t)buffer->mark.line - (int64_t)buffer->dot.line) + labs((int64_t)buffer->mark.col - (int64_t)buffer->dot.col)) > 0; } struct text_chunk *copy_region(struct buffer *buffer, struct region region) { - g_kill_ring.curr_idx = g_kill_ring.curr_idx + 1 % KILL_RING_SZ; 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 != NULL) { free(curr->text); } @@ -212,23 +236,57 @@ struct text_chunk *copy_region(struct buffer *buffer, struct region region) { } void buffer_copy(struct buffer *buffer) { - if (region_has_size(buffer)) { - struct region reg = to_region(buffer->dot, buffer->mark); + if (buffer_region_has_size(buffer)) { + struct region reg = buffer_get_region(buffer); struct text_chunk *curr = copy_region(buffer, reg); buffer_clear_mark(buffer); } } +void paste(struct buffer *buffer, uint32_t ring_idx) { + if (ring_idx > 0) { + struct text_chunk *curr = &g_kill_ring.buffer[ring_idx - 1]; + if (curr != NULL) { + g_kill_ring.last_paste = buffer->mark_set ? buffer->mark : buffer->dot; + buffer_add_text(buffer, curr->text, curr->nbytes); + g_kill_ring.paste_up_to_date = true; + } + } +} + void buffer_paste(struct buffer *buffer) { - struct text_chunk *curr = &g_kill_ring.buffer[g_kill_ring.curr_idx]; - if (curr != NULL) { - buffer_add_text(buffer, curr->text, curr->nbytes); + g_kill_ring.paste_idx = g_kill_ring.curr_idx; + paste(buffer, g_kill_ring.curr_idx); +} + +void buffer_paste_older(struct buffer *buffer) { + if (g_kill_ring.paste_up_to_date) { + + // remove previous paste + struct text_chunk *curr = &g_kill_ring.buffer[g_kill_ring.curr_idx]; + text_delete(buffer->text, g_kill_ring.last_paste.line, + g_kill_ring.last_paste.col, buffer->dot.line, buffer->dot.col); + + // place ourselves right + buffer->dot = g_kill_ring.last_paste; + + // paste older + if (g_kill_ring.paste_idx - 1 > 0) { + --g_kill_ring.paste_idx; + } else { + g_kill_ring.paste_idx = g_kill_ring.curr_idx; + } + + paste(buffer, g_kill_ring.paste_idx); + + } else { + buffer_paste(buffer); } } void buffer_cut(struct buffer *buffer) { - if (region_has_size(buffer)) { - struct region reg = to_region(buffer->dot, buffer->mark); + if (buffer_region_has_size(buffer)) { + struct region reg = buffer_get_region(buffer); copy_region(buffer, reg); text_delete(buffer->text, reg.begin.line, reg.begin.col, reg.end.line, reg.end.col); @@ -237,6 +295,20 @@ void buffer_cut(struct buffer *buffer) { } } +bool maybe_delete_region(struct buffer *buffer) { + if (buffer_region_has_size(buffer)) { + struct region reg = buffer_get_region(buffer); + text_delete(buffer->text, reg.begin.line, reg.begin.col, reg.end.line, + reg.end.col); + + buffer_clear_mark(buffer); + buffer->dot = reg.begin; + return true; + } + + return false; +} + void buffer_kill_line(struct buffer *buffer) { uint32_t nchars = text_line_length(buffer->text, buffer->dot.line); if (nchars == 0) { @@ -257,11 +329,19 @@ void buffer_kill_line(struct buffer *buffer) { } void buffer_forward_delete_char(struct buffer *buffer) { + if (maybe_delete_region(buffer)) { + return; + } + text_delete(buffer->text, buffer->dot.line, buffer->dot.col, buffer->dot.line, buffer->dot.col + 1); } void buffer_backward_delete_char(struct buffer *buffer) { + if (maybe_delete_region(buffer)) { + return; + } + moveh(buffer, -1); buffer_forward_delete_char(buffer); } @@ -358,9 +438,8 @@ void buffer_to_file(struct buffer *buffer) { buffer->name); return; } - // TODO: handle errors - FILE *file = fopen(buffer->filename, "w"); + FILE *file = fopen(buffer->filename, "w"); if (file == NULL) { minibuffer_echo("failed to open file %s for writing: %s", buffer->filename, strerror(errno)); @@ -377,7 +456,19 @@ void buffer_to_file(struct buffer *buffer) { fclose(file); } +void buffer_write_to(struct buffer *buffer, const char *filename) { + buffer->filename = strdup(filename); + buffer_to_file(buffer); +} + int buffer_add_text(struct buffer *buffer, uint8_t *text, uint32_t nbytes) { + // invalidate last paste + g_kill_ring.paste_up_to_date = false; + + /* If we currently have a selection active, + * replace it with the text to insert. */ + maybe_delete_region(buffer); + uint32_t lines_added, cols_added; text_insert_at(buffer->text, buffer->dot.line, buffer->dot.col, text, nbytes, &lines_added, &cols_added); @@ -434,9 +525,9 @@ void buffer_set_mark_at(struct buffer *buffer, uint32_t line, uint32_t col) { struct cmdbuf { struct command_list *cmds; - uint32_t scroll_line; + struct buffer_location scroll; uint32_t line_offset; - uint32_t col_offset; + uint32_t left_margin; uint32_t width; struct region region; @@ -447,16 +538,28 @@ struct cmdbuf { }; 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; + uint32_t visual_line = line->line - cmdbuf->scroll.line + cmdbuf->line_offset; + for (uint32_t hooki = 0; hooki < cmdbuf->nlinerender_hooks; ++hooki) { struct line_render_hook *hook = &cmdbuf->line_render_hooks[hooki]; hook->callback(line, visual_line, cmdbuf->cmds, hook->userdata); } + uint32_t scroll_bytes = + utf8_nbytes(line->text, line->nbytes, cmdbuf->scroll.col); + uint8_t *text = line->text + scroll_bytes; + uint32_t text_nbytes = + scroll_bytes > line->nbytes ? 0 : line->nbytes - scroll_bytes; + uint32_t text_nchars = + scroll_bytes > line->nchars ? 0 : line->nchars - cmdbuf->scroll.col; + command_list_set_show_whitespace(cmdbuf->cmds, true); struct buffer_location *begin = &cmdbuf->region.begin, *end = &cmdbuf->region.end; + + // should we draw region if (cmdbuf->mark_set && line->line >= begin->line && line->line <= end->line) { uint32_t byte_offset = 0; @@ -464,15 +567,16 @@ void render_line(struct text_chunk *line, void *userdata) { // draw any text on the line that should not be part of region if (begin->line == line->line) { - if (begin->col > 0) { - uint32_t nbytes = utf8_nbytes(line->text, line->nbytes, begin->col); - command_list_draw_text(cmdbuf->cmds, cmdbuf->col_offset, visual_line, - line->text, nbytes); + if (begin->col > cmdbuf->scroll.col) { + uint32_t nbytes = + utf8_nbytes(text, text_nbytes, begin->col - cmdbuf->scroll.col); + command_list_draw_text(cmdbuf->cmds, cmdbuf->left_margin, visual_line, + text, nbytes); byte_offset += nbytes; } - col_offset = begin->col; + col_offset = begin->col - cmdbuf->scroll.col; } // activate region color @@ -480,38 +584,38 @@ void render_line(struct text_chunk *line, void *userdata) { // draw any text on line that should be part of region if (end->line == line->line) { - if (end->col > 0) { + if (end->col > cmdbuf->scroll.col) { uint32_t nbytes = - utf8_nbytes(line->text + byte_offset, line->nbytes - byte_offset, - end->col - col_offset); - command_list_draw_text(cmdbuf->cmds, cmdbuf->col_offset + col_offset, - visual_line, line->text + byte_offset, nbytes); + utf8_nbytes(text + byte_offset, text_nbytes - byte_offset, + end->col - col_offset - cmdbuf->scroll.col); + command_list_draw_text(cmdbuf->cmds, cmdbuf->left_margin + col_offset, + visual_line, text + byte_offset, nbytes); byte_offset += nbytes; } - col_offset = end->col; + col_offset = end->col - cmdbuf->scroll.col; command_list_reset_color(cmdbuf->cmds); } // draw rest of line - if (line->nbytes - byte_offset > 0) { - command_list_draw_text(cmdbuf->cmds, cmdbuf->col_offset + col_offset, - visual_line, line->text + byte_offset, - line->nbytes - byte_offset); + if (text_nbytes - byte_offset > 0) { + command_list_draw_text(cmdbuf->cmds, cmdbuf->left_margin + col_offset, + visual_line, text + byte_offset, + text_nbytes - byte_offset); } // done rendering region command_list_reset_color(cmdbuf->cmds); } else { - command_list_draw_text(cmdbuf->cmds, cmdbuf->col_offset, visual_line, - line->text, line->nbytes); + command_list_draw_text(cmdbuf->cmds, cmdbuf->left_margin, visual_line, text, + text_nbytes); } command_list_set_show_whitespace(cmdbuf->cmds, false); - uint32_t col = line->nchars + cmdbuf->col_offset; - for (uint32_t bytei = 0; bytei < line->nbytes; ++bytei) { + uint32_t col = text_nchars + cmdbuf->left_margin; + for (uint32_t bytei = scroll_bytes; bytei < line->nbytes; ++bytei) { if (line->text[bytei] == '\t') { col += 3; } else if (utf8_byte_is_unicode_start(line->text[bytei])) { @@ -522,30 +626,34 @@ void render_line(struct text_chunk *line, void *userdata) { } } - if (cmdbuf->width > line->nchars) { + if (col < cmdbuf->width) { command_list_draw_repeated(cmdbuf->cmds, col, visual_line, ' ', - cmdbuf->width - col + cmdbuf->col_offset); + cmdbuf->width - col); } } void scroll(struct buffer *buffer, int line_delta, int col_delta) { uint32_t nlines = text_num_lines(buffer->text); - int64_t new_line = (int64_t)buffer->scroll_line + line_delta; + int64_t new_line = (int64_t)buffer->scroll.line + line_delta; if (new_line >= 0 && new_line < nlines) { - buffer->scroll_line = (uint32_t)new_line; + buffer->scroll.line = (uint32_t)new_line; + } else if (new_line < 0) { + buffer->scroll.line = 0; } - int64_t new_col = (int64_t)buffer->scroll_col + col_delta; + int64_t new_col = (int64_t)buffer->scroll.col + col_delta; if (new_col >= 0 && new_col < text_line_length(buffer->text, buffer->dot.line)) { - buffer->scroll_col = (uint32_t)new_col; + buffer->scroll.col = (uint32_t)new_col; + } else if (new_col < 0) { + buffer->scroll.col = 0; } } void to_relative(struct buffer *buffer, uint32_t line, uint32_t col, int64_t *rel_line, int64_t *rel_col) { - *rel_col = (int64_t)col - (int64_t)buffer->scroll_col; - *rel_line = (int64_t)line - (int64_t)buffer->scroll_line; + *rel_col = (int64_t)col - (int64_t)buffer->scroll.col; + *rel_line = (int64_t)line - (int64_t)buffer->scroll.line; } uint32_t visual_dot_col(struct buffer *buffer, uint32_t dot_col) { @@ -707,36 +815,36 @@ void buffer_update(struct buffer *buffer, uint32_t width, uint32_t height, to_relative(buffer, buffer->dot.line, buffer->dot.col, &rel_line, &rel_col); int line_delta = 0, col_delta = 0; if (rel_line < 0) { - line_delta = -(int)height / 2; + line_delta = rel_line - ((int)height / 2); } else if (rel_line >= height) { - line_delta = height / 2; + line_delta = (rel_line - height) + height / 2; } if (rel_col < 0) { - col_delta = rel_col; + col_delta = rel_col - ((int)width / 2); } else if (rel_col > width) { - col_delta = rel_col - width; + col_delta = (rel_col - width) + width / 2; } scroll(buffer, line_delta, col_delta); struct cmdbuf cmdbuf = (struct cmdbuf){ .cmds = commands, - .scroll_line = buffer->scroll_line, - .col_offset = total_margins.left, - .width = width, + .scroll = buffer->scroll, + .left_margin = total_margins.left, + .width = total_width, .line_offset = total_margins.top, .line_render_hooks = line_hooks, .nlinerender_hooks = nlinehooks, .mark_set = buffer->mark_set, .region = to_region(buffer->dot, buffer->mark), }; - text_for_each_line(buffer->text, buffer->scroll_line, height, render_line, + text_for_each_line(buffer->text, buffer->scroll.line, height, render_line, &cmdbuf); // draw empty lines uint32_t nlines = text_num_lines(buffer->text); - for (uint32_t linei = nlines - buffer->scroll_line + total_margins.top; + for (uint32_t linei = nlines - buffer->scroll.line + total_margins.top; linei < height; ++linei) { command_list_draw_repeated(commands, 0, linei, ' ', total_width); } |
