ledit

Text editor (WIP)
git clone git://lumidify.org/ledit.git (fast, but not encrypted)
git clone https://lumidify.org/git/ledit.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

commit 8779bb819724ae7d05491112ae700063f49cde93
parent 0e379b0cf1ba991e2024b2541a9d5d4f80068d5b
Author: lumidify <nobody@lumidify.org>
Date:   Sat, 12 Jun 2021 22:49:54 +0200

Use gap buffer for text

This probably doesn't make anything more efficient and
really just makes everything more complicated, but whatever.

Diffstat:
Mbuffer.c | 540++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mbuffer.h | 76+++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mledit.c | 4++++
Msearch.c | 8++++++++
4 files changed, 495 insertions(+), 133 deletions(-)

diff --git a/buffer.c b/buffer.c @@ -1,4 +1,5 @@ /* FIXME: shrink buffers when text length less than a fourth of the size */ +/* FIXME: also cache PangoLayouts since keeping them around isn't really of much use? */ #include <string.h> #include <assert.h> @@ -13,11 +14,21 @@ #include "buffer.h" #include "cache.h" +/* + * Important notes: + * - Lines must be null-terminated for some things to work (e.g. text search), + * but the '\0' is not included in 'len'. + * - When the line is not normalized, the '\0' is not always included! This + * should maybe be changed for consistency, but for now, the resizing just + * always makes sure to add one so there's enough space when the text is + * normalized. This is a bit ugly, but oh well. +*/ + static PangoAttrList *basic_attrs = NULL; static void init_line(ledit_buffer *buffer, ledit_line *line); -static void recalc_line_size_absolute(ledit_buffer *buffer); -static void recalc_single_line_size(ledit_buffer *buffer, int line_index); +/*static void delete_line_section(ledit_buffer *buffer, int line, int start, int length);*/ +static void delete_line_section_base(ledit_buffer *buffer, int line, int start, int length); /* FIXME: destroy basic_attrs somewhere */ ledit_buffer * @@ -35,6 +46,7 @@ ledit_create_buffer(ledit_common_state *state) { buffer->lines_cap = 0; buffer->cur_line = 0; buffer->cur_index = 0; + /* FIXME: trailing currently not used */ buffer->trailing = 0; buffer->trailing_bytes = 0; buffer->end_of_soft_line = 0; @@ -42,23 +54,53 @@ ledit_create_buffer(ledit_common_state *state) { buffer->display_offset = 0; buffer->sel.line1 = buffer->sel.byte1 = -1; buffer->sel.line2 = buffer->sel.byte2 = -1; - ledit_append_line(buffer, -1, -1); + ledit_append_line_base(buffer, -1, -1); + ledit_recalc_all_lines(buffer); return buffer; } void ledit_destroy_buffer(ledit_buffer *buffer) { + ledit_line *l; for (int i = 0; i < buffer->lines_num; i++) { - g_object_unref(buffer->lines[i].layout); - free(buffer->lines[i].text); + l = ledit_get_line(buffer, i); + g_object_unref(l->layout); + free(l->text); } free(buffer->lines); free(buffer); } void +ledit_normalize_line(ledit_line *line) { + if (line->gap < line->len) { + memmove( + line->text + line->gap, + line->text + line->gap + line->cap - line->len, + line->len - line->gap + ); + line->gap = line->len; + } + line->text[line->len] = '\0'; +} + +static void +err_text_dirty(ledit_buffer *buffer, int line) { + fprintf( + stderr, + "WARNING: Line had text_dirty or h_dirty attribute " + "set when rendering. Fix your code!\n" + ); + ledit_recalc_from_line(buffer, line); +} + +void ledit_set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end_byte) { + ledit_line *l = ledit_get_line(buffer, line); + if (l->text_dirty) + err_text_dirty(buffer, line); + /* FIXME: configure color */ PangoAttribute *attr0 = pango_attr_background_new(0, 0, 0); PangoAttribute *attr1 = pango_attr_foreground_new(65535, 65535, 65535); attr0->start_index = start_byte; @@ -70,12 +112,15 @@ ledit_set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end pango_attr_list_insert(list, attr0); pango_attr_list_insert(list, attr1); pango_attr_list_insert(list, attr2); - pango_layout_set_attributes(buffer->lines[line].layout, list); - buffer->lines[line].dirty = 1; + pango_layout_set_attributes(l->layout, list); + l->dirty = 1; } void ledit_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index) { + ledit_line *l = ledit_get_line(buffer, line); + if (l->text_dirty) + err_text_dirty(buffer, line); if (buffer->state->mode == NORMAL) { PangoAttribute *attr0 = pango_attr_background_new(0, 0, 0); PangoAttribute *attr1 = pango_attr_foreground_new(65535, 65535, 65535); @@ -88,17 +133,18 @@ ledit_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index) { pango_attr_list_insert(list, attr0); pango_attr_list_insert(list, attr1); pango_attr_list_insert(list, attr2); - pango_layout_set_attributes(buffer->lines[line].layout, list); + pango_layout_set_attributes(l->layout, list); } else { - pango_layout_set_attributes(buffer->lines[line].layout, basic_attrs); + pango_layout_set_attributes(l->layout, basic_attrs); } - buffer->lines[line].dirty = 1; + l->dirty = 1; } void ledit_wipe_line_cursor_attrs(ledit_buffer *buffer, int line) { - pango_layout_set_attributes(buffer->lines[line].layout, basic_attrs); - buffer->lines[line].dirty = 1; + ledit_line *l = ledit_get_line(buffer, line); + pango_layout_set_attributes(l->layout, basic_attrs); + l->dirty = 1; } void @@ -106,34 +152,153 @@ ledit_insert_text_from_line( ledit_buffer *buffer, int dst_line, int dst_index, int src_line, int src_index, int src_len) { + ledit_insert_text_from_line_base( + buffer, dst_line, dst_index, src_line, src_index, src_len + ); + ledit_recalc_line(buffer, dst_line); +} + +void +ledit_insert_text_from_line_base( + ledit_buffer *buffer, + int dst_line, int dst_index, + int src_line, int src_index, int src_len) { assert(dst_line != src_line); ledit_line *ll = ledit_get_line(buffer, src_line); if (src_len == -1) src_len = ll->len - src_index; - ledit_insert_text(buffer, dst_line, dst_index, ll->text, src_len); + if (src_index >= ll->gap) { + /* all text to insert is after gap */ + ledit_insert_text_base( + buffer, dst_line, dst_index, + ll->text + src_index + ll->cap - ll->len, src_len + ); + } else if (ll->gap - src_index >= src_len) { + /* all text to insert is before gap */ + ledit_insert_text_base( + buffer, dst_line, dst_index, + ll->text + src_index, src_len + ); + } else { + /* insert part of text before gap */ + ledit_insert_text_base( + buffer, dst_line, dst_index, + ll->text + src_index, ll->gap - src_index + ); + /* insert part of text after gap */ + ledit_insert_text_base( + buffer, dst_line, dst_index + ll->gap - src_index, + ll->text + ll->gap + ll->cap - ll->len, + src_len - ll->gap + src_index + ); + + } +} + +static void +move_text_gap(ledit_line *line, int index) { + if (index > line->gap) { + /* move piece between end of original gap and + index to beginning of original gap */ + memmove( + line->text + line->gap, + line->text + line->gap + line->cap - line->len, + index - line->gap + ); + } else if (index < line->gap) { + /* move piece between index and original gap to + end of original gap */ + memmove( + line->text + index + line->cap - line->len, + line->text + index, + line->gap - index + ); + } + line->gap = index; +} + +/* This is almost certainly premature optimization and maybe + not optimization at all. */ +/* FIXME: add "final" versions of the functions that include the + normalization, i.e. if they have to move the gap anyways, they + just move it to the end */ +static void +resize_and_move_text_gap(ledit_line *line, int min_size, int index) { + int gap_size = line->cap - line->len; + /* FIXME: read up on what the best values are here */ + line->cap = line->cap * 2 > min_size ? line->cap * 2 : min_size; + line->text = ledit_realloc(line->text, line->cap); + if (index > line->gap) { + /* move piece between end of original gap and index to + beginning of original gap */ + memmove( + line->text + line->gap, + line->text + line->gap + gap_size, + index - line->gap + ); + /* move piece after index to end of buffer */ + memmove( + line->text + line->cap - (line->len - index), + line->text + index + gap_size, + line->len - index + ); + } else if (index < line->gap) { + /* move piece after original gap to end of buffer */ + memmove( + line->text + line->cap - (line->len - line->gap), + line->text + line->gap + gap_size, + line->len - line->gap + ); + /* move piece between index and original gap to end */ + memmove( + line->text + line->cap - line->len + index, + line->text + index, + line->gap - index + ); + } else { + /* move piece after original gap to end of buffer */ + memmove( + line->text + line->cap - (line->len - line->gap), + line->text + line->gap + gap_size, + line->len - line->gap + ); + } + line->gap = index; } void ledit_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, int len) { + ledit_insert_text_base(buffer, line_index, index, text, len); + ledit_recalc_line(buffer, line_index); +} + +void +ledit_insert_text_base(ledit_buffer *buffer, int line_index, int index, char *text, int len) { ledit_line *line = &buffer->lines[line_index]; if (len == -1) len = strlen(text); /* \0 is not included in line->len */ - if (line->len + len + 1 > line->cap || line->text == NULL) { - /* FIXME: read up on what the best values are here */ - line->cap = line->cap * 2 > line->len + len + 1 ? line->cap * 2 : line->len + len + 1; - line->text = ledit_realloc(line->text, line->cap); - } - memmove(line->text + index + len, line->text + index, line->len - index); + if (line->len + len + 1 > line->cap || line->text == NULL) + resize_and_move_text_gap(line, line->len + len + 1, index); + else + move_text_gap(line, index); + /* the gap is now located at 'index' and least large enough to hold the new text */ memcpy(line->text + index, text, len); + line->gap += len; line->len += len; - line->text[line->len] = '\0'; - pango_layout_set_text(line->layout, line->text, line->len); - /*recalc_single_line_size(buffer, line_index);*/ line->dirty = 1; + line->text_dirty = 1; + line->h_dirty = 1; } -static void append_line_impl(ledit_buffer *buffer, int line_index, int text_index); +/* FIXME: implement this - if it is known that this is the last insertion, + move gap to end immediately if the gap needs to be enlarged anyways to + avoid another copy before rendering */ +/* +void +ledit_insert_text_final(ledit_buffer *buffer, int line_index, int index, char *text, int len) { +} +*/ /* FIXME: this isn't optimized like the standard version, but whatever */ static char * @@ -145,12 +310,32 @@ strchr_len(char *text, char c, int len) { return NULL; } +/* FIXME: make these functions that call recalc* also be final as described above */ void ledit_insert_text_with_newlines( ledit_buffer *buffer, int line_index, int index, char *text, int len, int *end_line_ret, int *end_char_ret) { + int end; + ledit_insert_text_with_newlines_base( + buffer, line_index, index, text, len, + &end, end_char_ret + ); + if (end_line_ret) + *end_line_ret = end; + if (line_index == end) + ledit_recalc_line(buffer, line_index); + else + ledit_recalc_from_line(buffer, line_index); +} + +void +ledit_insert_text_with_newlines_base( + ledit_buffer *buffer, + int line_index, int index, + char *text, int len, + int *end_line_ret, int *end_char_ret) { if (len == -1) len = strlen(text); int rem_len = len; @@ -158,27 +343,30 @@ ledit_insert_text_with_newlines( int cur_line = line_index; int cur_index = index; while ((cur = strchr_len(last, '\n', rem_len)) != NULL) { - ledit_insert_text(buffer, cur_line, cur_index, last, cur - last); + ledit_insert_text_base(buffer, cur_line, cur_index, last, cur - last); /* FIXME: inefficient because there's no gap buffer yet */ - append_line_impl(buffer, cur_line, -1); + ledit_append_line_base(buffer, cur_line, -1); cur_index = 0; cur_line++; last = cur + 1; rem_len -= cur - last + 1; } /* FIXME: check how legal this casting between pointers and ints is */ - ledit_insert_text(buffer, cur_line, cur_index, last, text + len - last); + ledit_insert_text_base(buffer, cur_line, cur_index, last, text + len - last); if (end_line_ret) *end_line_ret = cur_line; if (end_char_ret) *end_char_ret = cur_index + text + len - last; - recalc_line_size_absolute(buffer); /* FIXME: make this more efficient */ } +/* FIXME: standardize variable names (line/line_index, etc.) */ void ledit_render_line(ledit_buffer *buffer, int line_index) { /* FIXME: check for <= 0 on size */ ledit_line *line = &buffer->lines[line_index]; + /* this shouldn't happen if the functions here are used correctly */ + if (line->text_dirty || line->h_dirty) + err_text_dirty(buffer, line_index); if (line->cache_index == -1) ledit_assign_free_cache_index(buffer, line_index); ledit_cache_pixmap *pix = ledit_get_cache_pixmap(line->cache_index); @@ -211,7 +399,6 @@ ledit_render_line(ledit_buffer *buffer, int line_index) { line->dirty = 0; } -/* FIXME: use proper "viewport width" instead of just subtracting 10 */ static void init_line(ledit_buffer *buffer, ledit_line *line) { line->parent_buffer = buffer; @@ -220,21 +407,32 @@ init_line(ledit_buffer *buffer, ledit_line *line) { pango_layout_set_font_description(line->layout, buffer->state->font); pango_layout_set_wrap(line->layout, PANGO_WRAP_WORD_CHAR); pango_layout_set_attributes(line->layout, basic_attrs); + line->gap = 0; line->cap = 2; /* arbitrary */ line->text = ledit_malloc(line->cap); line->text[0] = '\0'; line->len = 0; line->cache_index = -1; line->dirty = 1; + line->text_dirty = 1; + line->h_dirty = 1; + /* FIXME: Is line->w needed? I don't think so. */ + line->w = line->h = 0; /* FIXME: does this set line height reasonably when no text yet? */ pango_layout_get_pixel_size(line->layout, &line->w, &line->h); line->w = buffer->state->text_w; line->y_offset = 0; } +void +ledit_append_line(ledit_buffer *buffer, int line_index, int text_index) { + ledit_append_line_base(buffer, line_index, text_index); + ledit_recalc_from_line(buffer, line_index); +} + /* FIXME: error checking (index out of bounds, etc.) */ -static void -append_line_impl(ledit_buffer *buffer, int line_index, int text_index) { +void +ledit_append_line_base(ledit_buffer *buffer, int line_index, int text_index) { if (buffer->lines_num >= buffer->lines_cap) { buffer->lines_cap *= 2; if (buffer->lines_cap == 0) @@ -248,38 +446,41 @@ append_line_impl(ledit_buffer *buffer, int line_index, int text_index) { buffer->lines + line_index + 1, (buffer->lines_num - (line_index + 1)) * sizeof(ledit_line) ); - ledit_line *new_l = &buffer->lines[line_index + 1]; + ledit_line *new_l = ledit_get_line(buffer, line_index + 1); init_line(buffer, new_l); buffer->lines_num++; if (text_index != -1) { - /* FIXME: use ledit_insert... here */ - ledit_line *l = &buffer->lines[line_index]; - int len = l->len - text_index; - new_l->len = len; - new_l->cap = len + 10; - new_l->text = ledit_malloc(new_l->cap); - memcpy(new_l->text, l->text + text_index, len); - new_l->text[new_l->len] = '\0'; - l->len = text_index; - l->text[l->len] = '\0'; - pango_layout_set_text(new_l->layout, new_l->text, new_l->len); - pango_layout_set_text(l->layout, l->text, l->len); - /* FIXME: set height here */ + ledit_line *l = ledit_get_line(buffer, line_index); + ledit_insert_text_from_line_base( + buffer, line_index + 1, 0, + line_index, text_index, -1 + ); + delete_line_section_base( + buffer, line_index, + text_index, l->len - text_index + ); } } +/* this is very weird and ugly with the recalc */ void -ledit_append_line(ledit_buffer *buffer, int line_index, int text_index) { - append_line_impl(buffer, line_index, text_index); - recalc_line_size_absolute(buffer); +ledit_delete_line_entries(ledit_buffer *buffer, int index1, int index2) { + ledit_delete_line_entries_base(buffer, index1, index2); + ledit_recalc_from_line(buffer, index1 > 0 ? index1 - 1 : 0); } +/* IMPORTANT: ledit_recalc_from_line needs to be called sometime after this! */ void -ledit_delete_line_entries(ledit_buffer *buffer, int index1, int index2) { +ledit_delete_line_entries_base(ledit_buffer *buffer, int index1, int index2) { + ledit_line *l; + /* FIXME: make sure this is always true */ + assert(index2 - index1 != buffer->lines_num); for (int i = index1; i <= index2; i++) { - g_object_unref(buffer->lines[i].layout); - free(buffer->lines[i].text); + l = ledit_get_line(buffer, i); + g_object_unref(l->layout); + free(l->text); } + /* FIXME: gap buffer */ if (index2 < buffer->lines_num - 1) { memmove( buffer->lines + index1, buffer->lines + index2 + 1, @@ -287,8 +488,15 @@ ledit_delete_line_entries(ledit_buffer *buffer, int index1, int index2) { ); } buffer->lines_num -= index2 - index1 + 1; - /* FIXME: avoid this by just subtracting the heights */ - recalc_line_size_absolute(buffer); + /* force a recalc of the lines */ + if (index1 == 0) { + l = ledit_get_line(buffer, 0); + l->y_offset = 0; + l->h_dirty = 1; + } else { + l = ledit_get_line(buffer, index1 - 1); + l->h_dirty = 1; + } } void @@ -296,6 +504,11 @@ ledit_delete_line_entry(ledit_buffer *buffer, int index) { ledit_delete_line_entries(buffer, index, index); } +void +ledit_delete_line_entry_base(ledit_buffer *buffer, int index) { + ledit_delete_line_entries_base(buffer, index, index); +} + /* FIXME: use some sort of gap buffer (that would make this function slightly more useful...) */ ledit_line * @@ -303,47 +516,74 @@ ledit_get_line(ledit_buffer *buffer, int index) { return &buffer->lines[index]; } -static void -recalc_single_line_size(ledit_buffer *buffer, int line_index) { +/* set text of pango layout if dirty and recalculate height of line + * - if height hasn't changed, nothing further is done + * - if height has changed, offset of all following lines is changed */ +void +ledit_recalc_line(ledit_buffer *buffer, int line) { int w, h; - ledit_line *line = &buffer->lines[line_index]; - pango_layout_get_pixel_size(line->layout, &w, &h); - /*line->w = w;*/ + ledit_line *l = ledit_get_line(buffer, line); + if (l->text_dirty) { + ledit_normalize_line(l); + pango_layout_set_text(l->layout, l->text, l->len); + l->text_dirty = 0; + } + l->h_dirty = 0; + pango_layout_get_pixel_size(l->layout, &w, &h); /* if height changed, set height of current line * and adjust offsets of all lines following it */ - if (line->h != h) { - int delta = h - line->h; - line->h = h; - buffer->total_height += delta; - if (buffer->total_height < 0) - buffer->total_height = 0; - for (int i = line_index + 1; i < buffer->lines_num; i++) { - buffer->lines[i].y_offset += delta; - if (buffer->lines[i].y_offset < 0) - buffer->lines[i].y_offset = 0; + if (l->h != h) { + long off = l->y_offset + h; + l->h = h; + for (int i = line + 1; i < buffer->lines_num; i++) { + l = ledit_get_line(buffer, i); + l->y_offset = off; + off += l->h; } + buffer->total_height = off; } } -static void -recalc_line_size_absolute(ledit_buffer *buffer) { +/* set text of pango layout and recalculate height + * and offset for all lines starting at 'line' */ +void +ledit_recalc_from_line(ledit_buffer *buffer, int line) { int w, h; - buffer->total_height = 0; - /* completely recalculate line sizes and offsets from scratch */ - for (int i = 0; i < buffer->lines_num; i++) { - buffer->lines[i].y_offset = buffer->total_height; - pango_layout_get_pixel_size(buffer->lines[i].layout, &w, &h); - buffer->total_height += h; - /*buffer->lines[i].w = w;*/ - buffer->lines[i].h = h; + ledit_line *l = ledit_get_line(buffer, line); + long off = l->y_offset; + for (int i = line; i < buffer->lines_num; i++) { + l = ledit_get_line(buffer, i); + if (l->text_dirty) { + ledit_normalize_line(l); + pango_layout_set_text(l->layout, l->text, l->len); + l->text_dirty = 0; + pango_layout_get_pixel_size(l->layout, &w, &h); + l->h = h; + } else if (l->h_dirty) { + pango_layout_get_pixel_size(l->layout, &w, &h); + l->h = h; + } + l->h_dirty = 0; + l->y_offset = off; + off += l->h; } + buffer->total_height = off; +} + +/* short for 'ledit_recalc_from_line' starting at line 0 */ +void +ledit_recalc_all_lines(ledit_buffer *buffer) { + /* force first line to offset 0, just in case */ + ledit_line *l = ledit_get_line(buffer, 0); + l->y_offset = 0; + ledit_recalc_from_line(buffer, 0); } int ledit_line_visible(ledit_buffer *buffer, int index) { - ledit_line *line = &buffer->lines[index]; - return line->y_offset < buffer->display_offset + buffer->state->h && - line->y_offset + line->h > buffer->display_offset; + ledit_line *l = ledit_get_line(buffer, index); + return l->y_offset < buffer->display_offset + buffer->state->text_h && + l->y_offset + l->h > buffer->display_offset; } /* get needed length of text range, including newlines @@ -368,6 +608,12 @@ ledit_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2) return len; } +/* FIXME: Only copy new text in selection when expanding it */ +/* FIXME: Work directly with gap buffer instead of normalizing first + -> I guess it doesn't matter right now because copying text is + only done when it is re-rendered (and thus normalized because + of pango's requirements). If a more efficient rendering + backend is added, it would be good to optimize this, though. */ /* copy text range into given buffer * - dst is null-terminated * - dst must be large enough to contain the text and NUL @@ -377,6 +623,7 @@ ledit_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2 assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); ledit_line *ll1 = ledit_get_line(buffer, line1); ledit_line *ll2 = ledit_get_line(buffer, line2); + ledit_normalize_line(ll1); if (line1 == line2) { memcpy(dst, ll1->text + byte1, byte2 - byte1); dst[byte2 - byte1] = '\0'; @@ -388,11 +635,13 @@ ledit_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2 cur_pos++; for (int i = line1 + 1; i < line2; i++) { ledit_line *ll = ledit_get_line(buffer, i); + ledit_normalize_line(ll); memcpy(dst + cur_pos, ll->text, ll->len); cur_pos += ll->len; dst[cur_pos] = '\n'; cur_pos++; } + ledit_normalize_line(ll2); memcpy(dst + cur_pos, ll2->text, byte2); cur_pos += byte2; dst[cur_pos] = '\0'; @@ -421,11 +670,14 @@ ledit_copy_text_with_resize( return len; } +/* get char with logical index i from line */ +#define LINE_CHAR(line, i) ((i) < (line)->gap ? (line)->text[i] : (line)->text[i + (line)->cap - (line)->len]) + int ledit_prev_utf8(ledit_line *line, int index) { int i = index - 1; /* find valid utf8 char - this probably needs to be improved */ - while (i > 0 && ((line->text[i] & 0xC0) == 0x80)) + while (i > 0 && ((LINE_CHAR(line, i) & 0xC0) == 0x80)) i--; return i; } @@ -433,44 +685,80 @@ ledit_prev_utf8(ledit_line *line, int index) { int ledit_next_utf8(ledit_line *line, int index) { int i = index + 1; - while (i < line->len && ((line->text[i] & 0xC0) == 0x80)) + while (i < line->len && ((LINE_CHAR(line, i) & 0xC0) == 0x80)) i++; return i; } +/* FIXME: no idea why this exists */ +/* +static void +delete_line_section(ledit_buffer *buffer, int line, int start, int length) { + delete_line_section_base(buffer, line, start, length); + ledit_recalc_line(buffer, line); +} +*/ + +static void +delete_line_section_base(ledit_buffer *buffer, int line, int start, int length) { + ledit_line *l = ledit_get_line(buffer, line); + if (start <= l->gap && start + length >= l->gap) { + l->gap = start; + } else if (start < l->gap && start + length < l->gap) { + memmove( + l->text + l->cap - l->len + start + length, + l->text + start + length, + l->gap - start - length + ); + l->gap = start; + } else { + memmove( + l->text + l->gap, + l->text + l->gap + l->cap - l->len, + start - l->gap + ); + } + l->len -= length; + l->dirty = 1; + l->text_dirty = 1; + l->h_dirty = 1; +} + int ledit_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index, int dir) { + int new_index = ledit_delete_unicode_char_base(buffer, line_index, byte_index, dir); + ledit_recalc_line(buffer, line_index); + return new_index; +} + +int +ledit_delete_unicode_char_base(ledit_buffer *buffer, int line_index, int byte_index, int dir) { ledit_line *l = ledit_get_line(buffer, line_index); int new_index = byte_index; if (dir < 0) { int i = ledit_prev_utf8(l, byte_index); - memmove(l->text + i, l->text + byte_index, l->len - byte_index); - l->len -= byte_index - i; + delete_line_section_base(buffer, line_index, i, byte_index - i); new_index = i; } else { int i = ledit_next_utf8(l, byte_index); - memmove(l->text + byte_index, l->text + i, l->len - i); - l->len -= i - byte_index; + delete_line_section_base(buffer, line_index, byte_index, i - byte_index); } - l->text[l->len] = '\0'; - pango_layout_set_text(l->layout, l->text, l->len); - recalc_single_line_size(buffer, line_index); return new_index; } static void -delete_line_section(ledit_buffer *buffer, int line, int start, int length) { - ledit_line *l = &buffer->lines[line]; - memmove(l->text + start, l->text + start + length, l->len - start - length); - l->len -= length; - l->text[l->len] = '\0'; - pango_layout_set_text(l->layout, l->text, l->len); - recalc_single_line_size(buffer, line); - l->dirty = 1; +normalize_and_set_pango_text(ledit_line *line) { + if (line->text_dirty) { + ledit_normalize_line(line); + pango_layout_set_text(line->layout, line->text, line->len); + line->text_dirty = 0; + line->h_dirty = 1; + } } void ledit_pos_to_x_softline(ledit_line *line, int pos, int *x_ret, int *softline_ret) { + normalize_and_set_pango_text(line); pango_layout_index_to_line_x(line->layout, pos, 0, softline_ret, x_ret); /* FIXME: do these lines need to be unref'd? */ PangoLayoutLine *pango_line = pango_layout_get_line_readonly(line->layout, *softline_ret); @@ -495,6 +783,7 @@ void ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret) { int trailing = 0; int x_relative = x; + normalize_and_set_pango_text(line); PangoLayoutLine *pango_line = pango_layout_get_line_readonly(line->layout, softline); /* x is absolute, so the margin at the left needs to be subtracted */ @@ -515,11 +804,13 @@ ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret) { } } +/* FIXME: make sure PangoLayout has newest text already when these functions are called */ int ledit_get_legal_normal_pos(ledit_buffer *buffer, int line, int pos) { /* move back one grapheme if at end of line */ int ret = pos; ledit_line *final_line = ledit_get_line(buffer, line); + normalize_and_set_pango_text(final_line); if (pos == final_line->len && pos > 0) { int nattrs; const PangoLogAttr *attrs = @@ -534,16 +825,35 @@ ledit_get_legal_normal_pos(ledit_buffer *buffer, int line, int pos) { return ret; } -/* FIXME: use at least somewhat sensible variable names */ void ledit_delete_range( ledit_buffer *buffer, int line_based, int line_index1, int byte_index1, int line_index2, int byte_index2, int *new_line_ret, int *new_byte_ret) { + ledit_delete_range_base( + buffer, line_based, + line_index1, byte_index1, + line_index2, byte_index2, + new_line_ret, new_byte_ret + ); + /* need to start recalculating one line before in case first + line was deleted and offset is now wrong */ + int min = line_index1 < line_index2 ? line_index1 : line_index2; + ledit_recalc_from_line(buffer, min > 0 ? min - 1 : min); +} + +/* FIXME: use at least somewhat sensible variable names */ +void +ledit_delete_range_base( + ledit_buffer *buffer, int line_based, + int line_index1, int byte_index1, + int line_index2, int byte_index2, + int *new_line_ret, int *new_byte_ret) { if (line_based) { int x, softline1, softline2; ledit_line *line1 = ledit_get_line(buffer, line_index1); + normalize_and_set_pango_text(line1); ledit_pos_to_x_softline(line1, byte_index1, &x, &softline1); if (line_index1 == line_index2) { int x_useless; @@ -553,9 +863,9 @@ ledit_delete_range( int softlines = pango_layout_get_line_count(line1->layout); PangoLayoutLine *pl1 = pango_layout_get_line_readonly(line1->layout, l1); PangoLayoutLine *pl2 = pango_layout_get_line_readonly(line1->layout, l2); - /* don't delete entire line of it is the last one remaining */ + /* don't delete entire line if it is the last one remaining */ if (l1 == 0 && l2 == softlines - 1 && buffer->lines_num > 1) { - ledit_delete_line_entry(buffer, line_index1); + ledit_delete_line_entry_base(buffer, line_index1); /* note: line_index1 is now the index of the next line since the current one was just deleted */ if (line_index1 < buffer->lines_num) { @@ -574,7 +884,7 @@ ledit_delete_range( } } else { /* FIXME: sanity checks that the length is actually positive, etc. */ - delete_line_section( + delete_line_section_base( buffer, line_index1, pl1->start_index, pl2->start_index + pl2->length - pl1->start_index ); @@ -618,6 +928,8 @@ ledit_delete_range( } ledit_line *ll1 = ledit_get_line(buffer, l1); ledit_line *ll2 = ledit_get_line(buffer, l2); + normalize_and_set_pango_text(ll1); + normalize_and_set_pango_text(ll2); pango_layout_index_to_line_x(ll1->layout, b1, 0, &sl1, &x_useless); pango_layout_index_to_line_x(ll2->layout, b2, 0, &sl2, &x_useless); PangoLayoutLine *pl1 = pango_layout_get_line_readonly(ll1->layout, sl1); @@ -625,12 +937,12 @@ ledit_delete_range( int softlines = pango_layout_get_line_count(ll2->layout); if (sl1 == 0 && sl2 == softlines - 1) { if (l1 == 0 && l2 == buffer->lines_num - 1) { - delete_line_section(buffer, l1, 0, ll1->len); - ledit_delete_line_entries(buffer, l1 + 1, l2); + delete_line_section_base(buffer, l1, 0, ll1->len); + ledit_delete_line_entries_base(buffer, l1 + 1, l2); *new_line_ret = 0; *new_byte_ret = 0; } else { - ledit_delete_line_entries(buffer, l1, l2); + ledit_delete_line_entries_base(buffer, l1, l2); if (l1 >= buffer->lines_num) { *new_line_ret = buffer->lines_num - 1; ledit_line *new_lline = ledit_get_line(buffer, *new_line_ret); @@ -645,13 +957,13 @@ ledit_delete_range( } } } else if (sl1 == 0) { - delete_line_section(buffer, l2, 0, pl2->start_index + pl2->length); - ledit_delete_line_entries(buffer, l1, l2 - 1); + delete_line_section_base(buffer, l2, 0, pl2->start_index + pl2->length); + ledit_delete_line_entries_base(buffer, l1, l2 - 1); *new_line_ret = l1; ledit_x_softline_to_pos(ledit_get_line(buffer, l1), x, 0, new_byte_ret); } else if (sl2 == softlines - 1) { - delete_line_section(buffer, l1, pl1->start_index, ll1->len - pl1->start_index); - ledit_delete_line_entries(buffer, l1 + 1, l2); + delete_line_section_base(buffer, l1, pl1->start_index, ll1->len - pl1->start_index); + ledit_delete_line_entries_base(buffer, l1 + 1, l2); if (l1 + 1 >= buffer->lines_num) { *new_line_ret = buffer->lines_num - 1; ledit_line *new_lline = ledit_get_line(buffer, *new_line_ret); @@ -666,10 +978,10 @@ ledit_delete_range( } } else { /* FIXME: should this join the two lines? */ - delete_line_section(buffer, l1, pl1->start_index, ll1->len - pl1->start_index); - delete_line_section(buffer, l2, 0, pl2->start_index + pl2->length); + delete_line_section_base(buffer, l1, pl1->start_index, ll1->len - pl1->start_index); + delete_line_section_base(buffer, l2, 0, pl2->start_index + pl2->length); if (l2 > l1 + 1) - ledit_delete_line_entries(buffer, l1 + 1, l2 - 1); + ledit_delete_line_entries_base(buffer, l1 + 1, l2 - 1); *new_line_ret = l1 + 1; ledit_x_softline_to_pos(ledit_get_line(buffer, l1 + 1), x, 0, new_byte_ret); } @@ -684,7 +996,7 @@ ledit_delete_range( b1 = byte_index2; b2 = byte_index1; } - delete_line_section(buffer, line_index1, b1, b2 - b1); + delete_line_section_base(buffer, line_index1, b1, b2 - b1); *new_line_ret = line_index1; *new_byte_ret = b1; } else { @@ -704,7 +1016,7 @@ ledit_delete_range( ledit_line *line2 = ledit_get_line(buffer, l2); line1->len = b1; if (b2 > 0) { - ledit_insert_text( + ledit_insert_text_base( buffer, l1, b1, line2->text + b2, line2->len - b2 @@ -712,7 +1024,7 @@ ledit_delete_range( } *new_line_ret = l1; *new_byte_ret = b1; - ledit_delete_line_entries(buffer, l1 + 1, l2); + ledit_delete_line_entries_base(buffer, l1 + 1, l2); } if (buffer->state->mode == NORMAL) *new_byte_ret = ledit_get_legal_normal_pos(buffer, *new_line_ret, *new_byte_ret); diff --git a/buffer.h b/buffer.h @@ -12,13 +12,17 @@ typedef struct { PangoLayout *layout; char *text; ledit_buffer *parent_buffer; + int gap; /* position of gap for gap buffer */ int cap; /* allocated space for text */ int len; /* actual length of text */ int w; int h; long y_offset; /* pixel offset starting at the top of the file */ int cache_index; /* index of pixmap in cache, or -1 if not assigned */ - char dirty; /* whether line needs to be rendered before being draw */ + char dirty; /* whether line needs to be rendered before being drawn */ + char text_dirty; /* whether the text in the PangoLayout needs to be + updated before the layout is rendered */ + char h_dirty; /* whether height needs to be recalculated still */ } ledit_line; struct ledit_buffer { @@ -40,9 +44,60 @@ struct ledit_buffer { ledit_buffer *ledit_create_buffer(ledit_common_state *state); void ledit_destroy_buffer(ledit_buffer *buffer); +void ledit_normalize_line(ledit_line *line); void ledit_set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end_byte); void ledit_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index); void ledit_wipe_line_cursor_attrs(ledit_buffer *buffer, int line); +void ledit_render_line(ledit_buffer *buffer, int line_index); +ledit_line *ledit_get_line(ledit_buffer *buffer, int index); +int ledit_line_visible(ledit_buffer *buffer, int index); +int ledit_get_legal_normal_pos(ledit_buffer *buffer, int line, int pos); +void ledit_pos_to_x_softline(ledit_line *line, int pos, int *x_ret, int *softline_ret); +void ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret); +int ledit_next_utf8(ledit_line *line, int index); +int ledit_prev_utf8(ledit_line *line, int index); +size_t ledit_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2); +void ledit_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2); +size_t ledit_copy_text_with_resize( + ledit_buffer *buffer, + char **dst, size_t *alloc, + int line1, int byte1, + int line2, int byte2 +); +void ledit_recalc_line(ledit_buffer *buffer, int line); +void ledit_recalc_from_line(ledit_buffer *buffer, int line); +void ledit_recalc_all_lines(ledit_buffer *buffer); + +/* The following functions all have two versions: + * - The _base version does not call any recalc functions - this can be used + * when multiple operations are performed before the next render in order to + * avoid recalculating everything every time. + * - The non-base versions call the appropriate recalc function in order to + * keep everything in a consistent state. */ + +void ledit_insert_text_base(ledit_buffer *buffer, int line_index, int index, char *text, int len); +void ledit_insert_text_with_newlines_base( + ledit_buffer *buffer, + int line_index, int index, + char *text, int len, + int *end_line_ret, int *end_char_ret +); +void ledit_append_line_base(ledit_buffer *buffer, int line_index, int text_index); +void ledit_delete_line_entries_base(ledit_buffer *buffer, int index1, int index2); +void ledit_delete_line_entry_base(ledit_buffer *buffer, int index); +int ledit_delete_unicode_char_base(ledit_buffer *buffer, int line_index, int byte_index, int dir); +void ledit_delete_range_base( + ledit_buffer *buffer, int line_based, + int line_index1, int byte_index1, + int line_index2, int byte_index2, + int *new_line_ret, int *new_byte_ret +); +void ledit_insert_text_from_line_base( + ledit_buffer *buffer, + int dst_line, int dst_index, + int src_line, int src_index, int src_len +); + void ledit_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, int len); void ledit_insert_text_with_newlines( ledit_buffer *buffer, @@ -50,34 +105,17 @@ void ledit_insert_text_with_newlines( char *text, int len, int *end_line_ret, int *end_char_ret ); -void ledit_render_line(ledit_buffer *buffer, int line_index); void ledit_append_line(ledit_buffer *buffer, int line_index, int text_index); void ledit_delete_line_entries(ledit_buffer *buffer, int index1, int index2); void ledit_delete_line_entry(ledit_buffer *buffer, int index); -ledit_line *ledit_get_line(ledit_buffer *buffer, int index); -int ledit_line_visible(ledit_buffer *buffer, int index); int ledit_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index, int dir); -int ledit_get_legal_normal_pos(ledit_buffer *buffer, int line, int pos); void ledit_delete_range( ledit_buffer *buffer, int line_based, int line_index1, int byte_index1, int line_index2, int byte_index2, int *new_line_ret, int *new_byte_ret ); -void ledit_pos_to_x_softline(ledit_line *line, int pos, int *x_ret, int *softline_ret); -void ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret); -int ledit_next_utf8(ledit_line *line, int index); -int ledit_prev_utf8(ledit_line *line, int index); -size_t ledit_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2); -void ledit_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2); -size_t ledit_copy_text_with_resize( - ledit_buffer *buffer, - char **dst, size_t *alloc, - int line1, int byte1, - int line2, int byte2 -); -void -ledit_insert_text_from_line( +void ledit_insert_text_from_line( ledit_buffer *buffer, int dst_line, int dst_index, int src_line, int src_index, int src_len diff --git a/ledit.c b/ledit.c @@ -1,3 +1,4 @@ +/* FIXME: Only redraw part of screen if needed */ /* FIXME: overflow in repeated commands */ /* FIXME: Fix lag when scrolling */ /* FIXME: Fix lag when selecting with mouse */ @@ -1367,6 +1368,7 @@ delete_key(void) { } else if (buffer->cur_index == cur_line->len) { if (buffer->cur_line != buffer->lines_num - 1) { int old_len = cur_line->len; + /* FIXME: THIS CURRENTLY DOESN'T RECALC LINE SIZE! */ ledit_insert_text_from_line( buffer, buffer->cur_line, cur_line->len, buffer->cur_line + 1, 0, -1 @@ -1839,12 +1841,14 @@ key_press(XEvent event) { is needed to make keys that use shift match */ cur_keys->keys[i].func(); found = 1; + break; } } else if ((cur_keys->keys[i].modes & state.mode) && cur_keys->keys[i].keysym == sym && match(cur_keys->keys[i].mods, key_state)) { cur_keys->keys[i].func(); found = 1; + break; } } if (found) { diff --git a/search.c b/search.c @@ -48,6 +48,7 @@ search_forward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { int byte = buffer->cur_index + 1; char *res; ledit_line *lline = ledit_get_line(buffer, line); + ledit_normalize_line(lline); if ((res = strstr(lline->text + byte, last_search)) != NULL) { *line_ret = line; *byte_ret = (int)(res - lline->text); @@ -55,6 +56,7 @@ search_forward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { } for (int i = line + 1; i < buffer->lines_num; i++) { lline = ledit_get_line(buffer, i); + ledit_normalize_line(lline); if ((res = strstr(lline->text, last_search)) != NULL) { *line_ret = i; *byte_ret = (int)(res - lline->text); @@ -63,6 +65,7 @@ search_forward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { } for (int i = 0; i < line; i++) { lline = ledit_get_line(buffer, i); + ledit_normalize_line(lline); if ((res = strstr(lline->text, last_search)) != NULL) { *line_ret = i; *byte_ret = (int)(res - lline->text); @@ -70,6 +73,7 @@ search_forward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { } } lline = ledit_get_line(buffer, line); + ledit_normalize_line(lline); if ((res = strstr(lline->text, last_search)) != NULL) { *line_ret = line; *byte_ret = (int)(res - lline->text); @@ -88,6 +92,7 @@ search_backward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { int line = buffer->cur_line; int byte = buffer->cur_index; ledit_line *lline = ledit_get_line(buffer, line); + ledit_normalize_line(lline); char *last = NULL, *res = lline->text; while ((res = strstr(res, last_search)) != NULL && res < lline->text + byte) { last = res; @@ -101,6 +106,7 @@ search_backward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { } for (int i = line - 1; i >= 0; i--) { lline = ledit_get_line(buffer, i); + ledit_normalize_line(lline); res = lline->text; while ((res = strstr(res, last_search)) != NULL) { last = res; @@ -114,6 +120,7 @@ search_backward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { } for (int i = buffer->lines_num - 1; i > line; i--) { lline = ledit_get_line(buffer, i); + ledit_normalize_line(lline); res = lline->text; while ((res = strstr(res, last_search)) != NULL) { last = res; @@ -126,6 +133,7 @@ search_backward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { } } lline = ledit_get_line(buffer, line); + ledit_normalize_line(lline); res = lline->text + byte; while ((res = strstr(res, last_search)) != NULL) { last = res;