diff options
| -rw-r--r-- | src/buffer.c | 11 | ||||
| -rw-r--r-- | src/buffer.h | 1 | ||||
| -rw-r--r-- | src/text.c | 122 | ||||
| -rw-r--r-- | src/text.h | 11 | ||||
| -rw-r--r-- | test/text.c | 40 |
5 files changed, 129 insertions, 56 deletions
diff --git a/src/buffer.c b/src/buffer.c index e08bca5..fb071da 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -98,14 +98,13 @@ void moveh(struct buffer *buffer, int coldelta) { } } +void buffer_forward_delete_char(struct buffer *buffer) { + text_delete(buffer->text, buffer->dot_line, buffer->dot_col, 1); +} + void buffer_backward_delete_char(struct buffer *buffer) { - // TODO: merge lines - if (text_line_length(buffer->text, buffer->dot_line) == 0) { - text_delete_line(buffer->text, buffer->dot_line); - } else if (buffer->dot_col > 0) { - text_delete(buffer->text, buffer->dot_line, buffer->dot_col - 1, 1); - } moveh(buffer, -1); + buffer_forward_delete_char(buffer); } void buffer_backward_char(struct buffer *buffer) { moveh(buffer, -1); } diff --git a/src/buffer.h b/src/buffer.h index 1b73505..66096b9 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -40,6 +40,7 @@ void buffer_add_keymap(struct buffer *buffer, struct keymap *keymap); int buffer_add_text(struct buffer *buffer, uint8_t *text, uint32_t nbytes); +void buffer_forward_delete_char(struct buffer *buffer); void buffer_backward_delete_char(struct buffer *buffer); void buffer_backward_char(struct buffer *buffer); void buffer_forward_char(struct buffer *buffer); @@ -48,6 +48,28 @@ void text_destroy(struct text *text) { free(text->lines); } +// given `char_idx` as a character index, return the byte index +uint32_t charidx_to_byteidx(struct line *line, uint32_t char_idx) { + if (char_idx > line->nchars) { + return line->nbytes; + } + return utf8_nbytes(line->data, char_idx); +} + +// TODO: grapheme clusters +// given `byte_idx` as a byte index, return the character index +uint32_t byteidx_to_charidx(struct line *line, uint32_t byte_idx) { + if (byte_idx > line->nbytes) { + return line->nchars; + } + + return utf8_nchars(line->data, byte_idx); +} + +uint32_t char_byte_size(struct line *line, uint32_t byte_idx) { + return utf8_nbytes(line->data + byte_idx, 1); +} + void append_to_line(struct line *line, uint32_t col, uint8_t *text, uint32_t len, uint32_t nchars) { @@ -58,13 +80,19 @@ void append_to_line(struct line *line, uint32_t col, uint8_t *text, line->nbytes += len; line->nchars += nchars; line->flags = LineChanged; - line->data = realloc(line->data, line->nbytes + len); + line->data = realloc(line->data, line->nbytes); + + uint32_t bytei = charidx_to_byteidx(line, col); // move chars out of the way - memmove(line->data + col + 1, line->data + col, line->nbytes - (col + 1)); + if (col + nchars < line->nchars) { + uint32_t nextcbytei = charidx_to_byteidx(line, col + nchars); + memmove(line->data + nextcbytei, line->data + bytei, + line->nbytes - nextcbytei); + } // insert new chars - memcpy(line->data + col, text, len); + memcpy(line->data + bytei, text, len); } uint32_t text_line_length(struct text *text, uint32_t lineidx) { @@ -77,28 +105,6 @@ uint32_t text_line_size(struct text *text, uint32_t lineidx) { uint32_t text_num_lines(struct text *text) { return text->nlines; } -// given `char_idx` as a character index, return the byte index -uint32_t charidx_to_byteidx(struct line *line, uint32_t char_idx) { - if (char_idx > line->nchars) { - return line->nchars; - } - return utf8_nbytes(line->data, char_idx); -} - -// TODO: grapheme clusters -// given `byte_idx` as a byte index, return the character index -uint32_t byteidx_to_charidx(struct line *line, uint32_t byte_idx) { - if (byte_idx > line->nbytes) { - return line->nchars; - } - - return utf8_nchars(line->data, byte_idx); -} - -uint32_t char_byte_size(struct line *line, uint32_t byte_idx) { - return utf8_nbytes(line->data + byte_idx, 1); -} - void split_line(uint32_t col, struct line *line, struct line *next) { uint8_t *data = line->data; uint32_t nbytes = line->nbytes; @@ -169,7 +175,7 @@ void new_line_at(struct text *text, uint32_t line, uint32_t col) { split_line(col, pl, cl); } -void text_delete_line(struct text *text, uint32_t line) { +void delete_line(struct text *text, uint32_t line) { // always keep a single line if (text->nlines == 1) { return; @@ -230,20 +236,49 @@ void text_append(struct text *text, uint32_t line, uint32_t col, uint8_t *bytes, void text_delete(struct text *text, uint32_t line, uint32_t col, uint32_t nchars) { - struct line *lp = &text->lines[line]; - uint32_t max_chars = nchars > lp->nchars ? lp->nchars : nchars; - if (lp->nchars > 0) { - // get byte index and size for char to remove - uint32_t bytei = charidx_to_byteidx(lp, col); - uint32_t nbytes = utf8_nbytes(lp->data + bytei, max_chars); + // delete chars from current line + struct line *lp = &text->lines[line]; + uint32_t chars_initial_line = + col + nchars > lp->nchars ? (lp->nchars - col) : nchars; + uint32_t bytei = charidx_to_byteidx(lp, col); + uint32_t nbytes = utf8_nbytes(lp->data + bytei, chars_initial_line); + + 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; + } - memcpy(lp->data + bytei, lp->data + bytei + nbytes, - lp->nbytes - (bytei + nbytes)); + if (line != initial_line) { + struct line *lp = &text->lines[line]; + uint32_t bytei = charidx_to_byteidx(lp, src_col); + if (src_col < lp->nchars) { + append_to_line(&text->lines[initial_line], col, lp->data + bytei, + lp->nbytes - bytei, lp->nchars - src_col); + } + } - lp->nbytes -= nbytes; - lp->nchars -= max_chars; - lp->flags |= LineChanged; + // delete all lines from current line + 1 to (and including) last line + for (uint32_t li = initial_line + 1; li <= line; ++li) { + delete_line(text, li); } } @@ -272,11 +307,16 @@ uint32_t text_render(struct text *text, uint32_t line, uint32_t nlines, return ncmds; } +void text_for_each_chunk(struct text *text, chunk_cb callback) { + // if representation of text is changed, this can be changed as well + text_for_each_line(text, 0, text->nlines, callback); +} + void text_for_each_line(struct text *text, uint32_t line, uint32_t nlines, - line_cb callback) { + chunk_cb callback) { for (uint32_t li = line; li < (line + nlines); ++li) { struct line *src_line = &text->lines[li]; - struct txt_line line = (struct txt_line){ + struct text_chunk line = (struct text_chunk){ .text = src_line->data, .nbytes = src_line->nbytes, .nchars = src_line->nchars, @@ -285,9 +325,9 @@ void text_for_each_line(struct text *text, uint32_t line, uint32_t nlines, } } -struct txt_line text_get_line(struct text *text, uint32_t line) { +struct text_chunk text_get_line(struct text *text, uint32_t line) { struct line *src_line = &text->lines[line]; - return (struct txt_line){ + return (struct text_chunk){ .text = src_line->data, .nbytes = src_line->nbytes, .nchars = src_line->nchars, @@ -15,7 +15,6 @@ void text_append(struct text *text, uint32_t line, uint32_t col, uint8_t *bytes, void text_delete(struct text *text, uint32_t line, uint32_t col, uint32_t nchars); -void text_delete_line(struct text *text, uint32_t line); uint32_t text_render(struct text *text, uint32_t line, uint32_t nlines, struct render_cmd *cmds, uint32_t max_ncmds); @@ -24,16 +23,18 @@ uint32_t text_num_lines(struct text *text); uint32_t text_line_length(struct text *text, uint32_t lineidx); uint32_t text_line_size(struct text *text, uint32_t lineidx); -struct txt_line { +struct text_chunk { uint8_t *text; uint32_t nbytes; uint32_t nchars; }; -typedef void (*line_cb)(struct txt_line *line); +typedef void (*chunk_cb)(struct text_chunk *chunk); void text_for_each_line(struct text *text, uint32_t line, uint32_t nlines, - line_cb callback); + chunk_cb callback); -struct txt_line text_get_line(struct text *text, uint32_t line); +void text_for_each_chunk(struct text *text, chunk_cb callback); + +struct text_chunk text_get_line(struct text *text, uint32_t line); bool text_line_contains_unicode(struct text *text, uint32_t line); diff --git a/test/text.c b/test/text.c index ec99890..9073553 100644 --- a/test/text.c +++ b/test/text.c @@ -1,4 +1,5 @@ #include "assert.h" +#include "stdio.h" #include "test.h" #include "text.h" @@ -7,7 +8,7 @@ #include <string.h> #include <wchar.h> -void assert_line_equal(struct txt_line *line) {} +void assert_line_equal(struct text_chunk *line) {} void test_add_text() { uint32_t lines_added, cols_added; @@ -25,8 +26,12 @@ void test_add_text() { const char *txt2 = "This is line 2\n"; text_append(t, 1, 0, (uint8_t *)txt2, strlen(txt2), &lines_added, &cols_added); + ASSERT(text_num_lines(t) == 3, + "Expected text to have three lines after second insertion"); ASSERT_STR_EQ((const char *)text_get_line(t, 1).text, "This is line 2", "Expected line 2 to be line 2"); + + text_destroy(t); } void test_delete_text() { @@ -49,13 +54,36 @@ void test_delete_text() { const char *txt2 = "This is line 1\nThis is line 2\nThis is line 3"; text_append(t, 0, 0, (uint8_t *)txt2, strlen(txt2), &lines_added, &cols_added); + ASSERT(text_num_lines(t) == 3, + "Expected to have three lines after inserting as many"); + text_delete(t, 1, 11, 3); ASSERT(text_line_length(t, 1) == 11, "Expected line to contain 11 chars after deletion"); - struct txt_line line = text_get_line(t, 1); + struct text_chunk line = text_get_line(t, 1); ASSERT(strncmp((const char *)line.text, "This is lin", line.nbytes) == 0, "Expected deleted characters to be gone in the second line"); + text_delete(t, 1, 0, text_line_length(t, 1) + 1); + ASSERT(text_num_lines(t) == 2, + "Expected to have two lines after deleting one"); + struct text_chunk line2 = text_get_line(t, 1); + ASSERT(strncmp((const char *)line2.text, "This is line 3", line2.nbytes) == 0, + "Expected lines to have shifted upwards after deleting"); + + struct text *t3 = text_create(10); + const char *delete_me = "This is line๐\nQ"; + text_append(t3, 0, 0, (uint8_t *)delete_me, strlen(delete_me), &lines_added, + &cols_added); + text_delete(t3, 0, 13, 1); + struct text_chunk top_line = text_get_line(t3, 0); + ASSERT(strncmp((const char *)top_line.text, "This is line๐Q", + top_line.nbytes) == 0, + "Expected text from second line to be appended to first line when " + "deleting newline"); + ASSERT(text_num_lines(t3) == 1, + "Expected text to have one line after deleting newline"); + // test utf-8 struct text *t2 = text_create(10); const char *txt3 = "Emojis: ๐ซ๐ฎ ๐ฎ\n"; @@ -71,9 +99,13 @@ void test_delete_text() { text_delete(t2, 0, 10, 2); ASSERT(text_line_length(t2, 0) == 10, "Line length should be 10 after deleting the cow emoji and a space"); - struct txt_line line2 = text_get_line(t2, 0); - ASSERT(strncmp((const char *)line2.text, "Emojis: ๐ซ๐ฎ", line2.nbytes) == 0, + struct text_chunk line3 = text_get_line(t2, 0); + ASSERT(strncmp((const char *)line3.text, "Emojis: ๐ซ๐ฎ", line3.nbytes) == 0, "Expected cow emoji plus space to be deleted"); + + text_destroy(t); + text_destroy(t2); + text_destroy(t3); } void run_text_tests() { |
