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:
M | buffer.c | | | 540 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------- |
M | buffer.h | | | 76 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------- |
M | ledit.c | | | 4 | ++++ |
M | search.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;