diff options
| author | Albert Cervin <albert@acervin.com> | 2022-11-02 22:20:04 +0100 |
|---|---|---|
| committer | Albert Cervin <albert@acervin.com> | 2022-11-16 23:33:49 +0100 |
| commit | 2f4cb88d5c60f725323739300bb49dfa8923e7d5 (patch) | |
| tree | 6ec22c2be92eff05f18e5919e747faab56e555ad /src/text.c | |
| download | dged-2f4cb88d5c60f725323739300bb49dfa8923e7d5.tar.gz dged-2f4cb88d5c60f725323739300bb49dfa8923e7d5.tar.xz dged-2f4cb88d5c60f725323739300bb49dfa8923e7d5.zip | |
🎉 And so it begins
Diffstat (limited to 'src/text.c')
| -rw-r--r-- | src/text.c | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/src/text.c b/src/text.c new file mode 100644 index 0000000..4162e94 --- /dev/null +++ b/src/text.c @@ -0,0 +1,299 @@ +#include "text.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "display.h" +#include "utf8.h" + +enum flags { + LineChanged = 1 << 0, +}; + +struct line { + uint8_t *data; + uint8_t flags; + uint32_t nbytes; + uint32_t nchars; +}; + +struct text { + // raw bytes without any null terminators + struct line *lines; + uint32_t nlines; + uint32_t capacity; +}; + +struct text *text_create(uint32_t initial_capacity) { + struct text *txt = calloc(1, sizeof(struct text)); + txt->lines = calloc(initial_capacity, sizeof(struct line)); + txt->capacity = initial_capacity; + + // we always have one line, since add line adds a second one + txt->nlines = 1; + + return txt; +} + +void text_destroy(struct text *text) { + for (uint32_t li = 0; li < text->nlines; ++li) { + free(text->lines[li].data); + text->lines[li].data = NULL; + text->lines[li].flags = 0; + text->lines[li].nbytes = 0; + text->lines[li].nchars = 0; + } + + free(text->lines); +} + +void append_to_line(struct line *line, uint32_t col, uint8_t *text, + uint32_t len, uint32_t nchars) { + + if (len == 0) { + return; + } + + line->nbytes += len; + line->nchars += nchars; + line->flags = LineChanged; + line->data = realloc(line->data, line->nbytes + len); + + // move chars out of the way + memmove(line->data + col + 1, line->data + col, line->nbytes - (col + 1)); + + // insert new chars + memcpy(line->data + col, text, len); +} + +uint32_t text_line_length(struct text *text, uint32_t lineidx) { + return text->lines[lineidx].nchars; +} + +uint32_t text_line_size(struct text *text, uint32_t lineidx) { + return text->lines[lineidx].nbytes; +} + +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; + uint32_t nchars = line->nchars; + + uint32_t chari = col; + uint32_t bytei = charidx_to_byteidx(line, chari); + + line->nbytes = bytei; + line->nchars = chari; + next->nbytes = nbytes - bytei; + next->nchars = nchars - chari; + line->flags = next->flags = line->flags; + + // first, handle some cases where the new line or the pre-existing one is + // empty + if (next->nbytes == 0) { + line->data = data; + } else if (line->nbytes == 0) { + next->data = data; + } else { + // actually split the line + next->data = (uint8_t *)malloc(next->nbytes); + memcpy(next->data, data + bytei, next->nbytes); + + line->data = (uint8_t *)malloc(line->nbytes); + memcpy(line->data, data, line->nbytes); + + free(data); + } +} + +void mark_lines_changed(struct text *text, uint32_t line, uint32_t nlines) { + for (uint32_t linei = line; linei < (line + nlines); ++linei) { + text->lines[linei].flags |= LineChanged; + } +} + +void shift_lines(struct text *text, uint32_t start, int32_t direction) { + struct line *dest = text->lines + ((int64_t)start + direction); + struct line *src = text->lines + start; + uint32_t nlines = text->nlines - (dest > src ? (start + direction) : start); + memmove(dest, src, nlines * sizeof(struct line)); +} + +void new_line_at(struct text *text, uint32_t line, uint32_t col) { + if (text->nlines == text->capacity) { + text->capacity *= 2; + text->lines = realloc(text->lines, sizeof(struct line) * text->capacity); + } + + struct line *nline = &text->lines[text->nlines]; + nline->data = NULL; + nline->nbytes = 0; + nline->nchars = 0; + nline->flags = 0; + + ++text->nlines; + + mark_lines_changed(text, line, text->nlines - line); + + // move following lines out of the way + shift_lines(text, line + 1, 1); + + // split line if needed + struct line *pl = &text->lines[line]; + struct line *cl = &text->lines[line + 1]; + split_line(col, pl, cl); +} + +void text_delete_line(struct text *text, uint32_t line) { + // always keep a single line + if (text->nlines == 1) { + return; + } + + mark_lines_changed(text, line, text->nlines - line); + free(text->lines[line].data); + text->lines[line].data = NULL; + + shift_lines(text, line + 1, -1); + + if (text->nlines > 0) { + --text->nlines; + text->lines[text->nlines].data = NULL; + text->lines[text->nlines].nbytes = 0; + text->lines[text->nlines].nchars = 0; + } +} + +void text_append(struct text *text, uint32_t line, uint32_t col, uint8_t *bytes, + uint32_t nbytes, uint32_t *lines_added, uint32_t *cols_added) { + uint32_t linelen = 0; + uint32_t nchars_counted = 0; + uint32_t nlines_added = 0; + uint32_t ncols_added = 0; + for (uint32_t bytei = 0; bytei < nbytes; ++bytei) { + uint8_t byte = bytes[bytei]; + if (byte == '\n') { + append_to_line(&text->lines[line], col, bytes + (bytei - linelen), + linelen, nchars_counted); + + col += nchars_counted; + new_line_at(text, line, col); + ++line; + ++nlines_added; + + col = text_line_length(text, line); + linelen = 0; + nchars_counted = 0; + } else { + if (utf8_byte_is_ascii(byte) || utf8_byte_is_unicode_start(byte)) { + ++nchars_counted; + } + ++linelen; + } + } + + // handle remaining + if (linelen > 0) { + append_to_line(&text->lines[line], col, bytes + (nbytes - linelen), linelen, + nchars_counted); + ncols_added = nchars_counted; + } + + *lines_added = nlines_added; + *cols_added = ncols_added; +} + +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); + + memcpy(lp->data + bytei, lp->data + bytei + nbytes, + lp->nbytes - (bytei + nbytes)); + + lp->nbytes -= nbytes; + lp->nchars -= max_chars; + lp->flags |= LineChanged; + } +} + +uint32_t text_render(struct text *text, uint32_t line, uint32_t nlines, + struct render_cmd *cmds, uint32_t max_ncmds) { + uint32_t nlines_max = nlines > text->capacity ? text->capacity : nlines; + + uint32_t ncmds = 0; + for (uint32_t lineidx = line; lineidx < nlines_max; ++lineidx) { + struct line *lp = &text->lines[lineidx]; + if (lp->flags & LineChanged) { + + cmds[ncmds] = (struct render_cmd){ + .row = lineidx, + .col = 0, // TODO: do not redraw full line + .data = lp->data, + .len = lp->nbytes, + }; + + lp->flags &= ~(LineChanged); + + ++ncmds; + } + } + + return ncmds; +} + +void text_for_each_line(struct text *text, uint32_t line, uint32_t nlines, + line_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){ + .text = src_line->data, + .nbytes = src_line->nbytes, + .nchars = src_line->nchars, + }; + callback(&line); + } +} + +struct txt_line text_get_line(struct text *text, uint32_t line) { + struct line *src_line = &text->lines[line]; + return (struct txt_line){ + .text = src_line->data, + .nbytes = src_line->nbytes, + .nchars = src_line->nchars, + }; +} + +bool text_line_contains_unicode(struct text *text, uint32_t line) { + return text->lines[line].nbytes != text->lines[line].nchars; +} |
