From 6c2963f91b90f73da862ac9feac25cdcc1266869 Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Tue, 15 Dec 2020 14:03:49 -0500 Subject: text insertion working very well! --- base.h | 1 + buffer.c | 119 +++++++++++++++++++++++++++++++++++++++++++++++-------------- main.c | 8 +++++ string32.c | 50 ++++++++++++++++++++++++++ util.c | 19 ++++++++++ 5 files changed, 170 insertions(+), 27 deletions(-) create mode 100644 string32.c diff --git a/base.h b/base.h index 114563f..65c8f20 100644 --- a/base.h +++ b/base.h @@ -17,6 +17,7 @@ #include #include #include +#include typedef uint8_t u8; typedef uint16_t u16; diff --git a/buffer.c b/buffer.c index 42c5ee3..47fd1ba 100644 --- a/buffer.c +++ b/buffer.c @@ -2,6 +2,7 @@ #include "unicode.h" #include "util.c" #include "text.h" +#include "string32.c" // a position in the buffer typedef struct { @@ -11,6 +12,7 @@ typedef struct { typedef struct { u32 len; + u32 capacity; char32_t *str; } Line; @@ -19,6 +21,7 @@ typedef struct { Font *font; BufferPos cursor_pos; u8 tab_width; + bool out_of_mem; // set to true if an allocation fails float x1, y1, x2, y2; u32 nlines; Line *lines; @@ -30,19 +33,49 @@ void buffer_create(TextBuffer *buffer, Font *font) { buffer->tab_width = 4; } -static Status buffer_line_append_char(Line *line, char32_t c) { - if (line->len == 0 || util_is_power_of_2(line->len)) { - // when the length of a line gets to a power of 2, double its allocated size - size_t new_size = line->len == 0 ? 1 : line->len * 2; - line->str = realloc(line->str, new_size * sizeof *line->str); - if (!line->str) { +// grow capacity of line to at least minimum_capacity +// returns true if allocation was succesful +static Status buffer_line_grow(TextBuffer *buffer, Line *line, u32 minimum_capacity) { + while (line->capacity < minimum_capacity) { + // double capacity of line + u32 new_capacity = line->capacity == 0 ? 4 : line->capacity * 2; + if (new_capacity < line->capacity) { + // overflow. this could only happen with an extremely long line, so we + // treat it as + buffer->out_of_mem = true; return false; } + char32_t *new_str = realloc(line->str, new_capacity * sizeof *line->str); + if (!new_str) { + // allocation failed ): + buffer->out_of_mem = true; + return false; + } + // allocation successful + line->str = new_str; + line->capacity = new_capacity; } - line->str[line->len++] = c; return true; } +static void buffer_line_append_char(TextBuffer *buffer, Line *line, char32_t c) { + if (buffer_line_grow(buffer, line, line->len + 1)) + line->str[line->len++] = c; +} + +// Does not free the pointer `buffer` (buffer might not have even been allocated with malloc) +void buffer_free(TextBuffer *buffer) { + Line *lines = buffer->lines; + u32 nlines = buffer->nlines; + for (u32 i = 0; i < nlines; ++i) { + free(lines[i].str); + } + free(lines); + buffer->nlines = 0; + buffer->lines = NULL; +} + + // fp should be a binary file Status buffer_load_file(TextBuffer *buffer, FILE *fp) { assert(fp); @@ -97,10 +130,7 @@ Status buffer_load_file(TextBuffer *buffer, FILE *fp) { u32 line_idx = buffer->nlines - 1; Line *line = &buffer->lines[line_idx]; - if (!buffer_line_append_char(line, c)) { - success = false; - break; - } + buffer_line_append_char(buffer, line, c); } } } @@ -108,10 +138,7 @@ Status buffer_load_file(TextBuffer *buffer, FILE *fp) { } if (ferror(fp)) success = false; if (!success) { - for (u32 i = 0; i < buffer->nlines; ++i) - free(buffer->lines[i].str); - free(buffer->lines); - buffer->nlines = 0; + buffer_free(buffer); } return success; @@ -134,18 +161,6 @@ static void buffer_print(TextBuffer const *buffer) { fflush(stdout); } -// Does not free the pointer `buffer` (buffer might not have even been allocated with malloc) -void buffer_free(TextBuffer *buffer) { - Line *lines = buffer->lines; - u32 nlines = buffer->nlines; - for (u32 i = 0; i < nlines; ++i) { - free(lines[i].str); - } - free(lines); - buffer->nlines = 0; - buffer->lines = NULL; -} - static u32 buffer_index_to_column(TextBuffer *buffer, u32 line, u32 index) { char32_t *str = buffer->lines[line].str; u32 col = 0; @@ -469,6 +484,56 @@ static void buffer_scroll_to_cursor(TextBuffer *buffer) { buffer_correct_scroll(buffer); // it's possible that min/max_scroll_x/y go too far } +void buffer_insert_text_at_cursor(TextBuffer *buffer, char32_t const *text, size_t len) { + u32 cur_line_idx = buffer->cursor_pos.line; + u32 cur_index = buffer->cursor_pos.index; + char32_t const *end = text + len; + + // `text` could consist of multiple lines, e.g. U"line 1\nline 2", + // so we need to go through them one by one + while (text != end) { + char32_t const *end_of_line = util_mem32chr_const(text, U'\n', len); + if (!end_of_line) end_of_line = end; + + u32 text_line_len = (u32)(end_of_line - text); + Line *cur_line = &buffer->lines[cur_line_idx]; + u32 old_len = cur_line->len; + u32 new_len = old_len + text_line_len; + if (new_len > old_len) { // handles both overflow and empty text lines + if (buffer_line_grow(buffer, cur_line, new_len)) { + // make space for text + memmove(cur_line->str + cur_index + (new_len - old_len), + cur_line->str + cur_index, + (old_len - cur_index) * sizeof *text); + // insert text + memcpy(cur_line->str + cur_index, text, text_line_len * sizeof *text); + + cur_line->len = new_len; + } + + text += text_line_len; + len -= text_line_len; + cur_index = buffer->cursor_pos.index += text_line_len; + } + + // @TODO: deal with multiple lines + } + + buffer_scroll_to_cursor(buffer); +} + +void buffer_insert_char_at_cursor(TextBuffer *buffer, char32_t c) { + buffer_insert_text_at_cursor(buffer, &c, 1); +} + +void buffer_insert_utf8_at_cursor(TextBuffer *buffer, char const *utf8) { + String32 s32 = s32_from_utf8(utf8); + if (s32.str) { + buffer_insert_text_at_cursor(buffer, s32.str, s32.len); + s32_free(&s32); + } +} + bool buffer_cursor_move_left(TextBuffer *buffer) { if (buffer_pos_move_left(buffer, &buffer->cursor_pos)) { buffer_scroll_to_cursor(buffer); diff --git a/main.c b/main.c index ac5fcc4..6d9e046 100644 --- a/main.c +++ b/main.c @@ -116,8 +116,16 @@ int main(void) { case SDLK_DOWN: buffer_cursor_move_down(&text_buffer); break; + case SDLK_RETURN: + //buffer_insert_text_at_cursor(buffer, U"\n", 1); + break; } } break; + case SDL_TEXTINPUT: { + char *text = event.text.text; + printf("TEXT: %s\n",text); + buffer_insert_utf8_at_cursor(&text_buffer, text); + } break; } } diff --git a/string32.c b/string32.c new file mode 100644 index 0000000..9b14e30 --- /dev/null +++ b/string32.c @@ -0,0 +1,50 @@ +// UTF-32 string +typedef struct { + size_t len; + char32_t *str; +} String32; + +void s32_free(String32 *s) { + free(s->str); + s->str = NULL; + s->len = 0; +} + +// the string returned should be s32_free'd. +// this will return an empty string if the allocation failed or the string is invalid UTF-8 +String32 s32_from_utf8(char const *utf8) { + String32 string = {0, NULL}; + size_t len = strlen(utf8); + if (len) { + // the wide string uses at most as many "characters" (elements?) as the UTF-8 string + char32_t *widestr = calloc(len, sizeof *widestr); + if (widestr) { + char32_t *wide_p = widestr; + char const *utf8_p = utf8; + char const *utf8_end = utf8_p + len; + mbstate_t mbstate = {0}; + while (utf8_p < utf8_end) { + char32_t c = 0; + size_t n = mbrtoc32(&c, utf8_p, (size_t)(utf8_end - utf8_p), &mbstate); + if (n == 0// null character. this shouldn't happen. + || n == (size_t)(-2) // incomplete character + || n == (size_t)(-1) // invalid UTF-8 + ) { + free(widestr); + widestr = wide_p = NULL; + break; + } else if (n == (size_t)(-3)) { // no bytes consumed, but a character was produced + *wide_p++ = c; + } else { + // n bytes consumed + *wide_p++ = c; + utf8_p += n; + } + } + string.str = widestr; + string.len = (size_t)(wide_p - widestr); + } + } + return string; +} + diff --git a/util.c b/util.c index e183461..09da480 100644 --- a/util.c +++ b/util.c @@ -33,4 +33,23 @@ static double util_mind(double a, double b) { return a < b ? a : b; } +// for finding a character in a char32 string +static char32_t *util_mem32chr(char32_t *s, char32_t c, size_t n) { + for (size_t i = 0; i < n; ++i) { + if (s[i] == c) { + return &s[i]; + } + } + return NULL; +} + +static char32_t const *util_mem32chr_const(char32_t const *s, char32_t c, size_t n) { + for (size_t i = 0; i < n; ++i) { + if (s[i] == c) { + return &s[i]; + } + } + return NULL; +} + #endif -- cgit v1.2.3