URI: 
       tUse gap buffer for text - ledit - Text editor (WIP)
  HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ledit.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 8779bb819724ae7d05491112ae700063f49cde93
   DIR parent 0e379b0cf1ba991e2024b2541a9d5d4f80068d5b
  HTML 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(-)
       ---
   DIR diff --git a/buffer.c b/buffer.c
       t@@ -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>
       t@@ -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 *
       t@@ -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;
       t@@ -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;
       t@@ -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);
       t@@ -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
       t@@ -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 *
       t@@ -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;
       t@@ -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);
       t@@ -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;
       t@@ -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)
       t@@ -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,
       t@@ -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
       t@@ -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 *
       t@@ -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
       t@@ -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
       t@@ -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';
       t@@ -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';
       t@@ -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;
        }
       t@@ -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);
       t@@ -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 */
       t@@ -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 =
       t@@ -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;
       t@@ -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) {
       t@@ -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
                                        );
       t@@ -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);
       t@@ -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);
       t@@ -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);
       t@@ -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);
                                }
       t@@ -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 {
       t@@ -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
       t@@ -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);
   DIR diff --git a/buffer.h b/buffer.h
       t@@ -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 {
       t@@ -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,
       t@@ -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
   DIR diff --git a/ledit.c b/ledit.c
       t@@ -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 */
       t@@ -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
       t@@ -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) {
   DIR diff --git a/search.c b/search.c
       t@@ -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);
       t@@ -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);
       t@@ -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);
       t@@ -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);
       t@@ -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;
       t@@ -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;
       t@@ -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;
       t@@ -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;