diff options
| author | Albert Cervin <albert@acervin.com> | 2023-01-31 00:07:54 +0100 |
|---|---|---|
| committer | Albert Cervin <albert@acervin.com> | 2023-01-31 00:12:56 +0100 |
| commit | 689ceeca3d25cb2f738a6c52776c53912abb797f (patch) | |
| tree | d61a96445beccae2c5b99c7aa9dee77738a14379 /src | |
| parent | f90d5e1f07fdc9dea7c24b11107049b613a5be7a (diff) | |
| download | dged-689ceeca3d25cb2f738a6c52776c53912abb797f.tar.gz dged-689ceeca3d25cb2f738a6c52776c53912abb797f.tar.xz dged-689ceeca3d25cb2f738a6c52776c53912abb797f.zip | |
Implement mark and copy-paste
Also fix some memory leaks
Diffstat (limited to 'src')
| -rw-r--r-- | src/buffer.c | 283 | ||||
| -rw-r--r-- | src/buffer.h | 33 | ||||
| -rw-r--r-- | src/buffers.c | 1 | ||||
| -rw-r--r-- | src/display.c | 4 | ||||
| -rw-r--r-- | src/main.c | 2 | ||||
| -rw-r--r-- | src/minibuffer.c | 3 | ||||
| -rw-r--r-- | src/text.c | 137 | ||||
| -rw-r--r-- | src/text.h | 7 |
8 files changed, 364 insertions, 106 deletions
diff --git a/src/buffer.c b/src/buffer.c index b9ec8fc..71d486e 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -16,9 +16,13 @@ #include <unistd.h> #include <wchar.h> -struct modeline { - uint8_t *buffer; -}; +static struct modeline { uint8_t *buffer; } g_modeline = {0}; + +#define KILL_RING_SZ 64 +static struct kill_ring { + struct text_chunk buffer[KILL_RING_SZ]; + uint32_t curr_idx; +} g_kill_ring = {.curr_idx = 0}; struct update_hook_result buffer_linenum_hook(struct buffer *buffer, struct command_list *commands, @@ -33,18 +37,20 @@ struct update_hook_result buffer_modeline_hook(struct buffer *buffer, void *userdata); struct buffer buffer_create(char *name, bool modeline) { - struct buffer b = - (struct buffer){.filename = NULL, - .name = strdup(name), - .text = text_create(10), - .dot_col = 0, - .dot_line = 0, - .keymaps = calloc(10, sizeof(struct keymap)), - .nkeymaps = 1, - .scroll_col = 0, - .scroll_line = 0, - .update_hooks = {0}, - .nkeymaps_max = 10}; + struct buffer b = (struct buffer){ + .filename = NULL, + .name = strdup(name), + .text = text_create(10), + .dot = {0}, + .mark = {0}, + .mark_set = false, + .keymaps = calloc(10, sizeof(struct keymap)), + .nkeymaps = 1, + .scroll_col = 0, + .scroll_line = 0, + .update_hooks = {0}, + .nkeymaps_max = 10, + }; b.keymaps[0] = keymap_create("buffer-default", 128); struct binding bindings[] = { @@ -71,14 +77,19 @@ struct buffer buffer_create(char *name, bool modeline) { BINDING(DELETE, "delete-char"), BINDING(Ctrl, 'D', "delete-char"), BINDING(BACKSPACE, "backward-delete-char"), + + BINDING(Ctrl, '@', "set-mark"), + + BINDING(Ctrl, 'W', "cut"), + BINDING(Ctrl, 'Y', "paste"), + BINDING(Meta, 'w', "copy"), }; keymap_bind_keys(&b.keymaps[0], bindings, sizeof(bindings) / sizeof(bindings[0])); if (modeline) { - struct modeline *modeline = calloc(1, sizeof(struct modeline)); - modeline->buffer = malloc(1024); - buffer_add_update_hook(&b, buffer_modeline_hook, modeline); + g_modeline.buffer = malloc(1024); + buffer_add_update_hook(&b, buffer_modeline_hook, &g_modeline); } if (modeline) { @@ -93,11 +104,19 @@ void buffer_destroy(struct buffer *buffer) { free(buffer->text); free(buffer->name); free(buffer->filename); + for (uint32_t keymapi = 0; keymapi < buffer->nkeymaps; ++keymapi) { + keymap_destroy(&buffer->keymaps[keymapi]); + } + free(buffer->keymaps); + + if (g_modeline.buffer != NULL) { + free(g_modeline.buffer); + } } void buffer_clear(struct buffer *buffer) { text_clear(buffer->text); - buffer->dot_col = buffer->dot_line = 0; + buffer->dot.col = buffer->dot.line = 0; } bool buffer_is_empty(struct buffer *buffer) { @@ -121,53 +140,125 @@ void buffer_add_keymap(struct buffer *buffer, struct keymap *keymap) { } bool movev(struct buffer *buffer, int rowdelta) { - int64_t new_line = (int64_t)buffer->dot_line + rowdelta; + int64_t new_line = (int64_t)buffer->dot.line + rowdelta; if (new_line < 0) { - buffer->dot_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; + buffer->dot.line = text_num_lines(buffer->text) - 1; return false; } else { - buffer->dot_line = (uint32_t)new_line; + buffer->dot.line = (uint32_t)new_line; // make sure column stays on the line - uint32_t linelen = text_line_length(buffer->text, buffer->dot_line); - buffer->dot_col = buffer->dot_col > linelen ? linelen : buffer->dot_col; + uint32_t linelen = text_line_length(buffer->text, buffer->dot.line); + buffer->dot.col = buffer->dot.col > linelen ? linelen : buffer->dot.col; return true; } } // move dot `coldelta` chars void moveh(struct buffer *buffer, int coldelta) { - int64_t new_col = (int64_t)buffer->dot_col + coldelta; + int64_t new_col = (int64_t)buffer->dot.col + coldelta; - if (new_col > (int64_t)text_line_length(buffer->text, buffer->dot_line)) { + if (new_col > (int64_t)text_line_length(buffer->text, buffer->dot.line)) { if (movev(buffer, 1)) { - buffer->dot_col = 0; + buffer->dot.col = 0; } } else if (new_col < 0) { if (movev(buffer, -1)) { - buffer->dot_col = text_line_length(buffer->text, buffer->dot_line); + buffer->dot.col = text_line_length(buffer->text, buffer->dot.line); } } else { - buffer->dot_col = new_col; + buffer->dot.col = new_col; + } +} + +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; +} + +bool region_has_size(struct buffer *buffer) { + return buffer->mark_set && + (labs((int64_t)buffer->mark.line - (int64_t)buffer->dot.line + 1) * + 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]; + if (curr != NULL) { + free(curr->text); + } + + struct text_chunk txt = + text_get_region(buffer->text, region.begin.line, region.begin.col, + region.end.line, region.end.col); + *curr = txt; + return curr; +} + +void buffer_copy(struct buffer *buffer) { + if (region_has_size(buffer)) { + struct region reg = to_region(buffer->dot, buffer->mark); + struct text_chunk *curr = copy_region(buffer, reg); + buffer_clear_mark(buffer); + } +} + +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); + } +} + +void buffer_cut(struct buffer *buffer) { + if (region_has_size(buffer)) { + struct region reg = to_region(buffer->dot, buffer->mark); + copy_region(buffer, reg); + text_delete(buffer->text, reg.begin.line, reg.begin.col, reg.end.line, + reg.end.col); + buffer_clear_mark(buffer); } } void buffer_kill_line(struct buffer *buffer) { - uint32_t nchars = - text_line_length(buffer->text, buffer->dot_line) - buffer->dot_col; + uint32_t nchars = text_line_length(buffer->text, buffer->dot.line); if (nchars == 0) { nchars = 1; } - text_delete(buffer->text, buffer->dot_line, buffer->dot_col, nchars); + struct region reg = { + .begin = buffer->dot, + .end = + { + .line = buffer->dot.line, + .col = buffer->dot.col + nchars, + }, + }; + copy_region(buffer, reg); + text_delete(buffer->text, buffer->dot.line, buffer->dot.col, buffer->dot.line, + buffer->dot.col + nchars); } void buffer_forward_delete_char(struct buffer *buffer) { - text_delete(buffer->text, buffer->dot_line, buffer->dot_col, 1); + 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) { @@ -180,9 +271,9 @@ void buffer_forward_char(struct buffer *buffer) { moveh(buffer, 1); } void buffer_forward_word(struct buffer *buffer) { moveh(buffer, 1); - struct text_chunk line = text_get_line(buffer->text, buffer->dot_line); + struct text_chunk line = text_get_line(buffer->text, buffer->dot.line); uint32_t bytei = - text_col_to_byteindex(buffer->text, buffer->dot_line, buffer->dot_col); + text_col_to_byteindex(buffer->text, buffer->dot.line, buffer->dot.col); for (; bytei < line.nbytes; ++bytei) { uint8_t b = line.text[bytei]; if (b == ' ' || b == '.') { @@ -191,15 +282,15 @@ void buffer_forward_word(struct buffer *buffer) { } uint32_t target_col = - text_byteindex_to_col(buffer->text, buffer->dot_line, bytei); - moveh(buffer, target_col - buffer->dot_col); + text_byteindex_to_col(buffer->text, buffer->dot.line, bytei); + moveh(buffer, target_col - buffer->dot.col); } void buffer_backward_word(struct buffer *buffer) { moveh(buffer, -1); - struct text_chunk line = text_get_line(buffer->text, buffer->dot_line); + struct text_chunk line = text_get_line(buffer->text, buffer->dot.line); uint32_t bytei = - text_col_to_byteindex(buffer->text, buffer->dot_line, buffer->dot_col); + text_col_to_byteindex(buffer->text, buffer->dot.line, buffer->dot.col); for (; bytei > 0; --bytei) { uint8_t b = line.text[bytei]; if (b == ' ' || b == '.') { @@ -208,18 +299,18 @@ void buffer_backward_word(struct buffer *buffer) { } uint32_t target_col = - text_byteindex_to_col(buffer->text, buffer->dot_line, bytei); - moveh(buffer, (int32_t)target_col - buffer->dot_col); + text_byteindex_to_col(buffer->text, buffer->dot.line, bytei); + moveh(buffer, (int32_t)target_col - buffer->dot.col); } void buffer_backward_line(struct buffer *buffer) { movev(buffer, -1); } void buffer_forward_line(struct buffer *buffer) { movev(buffer, 1); } void buffer_end_of_line(struct buffer *buffer) { - buffer->dot_col = text_line_length(buffer->text, buffer->dot_line); + buffer->dot.col = text_line_length(buffer->text, buffer->dot.line); } -void buffer_beginning_of_line(struct buffer *buffer) { buffer->dot_col = 0; } +void buffer_beginning_of_line(struct buffer *buffer) { buffer->dot.col = 0; } struct buffer buffer_from_file(char *filename) { struct buffer b = buffer_create(basename((char *)filename), true); @@ -288,13 +379,13 @@ void buffer_to_file(struct buffer *buffer) { int buffer_add_text(struct buffer *buffer, uint8_t *text, uint32_t nbytes) { uint32_t lines_added, cols_added; - text_insert_at(buffer->text, buffer->dot_line, buffer->dot_col, text, nbytes, + text_insert_at(buffer->text, buffer->dot.line, buffer->dot.col, text, nbytes, &lines_added, &cols_added); movev(buffer, lines_added); if (lines_added > 0) { // does not make sense to use position from another line - buffer->dot_col = 0; + buffer->dot.col = 0; } moveh(buffer, cols_added); @@ -306,6 +397,7 @@ void buffer_newline(struct buffer *buffer) { } void buffer_indent(struct buffer *buffer) { + // TODO: config buffer_add_text(buffer, (uint8_t *)" ", 4); } @@ -322,12 +414,34 @@ uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook, return buffer->update_hooks.nhooks - 1; } +void buffer_set_mark(struct buffer *buffer) { + buffer->mark_set + ? buffer_clear_mark(buffer) + : buffer_set_mark_at(buffer, buffer->dot.line, buffer->dot.col); +} + +void buffer_clear_mark(struct buffer *buffer) { + buffer->mark_set = false; + minibuffer_echo_timeout(2, "mark cleared"); +} + +void buffer_set_mark_at(struct buffer *buffer, uint32_t line, uint32_t col) { + buffer->mark_set = true; + buffer->mark.line = line; + buffer->mark.col = col; + minibuffer_echo_timeout(2, "mark set"); +} + struct cmdbuf { struct command_list *cmds; uint32_t scroll_line; uint32_t line_offset; uint32_t col_offset; uint32_t width; + + struct region region; + bool mark_set; + struct line_render_hook *line_render_hooks; uint32_t nlinerender_hooks; }; @@ -341,8 +455,59 @@ void render_line(struct text_chunk *line, void *userdata) { } command_list_set_show_whitespace(cmdbuf->cmds, true); - command_list_draw_text(cmdbuf->cmds, cmdbuf->col_offset, visual_line, - line->text, line->nbytes); + struct buffer_location *begin = &cmdbuf->region.begin, + *end = &cmdbuf->region.end; + if (cmdbuf->mark_set && line->line >= begin->line && + line->line <= end->line) { + uint32_t byte_offset = 0; + uint32_t col_offset = 0; + + // draw any text on the line that should not be part of region + if (begin->line == line->line) { + if (begin->col > 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); + + byte_offset += nbytes; + } + + col_offset = begin->col; + } + + // activate region color + command_list_set_index_color_bg(cmdbuf->cmds, 5); + + // draw any text on line that should be part of region + if (end->line == line->line) { + if (end->col > 0) { + 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); + byte_offset += nbytes; + } + + col_offset = end->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); + } + + // 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_set_show_whitespace(cmdbuf->cmds, false); uint32_t col = line->nchars + cmdbuf->col_offset; @@ -372,7 +537,7 @@ void scroll(struct buffer *buffer, int line_delta, int 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)) { + new_col < text_line_length(buffer->text, buffer->dot.line)) { buffer->scroll_col = (uint32_t)new_col; } } @@ -385,10 +550,10 @@ void to_relative(struct buffer *buffer, uint32_t line, uint32_t col, uint32_t visual_dot_col(struct buffer *buffer, uint32_t dot_col) { uint32_t visual_dot_col = dot_col; - struct text_chunk line = text_get_line(buffer->text, buffer->dot_line); + struct text_chunk line = text_get_line(buffer->text, buffer->dot.line); for (uint32_t bytei = 0; bytei < - text_col_to_byteindex(buffer->text, buffer->dot_line, buffer->dot_col); + text_col_to_byteindex(buffer->text, buffer->dot.line, buffer->dot.col); ++bytei) { if (line.text[bytei] == '\t') { visual_dot_col += 3; @@ -424,8 +589,8 @@ struct update_hook_result buffer_modeline_hook(struct buffer *buffer, struct tm *lt = localtime(&now); char left[128], right[128]; - snprintf(left, 128, " %-16s (%d, %d)", buffer->name, buffer->dot_line + 1, - visual_dot_col(buffer, buffer->dot_col)); + snprintf(left, 128, " %-16s (%d, %d)", buffer->name, buffer->dot.line + 1, + visual_dot_col(buffer, buffer->dot.col)); snprintf(right, 128, "(%.2f ms) %02d:%02d", frame_time / 1e6, lt->tm_hour, lt->tm_min); @@ -497,7 +662,7 @@ struct update_hook_result buffer_linenum_hook(struct buffer *buffer, } linenum_data.longest_nchars = longest_nchars; - linenum_data.dot_line = buffer->dot_line; + linenum_data.dot_line = buffer->dot.line; struct update_hook_result res = {0}; res.margins.left = longest_nchars + 2; res.line_render_hook.callback = linenum_render_hook; @@ -539,7 +704,7 @@ void buffer_update(struct buffer *buffer, uint32_t width, uint32_t height, } int64_t rel_line, rel_col; - to_relative(buffer, buffer->dot_line, buffer->dot_col, &rel_line, &rel_col); + to_relative(buffer, buffer->dot.line, buffer->dot.col, &rel_line, &rel_col); int line_delta = 0, col_delta = 0; if (rel_line < 0) { line_delta = -(int)height / 2; @@ -563,6 +728,8 @@ void buffer_update(struct buffer *buffer, uint32_t width, uint32_t height, .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, &cmdbuf); @@ -575,9 +742,9 @@ void buffer_update(struct buffer *buffer, uint32_t width, uint32_t height, } // update the visual cursor position - to_relative(buffer, buffer->dot_line, buffer->dot_col, &rel_line, &rel_col); - uint32_t visual_col = visual_dot_col(buffer, buffer->dot_col); - to_relative(buffer, buffer->dot_line, visual_col, &rel_line, &rel_col); + to_relative(buffer, buffer->dot.line, buffer->dot.col, &rel_line, &rel_col); + uint32_t visual_col = visual_dot_col(buffer, buffer->dot.col); + to_relative(buffer, buffer->dot.line, visual_col, &rel_line, &rel_col); *relline = rel_line < 0 ? 0 : (uint32_t)rel_line + total_margins.top; *relcol = rel_col < 0 ? 0 : (uint32_t)rel_col + total_margins.left; diff --git a/src/buffer.h b/src/buffer.h index a565a6e..14389e7 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -74,6 +74,11 @@ struct update_hooks { uint32_t nhooks; }; +struct buffer_location { + uint32_t line; + uint32_t col; +}; + /** * A buffer of text that can be modified, read from and written to disk. * @@ -81,13 +86,18 @@ struct update_hooks { * implemented on top of it. */ struct buffer { + + /** Buffer name */ char *name; + /** Associated filename, this is where the buffer will be saved to */ char *filename; + /** Text data structure */ struct text *text; - uint32_t dot_line; - uint32_t dot_col; + struct buffer_location dot; + struct buffer_location mark; + bool mark_set; // local keymaps struct keymap *keymaps; @@ -124,6 +134,15 @@ void buffer_beginning_of_line(struct buffer *buffer); void buffer_newline(struct buffer *buffer); void buffer_indent(struct buffer *buffer); +void buffer_set_mark(struct buffer *buffer); +void buffer_clear_mark(struct buffer *buffer); +void buffer_set_mark_at(struct buffer *buffer, uint32_t line, uint32_t col); + +void buffer_copy(struct buffer *buffer); +void buffer_paste(struct buffer *buffer); +void buffer_paste_older(struct buffer *buffer); +void buffer_cut(struct buffer *buffer); + struct text_chunk buffer_get_line(struct buffer *buffer, uint32_t line); uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook, @@ -158,6 +177,11 @@ BUFFER_WRAPCMD(buffer_beginning_of_line); BUFFER_WRAPCMD(buffer_newline); BUFFER_WRAPCMD(buffer_indent); BUFFER_WRAPCMD(buffer_to_file); +BUFFER_WRAPCMD(buffer_set_mark); +BUFFER_WRAPCMD(buffer_clear_mark); +BUFFER_WRAPCMD(buffer_copy); +BUFFER_WRAPCMD(buffer_cut); +BUFFER_WRAPCMD(buffer_paste); static struct command BUFFER_COMMANDS[] = { {.name = "kill-line", .fn = buffer_kill_line_cmd}, @@ -174,4 +198,9 @@ static struct command BUFFER_COMMANDS[] = { {.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}, }; diff --git a/src/buffers.c b/src/buffers.c index 479caa5..38b51b7 100644 --- a/src/buffers.c +++ b/src/buffers.c @@ -39,4 +39,5 @@ void buffers_destroy(struct buffers *buffers) { } buffers->nbuffers = 0; + free(buffers->buffers); } diff --git a/src/display.c b/src/display.c index 2f147d5..39993ef 100644 --- a/src/display.c +++ b/src/display.c @@ -127,9 +127,9 @@ void putbyte(uint8_t c) { void putbyte_ws(uint8_t c, bool show_whitespace) { if (show_whitespace && c == '\t') { - fputs("\x1b[90m → \x1b[0m", stdout); + fputs("\x1b[90m → \x1b[39m", stdout); } else if (show_whitespace && c == ' ') { - fputs("\x1b[90m·\x1b[0m", stdout); + fputs("\x1b[90m·\x1b[39m", stdout); } else { putbyte(c); } @@ -309,7 +309,9 @@ int main(int argc, char *argv[]) { display_clear(display); display_destroy(display); keymap_destroy(&global_keymap); + keymap_destroy(&ctrlx_map); command_registry_destroy(&commands); + reactor_destroy(reactor); frame_allocator_destroy(&frame_allocator); return 0; diff --git a/src/minibuffer.c b/src/minibuffer.c index 5bf5043..41c669c 100644 --- a/src/minibuffer.c +++ b/src/minibuffer.c @@ -99,6 +99,8 @@ void minibuffer_init(struct buffer *buffer) { } g_minibuffer.buffer = buffer; + g_minibuffer.keymap = keymap_create("minibuffer", 10); + struct binding bindings[] = { ANONYMOUS_BINDING(Ctrl, 'M', &execute_minibuffer_command), ANONYMOUS_BINDING(Ctrl, 'I', &complete_minibuffer_command), @@ -106,6 +108,7 @@ void minibuffer_init(struct buffer *buffer) { keymap_bind_keys(&g_minibuffer.keymap, bindings, sizeof(bindings) / sizeof(bindings[0])); buffer_add_keymap(g_minibuffer.buffer, &g_minibuffer.keymap); + buffer_add_update_hook(g_minibuffer.buffer, update, &g_minibuffer); } @@ -255,56 +255,44 @@ void text_insert_at(struct text *text, uint32_t line, uint32_t col, *lines_added = line - start_line; } -void text_delete(struct text *text, uint32_t line, uint32_t col, - uint32_t nchars) { +void text_delete(struct text *text, uint32_t start_line, uint32_t start_col, + uint32_t end_line, uint32_t end_col) { - struct line *lp = &text->lines[line]; - if (col > lp->nchars) { + struct line *firstline = &text->lines[start_line]; + struct line *lastline = &text->lines[end_line]; + if (start_col > firstline->nchars) { return; } - // delete chars from current line - uint32_t chars_initial_line = - col + nchars > lp->nchars ? (lp->nchars - col) : nchars; - uint32_t bytei = charidx_to_byteidx(lp, col); - uint32_t nbytes = - utf8_nbytes(lp->data + bytei, lp->nbytes - bytei, chars_initial_line); - - memcpy(lp->data + bytei, lp->data + bytei + nbytes, - lp->nbytes - (bytei + nbytes)); - - lp->nbytes -= nbytes; - lp->nchars -= chars_initial_line; - lp->flags |= LineChanged; - - uint32_t initial_line = line; - uint32_t left_to_delete = nchars - chars_initial_line; - - // grab remaining chars from last line to delete from (if any) - uint32_t src_col = 0; - while (left_to_delete > 0 && line < text->nlines) { - ++line; - --left_to_delete; // newline char - - struct line *lp = &text->lines[line]; - uint32_t deleted_in_line = - left_to_delete > lp->nchars ? lp->nchars : left_to_delete; - src_col = deleted_in_line; - left_to_delete -= deleted_in_line; + // handle deletion of newlines + if (end_col > lastline->nchars) { + ++end_line; + end_col = 0; + lastline = &text->lines[end_line]; } - if (line != initial_line) { - struct line *lp = &text->lines[line]; - uint32_t bytei = charidx_to_byteidx(lp, src_col); - if (src_col < lp->nchars) { - insert_at_col(&text->lines[initial_line], col, lp->data + bytei, - lp->nbytes - bytei, lp->nchars - src_col); - } + uint32_t bytei = utf8_nbytes(lastline->data, lastline->nbytes, end_col); + if (lastline == firstline) { + // in this case we can "overwrite" + uint32_t dstbytei = + utf8_nbytes(firstline->data, firstline->nbytes, start_col); + memcpy(firstline->data + dstbytei, lastline->data + bytei, + lastline->nbytes - bytei); + } else { + // otherwise we actually have to copy from the last line + insert_at_col(firstline, start_col, lastline->data + bytei, + lastline->nbytes - bytei, lastline->nchars - end_col); } - // delete all lines from current line + 1 to (and including) last line - for (uint32_t li = initial_line + 1; li <= line && li < text->nlines; ++li) { - delete_line(text, li); + firstline->nchars = start_col + (lastline->nchars - end_col); + firstline->nbytes = + utf8_nbytes(firstline->data, firstline->nbytes, start_col) + + (lastline->nbytes - bytei); + + // delete full lines + for (uint32_t linei = start_line + 1; + linei <= end_line && linei < text->nlines; ++linei) { + delete_line(text, linei); } } @@ -339,6 +327,71 @@ struct text_chunk text_get_line(struct text *text, uint32_t line) { }; } +struct copy_cmd { + uint32_t line; + uint32_t byteindex; + uint32_t nbytes; +}; + +struct text_chunk text_get_region(struct text *text, uint32_t start_line, + uint32_t start_col, uint32_t end_line, + uint32_t end_col) { + struct copy_cmd *copy_cmds = malloc(end_line - start_line + 1); + + uint32_t total_chars = 0, total_bytes = 0; + for (uint32_t line = start_line; line <= end_line; ++line) { + struct line *l = &text->lines[line]; + total_chars += l->nchars; + total_bytes += l->nbytes; + + struct copy_cmd *cmd = ©_cmds[line - start_line]; + cmd->line = line; + cmd->byteindex = 0; + cmd->nbytes = l->nbytes; + + ++line; + } + + // correct first line + struct copy_cmd *cmd_first = ©_cmds[start_line]; + struct line *first_line = &text->lines[start_line]; + uint32_t byteoff = + utf8_nbytes(first_line->data, first_line->nbytes, start_col); + cmd_first->byteindex += byteoff; + cmd_first->nbytes -= byteoff; + total_bytes -= byteoff; + total_chars -= start_col; + + // correct last line + struct copy_cmd *cmd_last = ©_cmds[end_line]; + struct line *last_line = &text->lines[end_line]; + uint32_t byteindex = utf8_nbytes(last_line->data, last_line->nbytes, end_col); + cmd_last->nbytes -= (last_line->nchars - end_col); + total_bytes -= (last_line->nbytes - byteindex); + total_chars -= (last_line->nchars - end_col); + + struct text_chunk txt = { + .text = (uint8_t *)malloc(total_bytes + end_line - start_line), + .line = 0, + .nbytes = total_bytes, + .nchars = total_chars, + }; + + // copy data + for (uint32_t cmdi = 0, curr = 0; cmdi <= end_line - start_line; ++cmdi) { + struct copy_cmd *c = ©_cmds[cmdi]; + struct line *l = &text->lines[c->line]; + memcpy(txt.text + curr, l->data + c->byteindex, c->nbytes); + curr += c->nbytes; + + if (cmdi != end_line - start_line) { + txt.text[++curr] = '\n'; + } + } + + return txt; +} + bool text_line_contains_unicode(struct text *text, uint32_t line) { return text->lines[line].nbytes != text->lines[line].nchars; } @@ -22,8 +22,8 @@ void text_insert_at(struct text *text, uint32_t line, uint32_t col, void text_append(struct text *text, uint8_t *bytes, uint32_t nbytes, uint32_t *lines_added, uint32_t *cols_added); -void text_delete(struct text *text, uint32_t line, uint32_t col, - uint32_t nchars); +void text_delete(struct text *text, uint32_t start_line, uint32_t start_col, + uint32_t end_line, uint32_t end_col); uint32_t text_num_lines(struct text *text); uint32_t text_line_length(struct text *text, uint32_t lineidx); @@ -46,5 +46,8 @@ void text_for_each_line(struct text *text, uint32_t line, uint32_t nlines, void text_for_each_chunk(struct text *text, chunk_cb callback, void *userdata); struct text_chunk text_get_line(struct text *text, uint32_t line); +struct text_chunk text_get_region(struct text *text, uint32_t start_line, + uint32_t start_col, uint32_t end_line, + uint32_t end_col); bool text_line_contains_unicode(struct text *text, uint32_t line); |
