URI: 
       tDon't keep a PangoLayout for every line - 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 6c98800389dc02e20954a157675ac67fb02b7f8c
   DIR parent 60385928394de59d1104da2c69241745cbba4784
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Sun, 21 Nov 2021 14:30:26 +0100
       
       Don't keep a PangoLayout for every line
       
       Diffstat:
         M LICENSE                             |       3 ++-
         M buffer.c                            |     632 ++++++++++++++++++-------------
         M buffer.h                            |      37 +++++++++++++++++--------------
         M cache.c                             |     169 +++++++++++++++++++++++--------
         M cache.h                             |     128 ++++++++++++++++++++++++++++---
         M keys_basic.c                        |      26 ++++++++++++--------------
         M keys_command.c                      |       2 --
         M memory.c                            |      23 +++++++++++++++++++++++
         M memory.h                            |       1 +
       
       9 files changed, 675 insertions(+), 346 deletions(-)
       ---
   DIR diff --git a/LICENSE b/LICENSE
       t@@ -1,4 +1,5 @@
       -Note: Some stuff is stolen from st (https://st.suckless.org)
       +Note 1: Some stuff is stolen from st (https://st.suckless.org)
       +Note 2: Some stuff is stolen from OpenBSD (https://openbsd.org)
        
        ISC License
        
   DIR diff --git a/buffer.c b/buffer.c
       t@@ -36,16 +36,44 @@
        
        static PangoAttrList *basic_attrs = NULL;
        
       -static void err_text_dirty(ledit_buffer *buffer, int line);
       +/*static void err_text_dirty(ledit_buffer *buffer, int line);*/
        static void move_text_gap(ledit_line *line, int index);
        static void resize_and_move_text_gap(ledit_line *line, int min_size, int index);
        static char *strchr_len(char *text, char c, long len);
        static void init_line(ledit_buffer *buffer, ledit_line *line);
        static void delete_line_section_base(ledit_buffer *buffer, int line, int start, int length);
       -static void normalize_and_set_pango_text(ledit_line *line);
        static void swap(int *a, int *b);
        static void copy_selection_to_x_primary(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2);
        
       +/*
       + * Assign a cache index to line and set text and highlight of the pango layout.
       + */
       +static void set_pango_text_and_highlight(ledit_buffer *buffer, int line);
       +
       +/*
       + * Get the pango layout for line.
       + * This first assigns a cache index (by calling set_pango_text_and_highlight).
       + */
       +static PangoLayout *get_pango_layout(ledit_buffer *buffer, int line);
       +
       +/*
       + * Get an attribute list for a text highlight between the given range.
       + */
       +static PangoAttrList *get_pango_attributes(ledit_buffer *buffer, int start_byte, int end_byte);
       +
       +/*
       + * Set the attributes for a PangoLayout belonging to the given line index.
       + * If the line is part of the buffer's selection, the selection is set.
       + * If that is not the case but cursor_index is set for the line, the character
       + * at that position is highlighted (this is used for the normal mode cursor).
       + * Otherwise, the default attributes (basic_attrs) are set.
       + */
       +static void set_line_layout_attrs(ledit_buffer *buffer, int line, PangoLayout *layout);
       +
       +static int line_visible_callback(void *data, int line);
       +static void set_pixmap_line_helper(void *data, int line, int index);
       +static void set_layout_line_helper(void *data, int line, int index);
       +
        void
        ledit_buffer_set_mode(ledit_buffer *buffer, enum ledit_mode mode) {
                buffer->common->mode = mode;
       t@@ -105,6 +133,13 @@ ledit_buffer_create(ledit_common *common, ledit_theme *theme, ledit_window *wind
                }
        
                ledit_buffer *buffer = ledit_malloc(sizeof(ledit_buffer));
       +        buffer->cache = cache_create(common->dpy);
       +        buffer->undo = ledit_undo_stack_create();
       +        buffer->theme = theme;
       +        buffer->marklist = marklist_create();
       +        ledit_window_set_scroll_callback(window, &ledit_buffer_scroll_handler, buffer);
       +        ledit_window_set_button_callback(window, &ledit_buffer_button_handler, buffer);
       +
                buffer->common = common;
                buffer->window = window;
                buffer->lines = NULL;
       t@@ -122,14 +157,9 @@ ledit_buffer_create(ledit_common *common, ledit_theme *theme, ledit_window *wind
                buffer->display_offset = 0;
                buffer->sel.line1 = buffer->sel.byte1 = -1;
                buffer->sel.line2 = buffer->sel.byte2 = -1;
       +
                ledit_buffer_append_line_base(buffer, -1, -1);
                ledit_buffer_recalc_all_lines(buffer);
       -        buffer->cache = ledit_cache_create(common);
       -        buffer->undo = ledit_undo_stack_create();
       -        buffer->theme = theme;
       -        buffer->marklist = marklist_create();
       -        ledit_window_set_scroll_callback(window, &ledit_buffer_scroll_handler, buffer);
       -        ledit_window_set_button_callback(window, &ledit_buffer_button_handler, buffer);
        
                return buffer;
        }
       t@@ -217,10 +247,9 @@ ledit_buffer_destroy(ledit_buffer *buffer) {
                ledit_line *l;
                for (int i = 0; i < buffer->lines_num; i++) {
                        l = ledit_buffer_get_line(buffer, i);
       -                g_object_unref(l->layout);
                        free(l->text);
                }
       -        ledit_cache_destroy(buffer->cache);
       +        cache_destroy(buffer->cache);
                ledit_undo_stack_destroy(buffer->undo);
                free(buffer->lines);
                if (buffer->filename)
       t@@ -249,6 +278,7 @@ ledit_buffer_normalize_line(ledit_line *line) {
                line->text[line->len] = '\0';
        }
        
       +#if 0
        static void
        err_text_dirty(ledit_buffer *buffer, int line) {
                fprintf(
       t@@ -258,9 +288,11 @@ err_text_dirty(ledit_buffer *buffer, int line) {
                );
                ledit_buffer_recalc_from_line(buffer, line);
        }
       +#endif
        
       -void
       -ledit_buffer_set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end_byte) {
       +#if 0
       +static void
       +set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end_byte) {
                ledit_line *l = ledit_buffer_get_line(buffer, line);
                if (l->text_dirty)
                        err_text_dirty(buffer, line);
       t@@ -284,41 +316,72 @@ ledit_buffer_set_line_selection(ledit_buffer *buffer, int line, int start_byte, 
                pango_attr_list_unref(list);
                l->dirty = 1;
        }
       +#endif
        
       -void
       -ledit_buffer_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index) {
       -        ledit_line *l = ledit_buffer_get_line(buffer, line);
       -        if (l->text_dirty)
       -                err_text_dirty(buffer, line);
       -        if (buffer->common->mode == NORMAL) {
       -                XRenderColor fg = buffer->theme->text_fg.color;
       -                XRenderColor bg = buffer->theme->text_bg.color;
       -                PangoAttribute *attr0 = pango_attr_background_new(fg.red, fg.green, fg.blue);
       -                PangoAttribute *attr1 = pango_attr_foreground_new(bg.red, bg.green, bg.blue);
       -                attr0->start_index = index;
       -                attr0->end_index = index + 1;
       -                attr1->start_index = index;
       -                attr1->end_index = index + 1;
       -                PangoAttrList *list = pango_attr_list_new();
       -                pango_attr_list_insert(list, attr0);
       -                pango_attr_list_insert(list, attr1);
       -                #if PANGO_VERSION_CHECK(1, 44, 0)
       -                PangoAttribute *attr2 = pango_attr_insert_hyphens_new(FALSE);
       -                pango_attr_list_insert(list, attr2);
       -                #endif
       -                pango_layout_set_attributes(l->layout, list);
       +static PangoAttrList *
       +get_pango_attributes(ledit_buffer *buffer, int start_byte, int end_byte) {
       +        XRenderColor fg = buffer->theme->text_fg.color;
       +        XRenderColor bg = buffer->theme->text_bg.color;
       +        PangoAttribute *attr0 = pango_attr_background_new(fg.red, fg.green, fg.blue);
       +        PangoAttribute *attr1 = pango_attr_foreground_new(bg.red, bg.green, bg.blue);
       +        attr0->start_index = start_byte;
       +        attr0->end_index = end_byte;
       +        attr1->start_index = start_byte;
       +        attr1->end_index = end_byte;
       +        PangoAttrList *list = pango_attr_list_new();
       +        pango_attr_list_insert(list, attr0);
       +        pango_attr_list_insert(list, attr1);
       +        #if PANGO_VERSION_CHECK(1, 44, 0)
       +        PangoAttribute *attr2 = pango_attr_insert_hyphens_new(FALSE);
       +        pango_attr_list_insert(list, attr2);
       +        #endif
       +        return list;
       +}
       +
       +/* this takes layout directly to possibly avoid infinite recursion */
       +static void
       +set_line_layout_attrs(ledit_buffer *buffer, int line, PangoLayout *layout) {
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line);
       +        ledit_range sel = ll->parent_buffer->sel;
       +        PangoAttrList *list = NULL;
       +        if (sel.line1 < line && sel.line2 > line) {
       +                list = get_pango_attributes(buffer, 0, ll->len);
       +        } else if (sel.line1 == line && sel.line2 == line) {
       +                int start = sel.byte1, end = sel.byte2;
       +                if (start > end)
       +                        swap(&start, &end);
       +                list = get_pango_attributes(buffer, start, end);
       +        } else if (sel.line1 == line && sel.line2 > line) {
       +                list = get_pango_attributes(buffer, sel.byte1, ll->len);
       +        } else if (sel.line1 < line && sel.line2 == line) {
       +                list = get_pango_attributes(buffer, 0, sel.byte2);
       +        } else if (ll->cursor_index >= 0 && ll->cursor_index < ll->len) {
       +                /* FIXME: does just adding one really do the right thing? */
       +                list = get_pango_attributes(buffer, ll->cursor_index, ll->cursor_index + 1);
       +        }
       +        if (list != NULL) {
       +                pango_layout_set_attributes(layout, list);
                        pango_attr_list_unref(list);
                } else {
       -                pango_layout_set_attributes(l->layout, basic_attrs);
       +                pango_layout_set_attributes(layout, basic_attrs);
                }
       -        l->dirty = 1;
       +        ll->highlight_dirty = 0;
       +}
       +
       +void
       +ledit_buffer_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index) {
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line);
       +        ll->cursor_index = index;
       +        ll->highlight_dirty = 1;
       +        ll->dirty = 1;
        }
        
        void
        ledit_buffer_wipe_line_cursor_attrs(ledit_buffer *buffer, int line) {
       -        ledit_line *l = ledit_buffer_get_line(buffer, line);
       -        pango_layout_set_attributes(l->layout, basic_attrs);
       -        l->dirty = 1;
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line);
       +        ll->cursor_index = -1;
       +        ll->highlight_dirty = 1;
       +        ll->dirty = 1;
        }
        
        /* FIXME: To simplify this a bit, maybe just copy text to txtbuf first and
       t@@ -589,36 +652,31 @@ line_visible_callback(void *data, int line) {
        void
        ledit_buffer_render_line(ledit_buffer *buffer, int line_index) {
                /* FIXME: check for <= 0 on size */
       -        ledit_line *line = ledit_buffer_get_line(buffer, 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) {
       -                int new_index = ledit_get_unneeded_cache_index(buffer->cache, buffer, &line_visible_callback);
       -                ledit_cache_pixmap *tmp_pix = ledit_get_cache_pixmap(buffer->cache, new_index);
       -                if (tmp_pix->line >= 0) {
       -                        ledit_line *old_line = ledit_buffer_get_line(buffer, tmp_pix->line);
       -                        old_line->cache_index = -1;
       -                }
       -                tmp_pix->line = line_index;
       -                line->cache_index = new_index;
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line_index);
       +        assert(!ll->h_dirty); /* FIXME */
       +        PangoLayout *layout = get_pango_layout(buffer, line_index);
       +        if (ll->cache_pixmap_index == -1) {
       +                cache_assign_pixmap_index(
       +                    buffer->cache, line_index, buffer,
       +                    &line_visible_callback, &set_pixmap_line_helper
       +                );
                }
       -        ledit_cache_pixmap *pix = ledit_get_cache_pixmap(buffer->cache, line->cache_index);
       +        cache_pixmap *pix = cache_get_pixmap(buffer->cache, ll->cache_pixmap_index);
                /* FIXME: sensible default pixmap sizes here */
                if (pix->pixmap == None || pix->draw == NULL) {
                        pix->pixmap = XCreatePixmap(
                            buffer->common->dpy, buffer->window->drawable,
       -                    line->w + 10, line->h + 10, buffer->common->depth
       +                    ll->w + 10, ll->h + 10, buffer->common->depth
                        );
       -                pix->w = line->w + 10;
       -                pix->h = line->h + 10;
       +                pix->w = ll->w + 10;
       +                pix->h = ll->h + 10;
                        pix->draw = XftDrawCreate(
                            buffer->common->dpy, pix->pixmap,
                            buffer->common->vis, buffer->common->cm
                        );
       -        } else if (pix->w < line->w || pix->h < line->h) {
       -                int new_w = line->w > pix->w ? line->w + 10 : pix->w + 10;
       -                int new_h = line->h > pix->h ? line->h + 10 : pix->h + 10;
       +        } else if (pix->w < ll->w || pix->h < ll->h) {
       +                int new_w = ll->w > pix->w ? ll->w + 10 : pix->w + 10;
       +                int new_h = ll->h > pix->h ? ll->h + 10 : pix->h + 10;
                        XFreePixmap(buffer->common->dpy, pix->pixmap);
                        pix->pixmap = XCreatePixmap(
                            buffer->common->dpy, buffer->window->drawable,
       t@@ -628,35 +686,32 @@ ledit_buffer_render_line(ledit_buffer *buffer, int line_index) {
                        pix->h = new_h;
                        XftDrawChange(pix->draw, pix->pixmap);
                }
       -        XftDrawRect(pix->draw, &buffer->theme->text_bg, 0, 0, line->w, line->h);
       -        pango_xft_render_layout(pix->draw, &buffer->theme->text_fg, line->layout, 0, 0);
       -        line->dirty = 0;
       +        XftDrawRect(pix->draw, &buffer->theme->text_bg, 0, 0, ll->w, ll->h);
       +        pango_xft_render_layout(pix->draw, &buffer->theme->text_fg, layout, 0, 0);
       +        ll->dirty = 0;
        }
        
        static void
        init_line(ledit_buffer *buffer, ledit_line *line) {
                int text_w, text_h;
                line->parent_buffer = buffer;
       -        line->layout = pango_layout_new(buffer->window->context);
                ledit_window_get_textview_size(buffer->window, &text_w, &text_h);
       -        pango_layout_set_width(line->layout, text_w * PANGO_SCALE);
       -        pango_layout_set_font_description(line->layout, buffer->window->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->cache_pixmap_index = -1;
       +        line->cache_layout_index = -1;
                line->dirty = 1;
                line->text_dirty = 1;
       +        line->highlight_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->cursor_index = -1;
       +        line->softlines = 1;
                line->w = text_w;
       +        line->h = 0;
                line->y_offset = 0;
        }
        
       t@@ -682,9 +737,9 @@ ledit_buffer_append_line_base(ledit_buffer *buffer, int line_index, int text_ind
                    buffer->lines + line_index + 1,
                    (buffer->lines_num - (line_index + 1)) * sizeof(ledit_line)
                );
       +        buffer->lines_num++;
                ledit_line *new_l = ledit_buffer_get_line(buffer, line_index + 1);
                init_line(buffer, new_l);
       -        buffer->lines_num++;
                if (text_index != -1) {
                        ledit_line *l = ledit_buffer_get_line(buffer, line_index);
                        ledit_buffer_insert_text_from_line_base(
       t@@ -705,15 +760,31 @@ ledit_buffer_delete_line_entries(ledit_buffer *buffer, int index1, int index2) {
                ledit_buffer_recalc_from_line(buffer, index1 > 0 ? index1 - 1 : 0);
        }
        
       +static void
       +set_pixmap_line_helper(void *data, int line, int index) {
       +        ledit_line *ll = ledit_buffer_get_line((ledit_buffer *)data, line);
       +        ll->cache_pixmap_index = index;
       +}
       +
       +static void
       +set_layout_line_helper(void *data, int line, int index) {
       +        ledit_line *ll = ledit_buffer_get_line((ledit_buffer *)data, line);
       +        ll->cache_layout_index = index;
       +}
       +
        /* IMPORTANT: ledit_buffer_recalc_from_line needs to be called sometime after this! */
        void
        ledit_buffer_delete_line_entries_base(ledit_buffer *buffer, int index1, int index2) {
                ledit_line *l;
                /* FIXME: make sure this is always true */
       +        /* FIXME: Ummm... what is that assert supposed to do? */
                assert(index2 - index1 != buffer->lines_num);
       +        cache_invalidate_from_line(
       +            buffer->cache, index1, buffer,
       +            &set_pixmap_line_helper, &set_layout_line_helper
       +        );
                for (int i = index1; i <= index2; i++) {
                        l = ledit_buffer_get_line(buffer, i);
       -                g_object_unref(l->layout);
                        free(l->text);
                }
                /* FIXME: gap buffer */
       t@@ -747,9 +818,9 @@ ledit_buffer_delete_line_entry_base(ledit_buffer *buffer, int index) {
        
        /* FIXME: use some sort of gap buffer (that would make this function
           slightly more useful...) */
       -/* FIXME: error checking, assert? */
        ledit_line *
        ledit_buffer_get_line(ledit_buffer *buffer, int index) {
       +        assert(index >= 0 && index < buffer->lines_num);
                return &buffer->lines[index];
        }
        
       t@@ -758,20 +829,15 @@ ledit_buffer_get_line(ledit_buffer *buffer, int index) {
         * - if height has changed, offset of all following lines is changed */
        void
        ledit_buffer_recalc_line(ledit_buffer *buffer, int line) {
       -        int w, h;
                ledit_line *l = ledit_buffer_get_line(buffer, line);
       -        if (l->text_dirty) {
       -                ledit_buffer_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 (l->text_dirty)
       +                set_pango_text_and_highlight(buffer, line);
       +
                /* if height changed, set height of current line
                 * and adjust offsets of all lines following it */
       -        if (l->h != h) {
       -                long off = l->y_offset + h;
       -                l->h = h;
       +        if (l->h_dirty) {
       +                l->h_dirty = 0;
       +                long off = l->y_offset + l->h;
                        for (int i = line + 1; i < buffer->lines_num; i++) {
                                l = ledit_buffer_get_line(buffer, i);
                                l->y_offset = off;
       t@@ -785,21 +851,12 @@ ledit_buffer_recalc_line(ledit_buffer *buffer, int line) {
         * and offset for all lines starting at 'line' */
        void
        ledit_buffer_recalc_from_line(ledit_buffer *buffer, int line) {
       -        int w, h;
                ledit_line *l = ledit_buffer_get_line(buffer, line);
                long off = l->y_offset;
                for (int i = line; i < buffer->lines_num; i++) {
                        l = ledit_buffer_get_line(buffer, i);
       -                if (l->text_dirty) {
       -                        ledit_buffer_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;
       -                }
       +                if (l->text_dirty)
       +                        set_pango_text_and_highlight(buffer, i);
                        l->h_dirty = 0;
                        l->y_offset = off;
                        off += l->h;
       t@@ -951,8 +1008,9 @@ ledit_buffer_next_cursor_pos(ledit_buffer *buffer, int line, int byte, int num) 
                ledit_line *ll = ledit_buffer_get_line(buffer, line);
                int c = line_byte_to_char(ll, byte);
                int cur_byte = byte;
       +        PangoLayout *layout = get_pango_layout(buffer, line);
                const PangoLogAttr *attrs =
       -            pango_layout_get_log_attrs_readonly(ll->layout, &nattrs);
       +            pango_layout_get_log_attrs_readonly(layout, &nattrs);
                for (int i = 0; i < num; i++) {
                        cur_byte = ledit_line_next_utf8(ll, byte);
                        for (c++; c < nattrs; c++) {
       t@@ -972,8 +1030,9 @@ ledit_buffer_prev_cursor_pos(ledit_buffer *buffer, int line, int byte, int num) 
                ledit_line *ll = ledit_buffer_get_line(buffer, line);
                int c = line_byte_to_char(ll, byte);
                int cur_byte = byte;
       +        PangoLayout *layout = get_pango_layout(buffer, line);
                const PangoLogAttr *attrs =
       -            pango_layout_get_log_attrs_readonly(ll->layout, &nattrs);
       +            pango_layout_get_log_attrs_readonly(layout, &nattrs);
                for (int i = 0; i < num; i++) {
                        cur_byte = ledit_line_prev_utf8(ll, cur_byte);
                        for (c--; c >= 0; c--) {
       t@@ -988,15 +1047,17 @@ ledit_buffer_prev_cursor_pos(ledit_buffer *buffer, int line, int byte, int num) 
        }
        
        static int
       -line_next_word(ledit_line *line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) {
       +line_next_word(ledit_buffer *buffer, int line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) {
                int c, nattrs;
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line);
                if (char_index >= 0)
                        c = char_index;
                else
       -                c = line_byte_to_char(line, byte);
       -        int cur_byte = wrapped_line ? byte : ledit_line_next_utf8(line, byte);
       +                c = line_byte_to_char(ll, byte);
       +        int cur_byte = wrapped_line ? byte : ledit_line_next_utf8(ll, byte);
       +        PangoLayout *layout = get_pango_layout(buffer, line);
                const PangoLogAttr *attrs =
       -            pango_layout_get_log_attrs_readonly(line->layout, &nattrs);
       +            pango_layout_get_log_attrs_readonly(layout, &nattrs);
                for (int i = wrapped_line ? c : c + 1; i < nattrs; i++) {
                        if (attrs[i].is_word_start) {
                                if (char_ret)
       t@@ -1005,21 +1066,23 @@ line_next_word(ledit_line *line, int byte, int char_index, int wrapped_line, int
                                        *real_byte_ret = cur_byte;
                                return cur_byte;
                        }
       -                cur_byte = ledit_line_next_utf8(line, cur_byte);
       +                cur_byte = ledit_line_next_utf8(ll, cur_byte);
                }
                return -1;
        }
        
        static int
       -line_prev_word(ledit_line *line, int byte, int char_index, int *char_ret) {
       +line_prev_word(ledit_buffer *buffer, int line, int byte, int char_index, int *char_ret) {
                int c, nattrs;
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line);
                if (char_index >= 0)
                        c = char_index;
                else
       -                c = line_byte_to_char(line, byte);
       -        int cur_byte = ledit_line_prev_utf8(line, byte);
       +                c = line_byte_to_char(ll, byte);
       +        int cur_byte = ledit_line_prev_utf8(ll, byte);
       +        PangoLayout *layout = get_pango_layout(buffer, line);
                const PangoLogAttr *attrs =
       -            pango_layout_get_log_attrs_readonly(line->layout, &nattrs);
       +            pango_layout_get_log_attrs_readonly(layout, &nattrs);
                if (c > nattrs)
                        return -1;
                for (int i = c - 1; i >= 0; i--) {
       t@@ -1028,21 +1091,23 @@ line_prev_word(ledit_line *line, int byte, int char_index, int *char_ret) {
                                        *char_ret = i;
                                return cur_byte;
                        }
       -                cur_byte = ledit_line_prev_utf8(line, cur_byte);
       +                cur_byte = ledit_line_prev_utf8(ll, cur_byte);
                }
                return -1;
        }
        
        static int
       -line_prev_bigword(ledit_line *line, int byte, int char_index, int *char_ret) {
       +line_prev_bigword(ledit_buffer *buffer, int line, int byte, int char_index, int *char_ret) {
                int c, nattrs;
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line);
                if (char_index >= 0)
                        c = char_index;
                else
       -                c = line_byte_to_char(line, byte);
       -        int cur_byte = ledit_line_prev_utf8(line, byte);
       +                c = line_byte_to_char(ll, byte);
       +        int cur_byte = ledit_line_prev_utf8(ll, byte);
       +        PangoLayout *layout = get_pango_layout(buffer, line);
                const PangoLogAttr *attrs =
       -            pango_layout_get_log_attrs_readonly(line->layout, &nattrs);
       +            pango_layout_get_log_attrs_readonly(layout, &nattrs);
                int next_cursorb = byte;
                int next_cursorc = c;
                int found_word = 0;
       t@@ -1063,20 +1128,22 @@ line_prev_bigword(ledit_line *line, int byte, int char_index, int *char_ret) {
                                next_cursorc = i;
                                next_cursorb = cur_byte;
                        }
       -                cur_byte = ledit_line_prev_utf8(line, cur_byte);
       +                cur_byte = ledit_line_prev_utf8(ll, cur_byte);
                }
                return -1;
        }
        
        int
       -line_next_bigword_end(ledit_line *line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) {
       +line_next_bigword_end(ledit_buffer *buffer, int line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) {
                int c, nattrs;
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line);
                if (char_index >= 0)
                        c = char_index;
                else
       -                c = line_byte_to_char(line, byte);
       +                c = line_byte_to_char(ll, byte);
       +        PangoLayout *layout = get_pango_layout(buffer, line);
                const PangoLogAttr *attrs =
       -            pango_layout_get_log_attrs_readonly(line->layout, &nattrs);
       +            pango_layout_get_log_attrs_readonly(layout, &nattrs);
                int last_cursorb, last_cursorc;
                if (wrapped_line) {
                        last_cursorb = byte;
       t@@ -1101,21 +1168,23 @@ line_next_bigword_end(ledit_line *line, int byte, int char_index, int wrapped_li
                                last_cursorc = i;
                                last_cursorb = cur_byte;
                        }
       -                cur_byte = ledit_line_next_utf8(line, cur_byte);
       +                cur_byte = ledit_line_next_utf8(ll, cur_byte);
                }
                return -1;
        }
        
        static int
       -line_next_word_end(ledit_line *line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) {
       +line_next_word_end(ledit_buffer *buffer, int line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) {
                int c, nattrs;
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line);
                if (char_index >= 0)
                        c = char_index;
                else
       -                c = line_byte_to_char(line, byte);
       -        int cur_byte = ledit_line_next_utf8(line, byte);
       +                c = line_byte_to_char(ll, byte);
       +        int cur_byte = ledit_line_next_utf8(ll, byte);
       +        PangoLayout *layout = get_pango_layout(buffer, line);
                const PangoLogAttr *attrs =
       -            pango_layout_get_log_attrs_readonly(line->layout, &nattrs);
       +            pango_layout_get_log_attrs_readonly(layout, &nattrs);
                int last_cursorb, last_cursorc;
                if (wrapped_line) {
                        last_cursorb = byte;
       t@@ -1136,21 +1205,23 @@ line_next_word_end(ledit_line *line, int byte, int char_index, int wrapped_line,
                                last_cursorc = i;
                                last_cursorb = cur_byte;
                        }
       -                cur_byte = ledit_line_next_utf8(line, cur_byte);
       +                cur_byte = ledit_line_next_utf8(ll, cur_byte);
                }
                return -1;
        }
        
        static int
       -line_next_bigword(ledit_line *line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) {
       +line_next_bigword(ledit_buffer *buffer, int line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) {
                int c, nattrs;
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line);
                if (char_index >= 0)
                        c = char_index;
                else
       -                c = line_byte_to_char(line, byte);
       +                c = line_byte_to_char(ll, byte);
                int cur_byte = byte;
       +        PangoLayout *layout = get_pango_layout(buffer, line);
                const PangoLogAttr *attrs =
       -            pango_layout_get_log_attrs_readonly(line->layout, &nattrs);
       +            pango_layout_get_log_attrs_readonly(layout, &nattrs);
                int found_ws = wrapped_line;
                for (int i = c; i < nattrs; i++) {
                        if (!found_ws && attrs[i].is_white) {
       t@@ -1162,24 +1233,26 @@ line_next_bigword(ledit_line *line, int byte, int char_index, int wrapped_line, 
                                        *real_byte_ret = cur_byte;
                                return cur_byte;
                        }
       -                cur_byte = ledit_line_next_utf8(line, cur_byte);
       +                cur_byte = ledit_line_next_utf8(ll, cur_byte);
                }
                return -1;
        }
        
        int
       -ledit_line_next_non_whitespace(ledit_line *line, int byte) {
       +ledit_line_next_non_whitespace(ledit_buffer *buffer, int line, int byte) {
                int c, nattrs;
       -        c = line_byte_to_char(line, byte);
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line);
       +        c = line_byte_to_char(ll, byte);
                int cur_byte = byte;
       +        PangoLayout *layout = get_pango_layout(buffer, line);
                const PangoLogAttr *attrs =
       -            pango_layout_get_log_attrs_readonly(line->layout, &nattrs);
       +            pango_layout_get_log_attrs_readonly(layout, &nattrs);
                for (; c < nattrs; c++) {
                        if (!attrs[c].is_white)
                                return cur_byte;
       -                cur_byte = ledit_line_next_utf8(line, cur_byte);
       +                cur_byte = ledit_line_next_utf8(ll, cur_byte);
                }
       -        return line->len;
       +        return ll->len;
        }
        
        /* FIXME: document that word and bigword are a bit weird because word uses unicode semantics */
       t@@ -1195,13 +1268,12 @@ ledit_buffer_next_##name(                                                       
                int cur_char = -1;                                                                                      \
                int real_byte = -1;                                                                                     \
                int wrapped_line;                                                                                       \
       -        ledit_line *ll = ledit_buffer_get_line(buffer, cur_line);                                               \
                for (int i = 0; i < num_repeat; i++) {                                                                  \
                        wrapped_line = 0;                                                                               \
       -                while ((cur_byte = func(ll, cur_byte, cur_char, wrapped_line, &cur_char, &real_byte)) == -1 &&  \
       +                while ((cur_byte = func(buffer, cur_line, cur_byte, cur_char,                                   \
       +                        wrapped_line, &cur_char, &real_byte)) == -1 &&                                          \
                               cur_line < buffer->lines_num - 1) {                                                      \
                                cur_line++;                                                                             \
       -                        ll = ledit_buffer_get_line(buffer, cur_line);                                           \
                                cur_byte = 0;                                                                           \
                                wrapped_line = 1;                                                                       \
                        }                                                                                               \
       t@@ -1210,6 +1282,7 @@ ledit_buffer_next_##name(                                                       
                }                                                                                                       \
                if (cur_byte == -1) {                                                                                   \
                        *line_ret = buffer->lines_num - 1;                                                              \
       +                ledit_line *ll = ledit_buffer_get_line(buffer, buffer->lines_num - 1);                          \
                        *byte_ret = ledit_buffer_get_legal_normal_pos(buffer, buffer->lines_num - 1, ll->len);          \
                        *real_byte_ret = ll->len;                                                                       \
                } else {                                                                                                \
       t@@ -1230,7 +1303,8 @@ ledit_buffer_prev_##name(                                                       
                int cur_char = -1;                                                                                \
                ledit_line *ll = ledit_buffer_get_line(buffer, cur_line);                                         \
                for (int i = 0; i < num_repeat; i++) {                                                            \
       -                while ((cur_byte = func(ll, cur_byte, cur_char, &cur_char)) == -1 && cur_line > 0) {      \
       +                while ((cur_byte = func(buffer, cur_line, cur_byte, cur_char,                             \
       +                        &cur_char)) == -1 && cur_line > 0) {                                              \
                                cur_line--;                                                                       \
                                ll = ledit_buffer_get_line(buffer, cur_line);                                     \
                                cur_byte = ll->len;                                                               \
       t@@ -1262,11 +1336,11 @@ ledit_buffer_get_pos_softline_bounds(
            int *start_byte_ret, int *end_byte_ret) {
                assert(line >= 0 && line < buffer->lines_num);
                ledit_line *ll = ledit_buffer_get_line(buffer, line);
       -        normalize_and_set_pango_text(ll);
                assert(pos >= 0 && pos <= ll->len);
       +        PangoLayout *layout = get_pango_layout(buffer, line);
                int x, sli;
       -        pango_layout_index_to_line_x(ll->layout, pos, 0, &sli, &x);
       -        PangoLayoutLine *pl = pango_layout_get_line_readonly(ll->layout, sli);
       +        pango_layout_index_to_line_x(layout, pos, 0, &sli, &x);
       +        PangoLayoutLine *pl = pango_layout_get_line_readonly(layout, sli);
                *start_byte_ret = pl->start_index;
                *end_byte_ret = pl->start_index + pl->length;
        }
       t@@ -1277,9 +1351,9 @@ ledit_buffer_get_softline_bounds(
            int *start_byte_ret, int *end_byte_ret) {
                assert(line >= 0 && line < buffer->lines_num);
                ledit_line *ll = ledit_buffer_get_line(buffer, line);
       -        normalize_and_set_pango_text(ll);
       -        assert(softline < pango_layout_get_line_count(ll->layout));
       -        PangoLayoutLine *pl = pango_layout_get_line_readonly(ll->layout, softline);
       +        PangoLayout *layout = get_pango_layout(buffer, line);
       +        assert(softline < ll->softlines);
       +        PangoLayoutLine *pl = pango_layout_get_line_readonly(layout, softline);
                *start_byte_ret = pl->start_index;
                *end_byte_ret = pl->start_index + pl->length;
        }
       t@@ -1288,7 +1362,9 @@ int
        ledit_buffer_get_softline_count(ledit_buffer *buffer, int line) {
                assert(line >= 0 && line < buffer->lines_num);
                ledit_line *ll = ledit_buffer_get_line(buffer, line);
       -        return pango_layout_get_line_count(ll->layout);
       +        if (ll->text_dirty)
       +                set_pango_text_and_highlight(buffer, line);
       +        return ll->softlines;
        }
        
        int
       t@@ -1296,8 +1372,9 @@ ledit_buffer_pos_to_softline(ledit_buffer *buffer, int line, int pos) {
                assert(line >= 0 && line < buffer->lines_num);
                ledit_line *ll = ledit_buffer_get_line(buffer, line);
                assert(pos >= 0 && pos <= ll->len);
       +        PangoLayout *layout = get_pango_layout(buffer, line);
                int x, sli;
       -        pango_layout_index_to_line_x(ll->layout, pos, 0, &sli, &x);
       +        pango_layout_index_to_line_x(layout, pos, 0, &sli, &x);
                return sli;
        }
        
       t@@ -1306,8 +1383,9 @@ ledit_buffer_get_cursor_pixel_pos(ledit_buffer *buffer, int line, int pos, int *
                assert(line >= 0 && line < buffer->lines_num);
                ledit_line *ll = ledit_buffer_get_line(buffer, line);
                assert(pos >= 0 && pos <= ll->len);
       +        PangoLayout *layout = get_pango_layout(buffer, line);
                PangoRectangle strong, weak;
       -        pango_layout_get_cursor_pos(ll->layout, 0, &strong, &weak);
       +        pango_layout_get_cursor_pos(layout, 0, &strong, &weak);
                *x_ret = strong.x / PANGO_SCALE;
                *y_ret = strong.y / PANGO_SCALE;
                *h_ret = strong.height / PANGO_SCALE;
       t@@ -1323,6 +1401,7 @@ ledit_buffer_move_cursor_visually(ledit_buffer *buffer, int line, int pos, int m
                /* FIXME: trailing */
                int trailing = 0, tmp_index;
                ledit_line *cur_line = ledit_buffer_get_line(buffer, line);
       +        PangoLayout *layout = get_pango_layout(buffer, line);
                int new_index = pos, last_index = pos;
                int dir = 1;
                int num = movement;
       t@@ -1333,7 +1412,7 @@ ledit_buffer_move_cursor_visually(ledit_buffer *buffer, int line, int pos, int m
                while (num > 0) {
                        tmp_index = new_index;
                        pango_layout_move_cursor_visually(
       -                    cur_line->layout, TRUE,
       +                    layout, TRUE,
                            new_index, trailing, dir,
                            &new_index, &trailing
                        );
       t@@ -1403,7 +1482,6 @@ delete_line_section_base(ledit_buffer *buffer, int line, int start, int length) 
                l->len -= length;
                l->dirty = 1;
                l->text_dirty = 1;
       -        l->h_dirty = 1;
        }
        
        int
       t@@ -1428,85 +1506,124 @@ ledit_buffer_delete_unicode_char_base(ledit_buffer *buffer, int line_index, int 
                return new_index;
        }
        
       -/* FIXME: normalize_line will generally be called whenever it is not normalized
       -   since text_dirty should be set then, but the naming of this function technically
       -   isn't very good */
        static void
       -normalize_and_set_pango_text(ledit_line *line) {
       -        if (line->text_dirty) {
       -                ledit_buffer_normalize_line(line);
       -                pango_layout_set_text(line->layout, line->text, line->len);
       -                line->text_dirty = 0;
       -                line->h_dirty = 1;
       +set_pango_text_and_highlight(ledit_buffer *buffer, int line) {
       +        cache_layout *cl;
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line);
       +        int old_index = ll->cache_layout_index;
       +        if (ll->cache_layout_index < 0) {
       +                cache_assign_layout_index(
       +                    ll->parent_buffer->cache, line,
       +                    ll->parent_buffer, &set_layout_line_helper
       +                );
       +                assert(ll->cache_layout_index >= 0);
       +                cl = cache_get_layout(ll->parent_buffer->cache, ll->cache_layout_index);
       +                if (cl->layout == NULL) {
       +                        cl->layout = pango_layout_new(ll->parent_buffer->window->context);
       +                        pango_layout_set_font_description(cl->layout, buffer->window->font);
       +                        pango_layout_set_wrap(cl->layout, PANGO_WRAP_WORD_CHAR);
       +                }
       +        } else {
       +                cl = cache_get_layout(ll->parent_buffer->cache, ll->cache_layout_index);
       +        }
       +        if (ll->text_dirty || old_index < 0) {
       +                ledit_buffer_normalize_line(ll);
       +                pango_layout_set_text(cl->layout, ll->text, ll->len);
       +                set_line_layout_attrs(buffer, line, cl->layout);
       +                /* FIXME: is this guard necessary? */
       +                ll->softlines = ll->len > 0 ? pango_layout_get_line_count(cl->layout) : 1;
       +                pango_layout_set_width(cl->layout, ll->w * PANGO_SCALE);
       +                int w, h;
       +                pango_layout_get_pixel_size(cl->layout, &w, &h);
       +                if (h != ll->h) {
       +                        ll->h = h;
       +                        ll->h_dirty = 1;
       +                }
       +                ll->text_dirty = 0;
       +                ll->dirty = 1;
       +        } else if (ll->highlight_dirty) {
       +                set_line_layout_attrs(buffer, line, cl->layout);
                }
       +        ll->highlight_dirty = 0;
       +}
       +
       +static PangoLayout *
       +get_pango_layout(ledit_buffer *buffer, int line) {
       +        set_pango_text_and_highlight(buffer, line);
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line);
       +        cache_layout *cl = cache_get_layout(
       +            ll->parent_buffer->cache, ll->cache_layout_index
       +        );
       +        return cl->layout;
        }
        
        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);
       +ledit_pos_to_x_softline(ledit_buffer *buffer, int line, int pos, int *x_ret, int *softline_ret) {
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line);
       +        PangoLayout *layout = get_pango_layout(buffer, line);
       +        pango_layout_index_to_line_x(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);
       +        PangoLayoutLine *pango_line = pango_layout_get_line_readonly(layout, *softline_ret);
                /* add left margin to x position if line is aligned right */
                if (pango_line->resolved_dir == PANGO_DIRECTION_RTL) {
                        PangoRectangle rect;
                        pango_layout_line_get_extents(pango_line, NULL, &rect);
       -                *x_ret += (line->w * PANGO_SCALE - rect.width);
       +                *x_ret += (ll->w * PANGO_SCALE - rect.width);
                }
                /* if in normal mode, change position to the middle of the
                   current rectangle so that moving around won't jump weirdly */
                /* FIXME: also in visual? */
                /* FIXME: this is too much magic for my taste */
       -        if (line->parent_buffer->common->mode == NORMAL) {
       +        if (ll->parent_buffer->common->mode == NORMAL) {
                        PangoRectangle rect;
       -                pango_layout_index_to_pos(line->layout, pos, &rect);
       +                pango_layout_index_to_pos(layout, pos, &rect);
                        *x_ret += rect.width / 2;
                }
        }
        
        /* FIXME: change this to return int */
        void
       -ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret) {
       +ledit_x_softline_to_pos(ledit_buffer *buffer, int line, int x, int softline, int *pos_ret) {
                int trailing = 0;
                int x_relative = x;
       -        normalize_and_set_pango_text(line);
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line);
       +        PangoLayout *layout = get_pango_layout(buffer, line);
                PangoLayoutLine *pango_line =
       -            pango_layout_get_line_readonly(line->layout, softline);
       +            pango_layout_get_line_readonly(layout, softline);
                /* x is absolute, so the margin at the left needs to be subtracted */
                if (pango_line->resolved_dir == PANGO_DIRECTION_RTL) {
                        PangoRectangle rect;
                        pango_layout_line_get_extents(pango_line, NULL, &rect);
       -                x_relative -= (line->w * PANGO_SCALE - rect.width);
       +                x_relative -= (ll->w * PANGO_SCALE - rect.width);
                }
                pango_layout_line_x_to_index(
                    pango_line, x_relative, pos_ret, &trailing
                );
                /* if in insert mode, snap to the nearest border between graphemes */
                /* FIXME: add parameter for this instead of checking mode */
       -        if (line->parent_buffer->common->mode == INSERT) {
       +        if (ll->parent_buffer->common->mode == INSERT) {
                        while (trailing > 0) {
                                trailing--;
       -                        *pos_ret = ledit_line_next_utf8(line, *pos_ret);
       +                        *pos_ret = ledit_line_next_utf8(ll, *pos_ret);
                        }
                }
        }
        
       -/* FIXME: make sure PangoLayout has newest text already when these functions are called */
        int
        ledit_buffer_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_buffer_get_line(buffer, line);
       -        normalize_and_set_pango_text(final_line);
       -        if (pos == final_line->len && pos > 0) {
       +        ledit_line *ll = ledit_buffer_get_line(buffer, line);
       +        if (pos == ll->len && pos > 0) {
                        int nattrs;
       +                PangoLayout *layout = get_pango_layout(buffer, line);
                        const PangoLogAttr *attrs =
       -                    pango_layout_get_log_attrs_readonly(final_line->layout, &nattrs);
       +                    pango_layout_get_log_attrs_readonly(layout, &nattrs);
                        int cur = nattrs - 2;
       -                ret = ledit_line_prev_utf8(final_line, ret);
       +                ret = ledit_line_prev_utf8(ll, ret);
                        while (ret > 0 && cur > 0 && !attrs[cur].is_cursor_position) {
                                cur--;
       -                        ret = ledit_line_prev_utf8(final_line, ret);
       +                        ret = ledit_line_prev_utf8(ll, ret);
                        }
                }
                return ret;
       t@@ -1565,7 +1682,7 @@ ledit_buffer_delete_range_base(
                        }
                        int dell1 = l1, dell2 = l2;
                        ledit_line *ll = ledit_buffer_get_line(buffer, line_index1);
       -                ledit_pos_to_x_softline(ll, byte_index1, &x, &sl_useless);
       +                ledit_pos_to_x_softline(buffer, line_index1, byte_index1, &x, &sl_useless);
                        if (l1 > 0 && l2 < buffer->lines_num - 1) {
                                rgl1 = l1;
                                rgb1 = 0;
       t@@ -1600,13 +1717,13 @@ ledit_buffer_delete_range_base(
                        if (l2 < buffer->lines_num - 1) {
                                new_line = l1;
                                ledit_x_softline_to_pos(
       -                            ledit_buffer_get_line(buffer, l2 + 1),
       +                            buffer, l2 + 1,
                                    x, 0, &new_byte
                                );
                        } else if (l1 > 0) {
                                new_line = l1 - 1;
                                ledit_x_softline_to_pos(
       -                            ledit_buffer_get_line(buffer, l1 - 1),
       +                            buffer, l1 - 1,
                                    x, 0, &new_byte
                                );
                        } else {
       t@@ -1626,23 +1743,22 @@ ledit_buffer_delete_range_base(
                } else if (delmode == DELETE_SOFTLINE) {
                        int x, softline1, softline2;
                        ledit_line *line1 = ledit_buffer_get_line(buffer, line_index1);
       -                normalize_and_set_pango_text(line1);
       -                ledit_pos_to_x_softline(line1, byte_index1, &x, &softline1);
       +                ledit_pos_to_x_softline(buffer, line_index1, byte_index1, &x, &softline1);
                        if (line_index1 == line_index2) {
                                int x_useless;
       -                        pango_layout_index_to_line_x(line1->layout, byte_index2, 0, &softline2, &x_useless);
       +                        PangoLayout *layout = get_pango_layout(buffer, line_index1);
       +                        pango_layout_index_to_line_x(layout, byte_index2, 0, &softline2, &x_useless);
                                int l1 = softline1 < softline2 ? softline1 : softline2;
                                int l2 = softline1 < softline2 ? softline2 : softline1;
       -                        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);
       +                        PangoLayoutLine *pl1 = pango_layout_get_line_readonly(layout, l1);
       +                        PangoLayoutLine *pl2 = pango_layout_get_line_readonly(layout, l2);
                                /* don't delete entire line if it is the last one remaining */
       -                        if (l1 == 0 && l2 == softlines - 1 && buffer->lines_num > 1) {
       +                        if (l1 == 0 && l2 == line1->softlines - 1 && buffer->lines_num > 1) {
                                        if (line_index1 < buffer->lines_num - 1) {
                                                /* cursor can be moved to next hard line */
                                                new_line = line_index1;
                                                ledit_x_softline_to_pos(
       -                                            ledit_buffer_get_line(buffer, line_index1 + 1),
       +                                            buffer, line_index1 + 1,
                                                    x, 0, &new_byte
                                                );
                                                rgl1 = line_index1;
       t@@ -1655,9 +1771,10 @@ ledit_buffer_delete_range_base(
                                                /* note: logically, line_index1 - 1 must be >= 0 because
                                                   buffer->lines_num > 1 && line_index1 >= buffer->lines_num - 1 */
                                                new_line = line_index1 - 1;
       -                                        ledit_line *prevline = ledit_buffer_get_line(buffer, line_index1 - 1);
       -                                        softlines = pango_layout_get_line_count(prevline->layout);
       -                                        ledit_x_softline_to_pos(prevline, x, softlines - 1, &new_byte);
       +                                        ledit_line *prevline = ledit_buffer_get_line(buffer, new_line);
       +                                        if (prevline->text_dirty)
       +                                                set_pango_text_and_highlight(buffer, new_line);
       +                                        ledit_x_softline_to_pos(buffer, new_line, x, prevline->softlines - 1, &new_byte);
                                                rgl1 = line_index1 - 1;
                                                rgb1 = prevline->len;
                                                rgl2 = line_index1;
       t@@ -1686,22 +1803,22 @@ ledit_buffer_delete_range_base(
                                        delete_line_section_base(
                                            buffer, line_index1, rgb1, rgb2 - rgb1
                                        );
       -                                if (l2 == softlines - 1 && line_index1 < buffer->lines_num - 1) {
       +                                if (l2 == line1->softlines - 1 && line_index1 < buffer->lines_num - 1) {
                                                new_line = line_index1 + 1;
                                                ledit_x_softline_to_pos(
       -                                            ledit_buffer_get_line(buffer, line_index1 + 1),
       +                                            buffer, line_index1 + 1,
                                                    x, 0, &new_byte
                                                );
       -                                } else if (l2 < softlines - 1) {
       +                                } else if (l2 < line1->softlines - 1) {
                                                new_line = line_index1;
                                                ledit_x_softline_to_pos(
       -                                            ledit_buffer_get_line(buffer, line_index1),
       +                                            buffer, line_index1,
                                                    x, l1, &new_byte
                                                );
                                        } else if (l1 > 0) {
                                                new_line = line_index1;
                                                ledit_x_softline_to_pos(
       -                                            ledit_buffer_get_line(buffer, line_index1),
       +                                            buffer, line_index1,
                                                    x, l1 - 1, &new_byte
                                                );
                                        } else {
       t@@ -1726,14 +1843,13 @@ ledit_buffer_delete_range_base(
                                }
                                ledit_line *ll1 = ledit_buffer_get_line(buffer, l1);
                                ledit_line *ll2 = ledit_buffer_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);
       -                        PangoLayoutLine *pl2 = pango_layout_get_line_readonly(ll2->layout, sl2);
       -                        int softlines = pango_layout_get_line_count(ll2->layout);
       -                        if (sl1 == 0 && sl2 == softlines - 1) {
       +                        PangoLayout *layout1 = get_pango_layout(buffer, l1);
       +                        PangoLayout *layout2 = get_pango_layout(buffer, l2);
       +                        pango_layout_index_to_line_x(layout1, b1, 0, &sl1, &x_useless);
       +                        pango_layout_index_to_line_x(layout2, b2, 0, &sl2, &x_useless);
       +                        PangoLayoutLine *pl1 = pango_layout_get_line_readonly(layout1, sl1);
       +                        PangoLayoutLine *pl2 = pango_layout_get_line_readonly(layout2, sl2);
       +                        if (sl1 == 0 && sl2 == ll2->softlines - 1) {
                                        if (l1 == 0 && l2 == buffer->lines_num - 1) {
                                                rgl1 = l1;
                                                rgl2 = l2;
       t@@ -1754,17 +1870,17 @@ ledit_buffer_delete_range_base(
                                                if (l2 == buffer->lines_num - 1) {
                                                        new_line = l1 - 1;
                                                        ledit_line *new_lline = ledit_buffer_get_line(buffer, new_line);
       -                                                int new_softlines = pango_layout_get_line_count(new_lline->layout);
       -                                                ledit_x_softline_to_pos(new_lline, x, new_softlines - 1, &new_byte);
       +                                                if (new_lline->text_dirty)
       +                                                        set_pango_text_and_highlight(buffer, new_line);
       +                                                ledit_x_softline_to_pos(buffer, new_line, x, new_lline->softlines - 1, &new_byte);
                                                        rgl1 = l1 - 1;
                                                        rgb1 = new_lline->len;
                                                        rgl2 = l2;
                                                        rgb2 = ll2->len;
                                                } else {
                                                        new_line = l1;
       -                                                ledit_line *nextline = ledit_buffer_get_line(buffer, l2 + 1);
                                                        ledit_x_softline_to_pos(
       -                                                    nextline, x, 0, &new_byte
       +                                                    buffer, l2 + 1, x, 0, &new_byte
                                                        );
                                                        rgl1 = l1;
                                                        rgb1 = 0;
       t@@ -1794,20 +1910,20 @@ ledit_buffer_delete_range_base(
                                        }
                                        delete_line_section_base(buffer, l2, 0, pl2->start_index + pl2->length);
                                        new_line = l1;
       -                                ledit_x_softline_to_pos(ll2, x, 0, &new_byte);
       +                                ledit_x_softline_to_pos(buffer, l2, x, 0, &new_byte);
                                        ledit_buffer_delete_line_entries_base(buffer, l1, l2 - 1);
       -                        } else if (sl2 == softlines - 1) {
       +                        } else if (sl2 == ll2->softlines - 1) {
                                        rgl1 = l1;
                                        rgb1 = pl1->start_index;
                                        rgl2 = l2;
                                        rgb2 = ll2->len;
                                        if (l2 + 1 == buffer->lines_num) {
                                                new_line = l1;
       -                                        ledit_x_softline_to_pos(ll1, x, sl1 - 1, &new_byte);
       +                                        ledit_x_softline_to_pos(buffer, l1, x, sl1 - 1, &new_byte);
                                        } else {
                                                new_line = l1 + 1;
                                                ledit_x_softline_to_pos(
       -                                            ledit_buffer_get_line(buffer, l2 + 1),
       +                                            buffer, l2 + 1,
                                                    x, 0, &new_byte
                                                );
                                        }
       t@@ -1843,7 +1959,7 @@ ledit_buffer_delete_range_base(
                                        );
                                        ledit_buffer_delete_line_entries_base(buffer, l1 + 1, l2);
                                        new_line = l1;
       -                                int new_softlines = pango_layout_get_line_count(ll1->layout);
       +                                set_pango_text_and_highlight(buffer, l1);
                                        /* it's technically possible that the remaining part of the
                                           second line is so small that it doesn't generate a new
                                           softline, so there needs to be a special case - this is
       t@@ -1851,7 +1967,7 @@ ledit_buffer_delete_range_base(
                                           same line, but it now includes the rest of the second line
                                           (FIXME: this is probably not the best thing to do) */
                                        ledit_x_softline_to_pos(
       -                                    ll1, x, sl1 + 1 < new_softlines ? sl1 + 1 : sl1, &new_byte
       +                                    buffer, l1, x, sl1 + 1 < ll1->softlines ? sl1 + 1 : sl1, &new_byte
                                        );
                                }
                        }
       t@@ -1919,22 +2035,21 @@ ledit_buffer_delete_range_base(
                        *new_byte_ret = new_byte;
        }
        
       -/* FIXME: always normalize lines */
       +/* FIXME: any way to make this more efficient? */
        void
        ledit_buffer_resize_textview(ledit_buffer *buffer) {
                buffer->total_height = 0;
       -        int tmp_w, tmp_h;
                int text_w, text_h;
                ledit_window_get_textview_size(buffer->window, &text_w, &text_h);
                for (int i = 0; i < buffer->lines_num; i++) {
                        ledit_line *line = ledit_buffer_get_line(buffer, i);
       -                pango_layout_set_width(line->layout, text_w * PANGO_SCALE);
       -                pango_layout_get_pixel_size(line->layout, &tmp_w, &tmp_h);
       -                line->h = tmp_h;
                        line->w = text_w;
       +                line->text_dirty = 1;
       +                set_pango_text_and_highlight(buffer, i);
                        line->y_offset = buffer->total_height;
                        line->dirty = 1;
       -                buffer->total_height += tmp_h;
       +                line->h_dirty = 0;
       +                buffer->total_height += line->h;
                }
                ledit_window_set_scroll_max(buffer->window, buffer->total_height);
                if (buffer->display_offset > 0 &&
       t@@ -1956,6 +2071,7 @@ ledit_buffer_scroll(ledit_buffer *buffer, long new_offset) {
        }
        
        /* FIXME: there's gotta be a better/more efficient way to do this... */
       +/* FIXME: make sure h_dirty is not set here */
        void
        ledit_buffer_get_nearest_legal_pos(
            ledit_buffer *buffer,
       t@@ -1967,10 +2083,9 @@ ledit_buffer_get_nearest_legal_pos(
                int x, sl_useless;
                ledit_window_get_textview_size(buffer->window, &text_w, &text_h);
                ledit_line *lline = ledit_buffer_get_line(buffer, line);
       -        pango_layout_get_cursor_pos(
       -            lline->layout, byte, &strong, &weak
       -        );
       -        ledit_pos_to_x_softline(lline, byte, &x, &sl_useless);
       +        PangoLayout *layout = get_pango_layout(buffer, line);
       +        pango_layout_get_cursor_pos(layout, byte, &strong, &weak);
       +        ledit_pos_to_x_softline(buffer, line, byte, &x, &sl_useless);
                long cursor_y = strong.y / PANGO_SCALE + lline->y_offset;
                PangoRectangle ink, log;
                if (cursor_y < buffer->display_offset) {
       t@@ -1980,13 +2095,14 @@ ledit_buffer_get_nearest_legal_pos(
                                lline = ledit_buffer_get_line(buffer, ++hline);
                        }
                        /* the current hard line is now the one at the very top of the screen*/
       -                int num_sl = pango_layout_get_line_count(lline->layout);
       +                layout = get_pango_layout(buffer, hline);
       +                int num_sl = lline->softlines;
                        int cur_y_off = 0;
                        int sl_index = -1;
                        PangoLayoutLine *sl;
                        /* search for first soft line completely on-screen */
                        for (int i = 0; i < num_sl; i++) {
       -                        sl = pango_layout_get_line_readonly(lline->layout, i);
       +                        sl = pango_layout_get_line_readonly(layout, i);
                                if (cur_y_off + lline->y_offset >= buffer->display_offset) {
                                        sl_index = i;
                                        break;
       t@@ -1997,17 +2113,16 @@ ledit_buffer_get_nearest_legal_pos(
                        if (sl_index >= 0) {
                                /* we found the correct soft line */
                                *line_ret = hline;
       -                        ledit_x_softline_to_pos(lline, x, sl_index, byte_ret);
       +                        ledit_x_softline_to_pos(buffer, hline, x, sl_index, byte_ret);
                        } else if (hline < buffer->lines_num - 1) {
                                /* need to move to next hard line */
                                *line_ret = hline + 1;
       -                        lline = ledit_buffer_get_line(buffer, hline + 1);
       -                        ledit_x_softline_to_pos(lline, x, 0, byte_ret);
       +                        ledit_x_softline_to_pos(buffer, hline + 1, x, 0, byte_ret);
                        } else {
                                /* no idea if this can happen, but just fail and use
                                   the last soft line of the last hard line */
                                *line_ret = hline;
       -                        ledit_x_softline_to_pos(lline, x, num_sl - 1, byte_ret);
       +                        ledit_x_softline_to_pos(buffer, hline, x, num_sl - 1, byte_ret);
                        }
                } else if (cursor_y + strong.height / PANGO_SCALE >
                           buffer->display_offset + text_h) {
       t@@ -2017,13 +2132,14 @@ ledit_buffer_get_nearest_legal_pos(
                                lline = ledit_buffer_get_line(buffer, --hline);
                        }
                        /* the current hard line is now the one at the very bottom of the screen*/
       -                int num_sl = pango_layout_get_line_count(lline->layout);
       +                layout = get_pango_layout(buffer, hline);
       +                int num_sl = lline->softlines;
                        int cur_y_off = 0;
                        int sl_index = -1;
                        PangoLayoutLine *sl;
                        /* search for last soft line completely on-screen */
                        for (int i = num_sl - 1; i >= 0; i--) {
       -                        sl = pango_layout_get_line_readonly(lline->layout, i);
       +                        sl = pango_layout_get_line_readonly(layout, i);
                                if (lline->y_offset + lline->h - cur_y_off < buffer->display_offset + text_h) {
                                        sl_index = i;
                                        break;
       t@@ -2034,18 +2150,18 @@ ledit_buffer_get_nearest_legal_pos(
                        if (sl_index >= 0) {
                                /* we found the correct soft line */
                                *line_ret = hline;
       -                        ledit_x_softline_to_pos(lline, x, sl_index, byte_ret);
       +                        ledit_x_softline_to_pos(buffer, hline, x, sl_index, byte_ret);
                        } else if (hline > 0) {
                                /* need to move to previous hard line */
                                *line_ret = hline - 1;
                                lline = ledit_buffer_get_line(buffer, hline - 1);
       -                        num_sl = pango_layout_get_line_count(lline->layout);
       -                        ledit_x_softline_to_pos(lline, x, num_sl - 1, byte_ret);
       +                        num_sl = lline->softlines;
       +                        ledit_x_softline_to_pos(buffer, hline - 1, x, num_sl - 1, byte_ret);
                        } else {
                                /* no idea if this can happen, but just fail and use
                                   the first soft line of the first hard line */
                                *line_ret = hline;
       -                        ledit_x_softline_to_pos(lline, x, 0, byte_ret);
       +                        ledit_x_softline_to_pos(buffer, hline, x, 0, byte_ret);
                        }
                }
        }
       t@@ -2060,9 +2176,10 @@ ledit_xy_to_line_byte(ledit_buffer *buffer, int x, int y, int snap_to_nearest, i
                        ledit_line *line = ledit_buffer_get_line(buffer, i);
                        if ((h <= pos && h + line->h > pos) || i == buffer->lines_num - 1) {
                                int index, trailing;
       +                        PangoLayout *layout = get_pango_layout(buffer, i);
                                /* FIXME: what if i == buffer->lines_num - 1 but pos - h < 0? */
                                pango_layout_xy_to_index(
       -                            line->layout,
       +                            layout,
                                    x * PANGO_SCALE, (int)(pos - h) * PANGO_SCALE,
                                    &index, &trailing
                                );
       t@@ -2086,7 +2203,8 @@ scroll_to_pos(ledit_buffer *buffer, int line, int byte, int top) {
                int text_w, text_h;
                ledit_window_get_textview_size(buffer->window, &text_w, &text_h);
                ledit_line *ll = ledit_buffer_get_line(buffer, line);
       -        pango_layout_get_cursor_pos(ll->layout, byte, &strong, &weak);
       +        PangoLayout *layout = get_pango_layout(buffer, line);
       +        pango_layout_get_cursor_pos(layout, byte, &strong, &weak);
                long cursor_y = strong.y / PANGO_SCALE + ll->y_offset;
                if (top) {
                        ledit_buffer_scroll(buffer, cursor_y);
       t@@ -2111,8 +2229,9 @@ ledit_buffer_ensure_cursor_shown(ledit_buffer *buffer) {
                int text_w, text_h;
                ledit_window_get_textview_size(buffer->window, &text_w, &text_h);
                ledit_line *line = ledit_buffer_get_line(buffer, buffer->cur_line);
       +        PangoLayout *layout = get_pango_layout(buffer, buffer->cur_line);
                pango_layout_get_cursor_pos(
       -            line->layout, buffer->cur_index, &strong, &weak
       +            layout, buffer->cur_index, &strong, &weak
                );
                long cursor_y = strong.y / PANGO_SCALE + line->y_offset;
                if (cursor_y < buffer->display_offset) {
       t@@ -2184,18 +2303,11 @@ ledit_buffer_set_selection(ledit_buffer *buffer, int line1, int byte1, int line2
                                }
                        }
                        if (l1_new >= 0 && l2_new >= 0) {
       -                        if (l1_new == l2_new) {
       -                                ledit_buffer_set_line_selection(buffer, l1_new, b1_new, b2_new);
       -                        } else {
       -                                ledit_line *ll1 = ledit_buffer_get_line(buffer, l1_new);
       -                                ledit_buffer_set_line_selection(buffer, l1_new, b1_new, ll1->len);
       -                                ledit_buffer_set_line_selection(buffer, l2_new, 0, b2_new);
       -                                /* FIXME: optimize this */
       -                                for (int i = l1_new + 1; i < l2_new; i++) {
       -                                        if (i <= buffer->sel.line1 || i >= buffer->sel.line2) {
       -                                                ledit_line *llx = ledit_buffer_get_line(buffer, i);
       -                                                ledit_buffer_set_line_selection(buffer, i, 0, llx->len);
       -                                        }
       +                        for (int i = l1_new; i <= l2_new; i++) {
       +                                /* only change the ones that were not already selected */
       +                                if (i <= buffer->sel.line1 || i >= buffer->sel.line2) {
       +                                        ledit_line *ll = ledit_buffer_get_line(buffer, i);
       +                                        ll->highlight_dirty = 1;
                                        }
                                }
                                if (l1_new != l2_new || b1_new != b2_new)
       t@@ -2281,7 +2393,10 @@ ledit_buffer_redraw(ledit_buffer *buffer) {
                for (int i = 0; i < buffer->lines_num; i++) {
                        ledit_line *line = ledit_buffer_get_line(buffer, i);
                        if (h + line->h > buffer->display_offset) {
       -                        if (line->dirty || line->cache_index == -1) {
       +                        /* FIXME: line->text_dirty should not happen here */
       +                        if (line->text_dirty || line->highlight_dirty)
       +                                set_pango_text_and_highlight(buffer, i);
       +                        if (line->dirty || line->cache_pixmap_index == -1) {
                                        ledit_buffer_render_line(buffer, i);
                                }
                                int final_y = 0;
       t@@ -2296,8 +2411,8 @@ ledit_buffer_redraw(ledit_buffer *buffer) {
                                        final_h -= final_y + final_h -
                                                   buffer->display_offset - text_h;
                                }
       -                        ledit_cache_pixmap *pix = ledit_get_cache_pixmap(
       -                            buffer->cache, line->cache_index
       +                        cache_pixmap *pix = cache_get_pixmap(
       +                            buffer->cache, line->cache_pixmap_index
                                );
                                XCopyArea(
                                    buffer->common->dpy, pix->pixmap,
       t@@ -2308,17 +2423,18 @@ ledit_buffer_redraw(ledit_buffer *buffer) {
                                        cur_line_y = h - buffer->display_offset;
                                        cursor_displayed = 1;
                                }
       +                        if (h + line->h >= buffer->display_offset + text_h)
       +                                break;
                        }
       -                if (h + line->h >= buffer->display_offset + text_h)
       -                        break;
                        h += line->h;
                }
        
                XSetForeground(buffer->common->dpy, buffer->window->gc, buffer->theme->text_fg.pixel);
                PangoRectangle strong, weak;
                ledit_line *cur_line = ledit_buffer_get_line(buffer, buffer->cur_line);
       +        PangoLayout *layout = get_pango_layout(buffer, buffer->cur_line);
                pango_layout_get_cursor_pos(
       -            cur_line->layout, buffer->cur_index, &strong, &weak
       +            layout, buffer->cur_index, &strong, &weak
                );
                /* FIXME: long, int, etc. */
                int cursor_y = strong.y / PANGO_SCALE + cur_line_y;
       t@@ -2338,11 +2454,11 @@ ledit_buffer_redraw(ledit_buffer *buffer) {
                                if (buffer->cur_index >= cur_line->len)
                                        tmp_index = cur_line->len - 1;
                                if (tmp_index >= 0)
       -                                dir = pango_layout_get_direction(cur_line->layout, tmp_index);
       +                                dir = pango_layout_get_direction(layout, tmp_index);
        
                                int x, sli;
       -                        pango_layout_index_to_line_x(cur_line->layout, buffer->cur_index, 0, &sli, &x);
       -                        PangoLayoutLine *sl = pango_layout_get_line_readonly(cur_line->layout, sli);
       +                        pango_layout_index_to_line_x(layout, buffer->cur_index, 0, &sli, &x);
       +                        PangoLayoutLine *sl = pango_layout_get_line_readonly(layout, sli);
                                if (dir != sl->resolved_dir) {
                                        box_w = 3;
                                }
   DIR diff --git a/buffer.h b/buffer.h
       t@@ -2,20 +2,24 @@ typedef struct ledit_buffer ledit_buffer;
        
        /* FIXME: size_t for len, etc. */
        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 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 */
       +        char *text;             /* text, stored as gap buffer */
       +        int gap;                /* position of gap for gap buffer */
       +        int cap;                /* allocated space for text */
       +        int len;                /* actual length of text */
       +        int w;                  /* width in pixels */
       +        int h;                  /* height in pixels */
       +        long y_offset;          /* pixel offset starting at the top of the file */
       +        int cache_pixmap_index; /* index of pixmap in cache, or -1 if not assigned */
       +        int cache_layout_index; /* index of pango layout in cache, or -1 if not assigned */
       +        int cursor_index;       /* cursor index if it should be highlighted, -1 else */
       +        int softlines;          /* number of softlines - cached from PangoLayout */
       +        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 highlight_dirty;   /* whether highlight (cursor or selection) needs to be
       +                                 * updated still in the PangoLayout before rendering */
       +        char h_dirty;           /* whether height needs to be recalculated */
        } ledit_line;
        
        typedef struct {
       t@@ -64,15 +68,14 @@ int ledit_buffer_load_file(ledit_buffer *buffer, char *filename, int line, char 
        int ledit_buffer_write_to_file(ledit_buffer *buffer, char *filename, char **errstr);
        void ledit_buffer_destroy(ledit_buffer *buffer);
        void ledit_buffer_normalize_line(ledit_line *line);
       -void ledit_buffer_set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end_byte);
        void ledit_buffer_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index);
        void ledit_buffer_wipe_line_cursor_attrs(ledit_buffer *buffer, int line);
        void ledit_buffer_render_line(ledit_buffer *buffer, int line_index);
        ledit_line *ledit_buffer_get_line(ledit_buffer *buffer, int index);
        int ledit_buffer_line_visible(ledit_buffer *buffer, int index);
        int ledit_buffer_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);
       +void ledit_pos_to_x_softline(ledit_buffer *buffer, int line, int pos, int *x_ret, int *softline_ret);
       +void ledit_x_softline_to_pos(ledit_buffer *buffer, int line, int x, int softline, int *pos_ret);
        int ledit_line_next_utf8(ledit_line *line, int index);
        int ledit_line_prev_utf8(ledit_line *line, int index);
        int ledit_buffer_next_cursor_pos(ledit_buffer *buffer, int line, int byte, int num);
       t@@ -182,5 +185,5 @@ void ledit_buffer_scroll_to_pos_top(ledit_buffer *buffer, int line, int byte);
        void ledit_buffer_scroll_to_pos_bottom(ledit_buffer *buffer, int line, int byte);
        /* FIXME: just make generic sort range */
        void ledit_buffer_sort_selection(int *line1, int *byte1, int *line2, int *byte2);
       -int ledit_line_next_non_whitespace(ledit_line *line, int byte);
       +int ledit_line_next_non_whitespace(ledit_buffer *buffer, int line, int byte);
        void ledit_buffer_insert_mark(ledit_buffer *buffer, char *mark, int len, int line, int byte);
   DIR diff --git a/cache.c b/cache.c
       t@@ -1,3 +1,6 @@
       +#include <stdio.h>
       +#include <assert.h>
       +#include <limits.h>
        #include <stdlib.h>
        #include <X11/Xlib.h>
        #include <X11/Xutil.h>
       t@@ -9,73 +12,153 @@
        #include "cache.h"
        
        ledit_cache *
       -ledit_cache_create(ledit_common *common) {
       -        /* FIXME: prevent overflow */
       +cache_create(Display *dpy) {
                ledit_cache *cache = ledit_malloc(sizeof(ledit_cache));
       -        cache->dpy = common->dpy;
       -        cache->entries = ledit_malloc(20 * sizeof(ledit_cache_pixmap));
       -        for (int i = 0; i < 20; i++) {
       -                cache->entries[i].pixmap = None;
       -                cache->entries[i].draw = NULL;
       -                cache->entries[i].line = -1;
       +        cache->dpy = dpy;
       +        cache->pixmaps = ledit_reallocarray(NULL, PIXMAP_CACHE_INITIAL_SIZE, sizeof(cache_pixmap));
       +        cache->layouts = ledit_reallocarray(NULL, LAYOUT_CACHE_SIZE, sizeof(cache_layout));
       +        for (size_t i = 0; i < PIXMAP_CACHE_INITIAL_SIZE; i++) {
       +                cache->pixmaps[i].pixmap = None;
       +                cache->pixmaps[i].draw = NULL;
       +                cache->pixmaps[i].line = -1;
                }
       -        cache->entries_num = 20;
       -        cache->cur_replace_index = -1;
       +        for (size_t i = 0; i < LAYOUT_CACHE_SIZE; i++) {
       +                cache->layouts[i].layout = NULL;
       +                cache->layouts[i].line = -1;
       +        }
       +        cache->num_pixmaps = PIXMAP_CACHE_INITIAL_SIZE;
       +        cache->num_layouts = LAYOUT_CACHE_SIZE;
       +        cache->cur_pixmap_index = cache->cur_layout_index = 0;
                return cache;
        }
        
        void
       -ledit_cache_flush(ledit_cache *cache) {
       -        for (int i = 0; i < cache->entries_num; i++) {
       -                cache->entries[i].line = -1;
       +cache_flush(
       +    ledit_cache *cache, void *callback_data,
       +    void (*set_pixmap_line)(void *, int, int),
       +    void (*set_layout_line)(void *, int, int)) {
       +        cache_invalidate_from_line(
       +            cache, 0, callback_data, set_pixmap_line, set_layout_line
       +        );
       +}
       +
       +void
       +cache_invalidate_from_line(
       +    ledit_cache *cache, int start, void *callback_data,
       +    void (*set_pixmap_line)(void *, int, int),
       +    void (*set_layout_line)(void *, int, int)) {
       +        for (size_t i = 0; i < cache->num_pixmaps; i++) {
       +                if (cache->pixmaps[i].line >= start) {
       +                        set_pixmap_line(callback_data, cache->pixmaps[i].line, -1);
       +                        cache->pixmaps[i].line = -1;
       +                }
       +        }
       +        for (size_t i = 0; i < cache->num_layouts; i++) {
       +                if (cache->layouts[i].line >= start) {
       +                        set_layout_line(callback_data, cache->layouts[i].line, -1);
       +                        cache->layouts[i].line = -1;
       +                }
                }
        }
        
        void
       -ledit_cache_destroy(ledit_cache *cache) {
       -        for (int i = 0; i < cache->entries_num; i++) {
       -                if (cache->entries[i].pixmap != None)
       -                        XFreePixmap(cache->dpy, cache->entries[i].pixmap);
       -                if (cache->entries[i].draw != NULL)
       -                        XftDrawDestroy(cache->entries[i].draw);
       +cache_destroy(ledit_cache *cache) {
       +        for (size_t i = 0; i < cache->num_pixmaps; i++) {
       +                if (cache->pixmaps[i].pixmap != None)
       +                        XFreePixmap(cache->dpy, cache->pixmaps[i].pixmap);
       +                if (cache->pixmaps[i].draw != NULL)
       +                        XftDrawDestroy(cache->pixmaps[i].draw);
       +        }
       +        for (size_t i = 0; i < cache->num_layouts; i++) {
       +                if (cache->layouts[i].layout != NULL)
       +                        g_object_unref(cache->layouts[i].layout);
                }
       -        free(cache->entries);
       +        free(cache->pixmaps);
       +        free(cache->layouts);
                free(cache);
        }
        
       -/* returns a cache index that is currently not needed (if needed, the cache size is increased)
       -   whether it is needed or not is checked with line_needed, to which
       -   callback_data is always passed as the first argument */
       -int
       -ledit_get_unneeded_cache_index(ledit_cache *cache, void *callback_data, int (*line_needed)(void *, int)) {
       -        int entry_index;
       +cache_pixmap *
       +cache_get_pixmap(ledit_cache *cache, int index) {
       +        assert(index >= 0 && (size_t)index < cache->num_pixmaps);
       +        return &cache->pixmaps[index];
       +}
       +
       +cache_layout *
       +cache_get_layout(ledit_cache *cache, int index) {
       +        assert(index >= 0 && (size_t)index < cache->num_layouts);
       +        return &cache->layouts[index];
       +}
       +
       +/* FIXME: standardize overflow checking */
       +static void
       +err_overflow(void) {
       +        fprintf(stderr, "ERROR: Integer overflow in cache handling.\n");
       +        exit(1);
       +}
       +
       +/* FIXME: decide on int or size_t, but not both */
       +/* or maybe ssize_t */
       +void
       +cache_assign_pixmap_index(
       +    ledit_cache *cache, int line,
       +    void *callback_data,
       +    int (*line_needed)(void *, int),
       +    void (*set_pixmap_line)(void *, int, int)) {
                int line_index;
       -        /* start at 1 because the cache->cur_replace_index is actually the last entry that was replaced */
       -        for (int i = 1; i <= cache->entries_num; i++) {
       -                entry_index = (i + cache->cur_replace_index) % cache->entries_num;
       -                line_index = cache->entries[entry_index].line;
       +        size_t entry_index;
       +        for (size_t i = 0; i <= cache->num_pixmaps; i++) {
       +                entry_index = (i + cache->cur_pixmap_index) % cache->num_pixmaps;
       +                line_index = cache->pixmaps[entry_index].line;
                        /* replace line when entry isn't assigned or currently assigned line is not visible */
                        if (line_index == -1 ||
                            (line_index >= 0 &&
                             !line_needed(callback_data, line_index))) {
       -                        cache->cur_replace_index = entry_index;
       -                        return entry_index;
       +                        cache->cur_pixmap_index = (entry_index + 1) % cache->num_pixmaps;
       +                        if (entry_index > INT_MAX)
       +                                err_overflow();
       +                        cache_pixmap *pix = &cache->pixmaps[entry_index];
       +                        if (pix->line >= 0)
       +                                set_pixmap_line(callback_data, pix->line, -1);
       +                        pix->line = line;
       +                        set_pixmap_line(callback_data, line, (int)entry_index);
       +                        return;
                        }
                }
        
                /* no free entry found, increase cache size */
       -        cache->entries = ledit_realloc(cache->entries, cache->entries_num * 2 * sizeof(ledit_cache_pixmap));
       -        entry_index = cache->entries_num;
       -        for (int i = cache->entries_num; i < cache->entries_num * 2; i++) {
       -                cache->entries[i].line = -1;
       -                cache->entries[i].pixmap = None;
       -                cache->entries[i].draw = NULL;
       +        /* FIXME: what is the ideal size to resize to? */
       +        /* FIXME: overflow */
       +        /* FIXME: maybe have maximum cache size */
       +        cache->pixmaps = ledit_reallocarray(cache->pixmaps, cache->num_pixmaps * 2, sizeof(cache_pixmap));
       +        entry_index = cache->num_pixmaps;
       +        for (size_t i = cache->num_pixmaps; i < cache->num_pixmaps * 2; i++) {
       +                cache->pixmaps[i].line = -1;
       +                cache->pixmaps[i].pixmap = None;
       +                cache->pixmaps[i].draw = NULL;
                }
       -        cache->entries_num *= 2;
       -        return entry_index;
       +        cache->num_pixmaps *= 2;
       +        if (entry_index > INT_MAX)
       +                err_overflow();
       +        cache_pixmap *pix = &cache->pixmaps[entry_index];
       +        pix->line = line;
       +        set_pixmap_line(callback_data, line, (int)entry_index);
        }
        
       -ledit_cache_pixmap *
       -ledit_get_cache_pixmap(ledit_cache *cache, int index) {
       -        return &cache->entries[index];
       +/* FIXME: perhaps use "real" clock cache management, i.e. set a bit on a cache entry
       +   when it is used so it isn't invalidated yet. */
       +void
       +cache_assign_layout_index(
       +    ledit_cache *cache, int line,
       +    void *callback_data,
       +    void (*set_layout_line)(void *, int, int)) {
       +        size_t old = cache->cur_layout_index;
       +        cache->cur_layout_index = (cache->cur_layout_index + 1) % cache->num_layouts;
       +        if (old > INT_MAX)
       +                err_overflow();
       +        cache_layout *layout = &cache->layouts[old];
       +        if (layout->line >= 0)
       +                set_layout_line(callback_data, layout->line, -1);
       +        layout->line = line;
       +        set_layout_line(callback_data, line, (int)old);
        }
   DIR diff --git a/cache.h b/cache.h
       t@@ -1,19 +1,125 @@
       +/*
       + *The maximum number of layouts in the cache.
       + */
       +#define LAYOUT_CACHE_SIZE 40
       +
       +/*
       + * The initial number of pixmas in the cache.
       + * The size is increased when more pixmaps are visible
       + * at the same time than there are entries in the cache.
       + */
       +#define PIXMAP_CACHE_INITIAL_SIZE 20
       +
        typedef struct {
                Pixmap pixmap;
                XftDraw *draw;
       -        int w, h;
       -        int line;
       -} ledit_cache_pixmap;
       +        int w, h; /* width and height of the pixmap */
       +        int line;/* the line associated with this entry, or -1 if unassigned */
       +} cache_pixmap;
       +
       +typedef struct {
       +        PangoLayout *layout;
       +        int line; /* the line associated with this entry, or -1 if unassigned */
       +} cache_layout;
        
        typedef struct {
                Display *dpy;
       -        ledit_cache_pixmap *entries;
       -        int entries_num;
       -        int cur_replace_index;
       +        cache_pixmap *pixmaps;
       +        cache_layout *layouts;
       +        size_t num_pixmaps;
       +        size_t num_layouts;
       +        size_t cur_pixmap_index; /* current replacement index for pixmaps */
       +        size_t cur_layout_index; /* current replacement index for layouts */
        } ledit_cache;
        
       -ledit_cache *ledit_cache_create(ledit_common *common);
       -void ledit_cache_flush(ledit_cache *cache);
       -void ledit_cache_destroy(ledit_cache *cache);
       -ledit_cache_pixmap *ledit_get_cache_pixmap(ledit_cache *cache, int index);
       -int ledit_get_unneeded_cache_index(ledit_cache *cache, void *callback_data, int (*line_needed)(void *, int));
       +/* FIXME: maybe handle pixmap creation and resizing here */
       +
       +/*
       + * Create cache using X Display dpy (this is
       + * needed to destroy the pixmaps in the end).
       + */
       +ledit_cache *cache_create(Display *dpy);
       +
       +/*
       + * Reset line index of every cache entry (pixmaps and layouts).
       + * set_pixmap_line is called with callback_data as its first argument,
       + * a line index which has been removed from the pixmap cache as the
       + * second argument, and '-1' as the third argument.
       + * set_layout_line is the same, but for the layout cache.
       + */
       +void cache_flush(
       +    ledit_cache *cache,
       +    void *callback_data,
       +    void (*set_pixmap_line)(void *, int, int),
       +    void (*set_layout_line)(void *, int, int)
       +);
       +
       +/*
       + * Like cache_flush, but only line numbers >= start are invalidated.
       + */
       +void cache_invalidate_from_line(
       +    ledit_cache *cache, int start,
       +    void *callback_data,
       +    void (*set_pixmap_line)(void *, int, int),
       +    void (*set_layout_line)(void *, int, int)
       +);
       +
       +/*
       + * Destroy cache.
       + */
       +void cache_destroy(ledit_cache *cache);
       +
       +/*
       + * Get the cache_pixmap at index.
       + */
       +cache_pixmap *cache_get_pixmap(ledit_cache *cache, int index);
       +
       +/*
       + * Get the cache_layout at index.
       + */
       +cache_layout *cache_get_layout(ledit_cache *cache, int index);
       +
       +/*
       + * The following two functions have a somewhat cumbersome interface
       + * because set_pixmap_line and set_layout_line are required as helper
       + * functions instead of just returning the cache index and letting the
       + * caller handle the updating. However, this has led to horrible bugs
       + * in the past, when one of the updating steps was forgotten, so this
       + * interface was designed instead to avoid some of those more basic bugs.
       + */
       +
       +/*
       + * Assign an unneeded pixmap cache index to the line with index 'line'.
       + * line_needed is used to check if a line is needed.
       + * It is called with callback_data as the first argument and the
       + * line to be checked as the second argument.
       + * The line of the cache entry is set to 'line' and if a line was
       + * set before, it is reset by calling 'reset_pixmap_line' with
       + * 'callback_data' as the first argument, the old line as the second
       + * argument, and '-1' as the third argument.
       + * Similarly, the cache index of the new line is changed by calling
       + * 'set_pixmap_line' with the new cache index as the third argument.
       + */
       +void cache_assign_pixmap_index(
       +    ledit_cache *cache, int line,
       +    void *callback_data,
       +    int (*line_needed)(void *, int),
       +    void (*set_pixmap_line)(void *, int, int)
       +);
       +
       +/*
       + * Assign a layout cache index to the line with index 'line'.
       + * Since it is not clear which layouts are needed more, this just
       + * uses the next index in a clock fashion.
       + * The line of the cache entry is set to 'line' and if a line was
       + * set before, it is reset by calling 'set_layout_line' with
       + * 'callback_data' as the first argument, the old line as the second
       + * argument, and '-1' as the third argument.
       + * Similarly, the cache index of the new line is changed by calling
       + * 'set_layout_line' with the new cache index as the third argument.
       + */
       +void cache_assign_layout_index(
       +    ledit_cache *cache, int line,
       +    void *callback_data,
       +    void (*set_layout_line)(void *, int, int)
       +);
   DIR diff --git a/keys_basic.c b/keys_basic.c
       t@@ -654,7 +654,7 @@ scroll_lines(ledit_buffer *buffer, int lines, int dir) {
                        ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
                        ledit_buffer_get_cursor_pixel_pos(buffer, buffer->cur_line, buffer->cur_index, &x, &y, &h);
                        /* get the middle position of char */
       -                ledit_pos_to_x_softline(ll, buffer->cur_index, &x, &sli);
       +                ledit_pos_to_x_softline(buffer, buffer->cur_line, buffer->cur_index, &x, &sli);
                        long abs_pos = ll->y_offset + y;
                        ledit_window_get_textview_size(buffer->window, &text_w, &text_h);
                        if (lines > 0)
       t@@ -670,7 +670,7 @@ scroll_lines(ledit_buffer *buffer, int lines, int dir) {
                        int start, end;
                        ledit_buffer_get_softline_bounds(buffer, buffer->cur_line, sli, &start, &end);
                        ll = ledit_buffer_get_line(buffer, buffer->cur_line);
       -                ledit_x_softline_to_pos(ll, x, sli, &buffer->cur_index);
       +                ledit_x_softline_to_pos(buffer, buffer->cur_line, x, sli, &buffer->cur_index);
                        ledit_buffer_get_cursor_pixel_pos(buffer, buffer->cur_line, buffer->cur_index, &x, &y, &h);
                        long new_abs_pos = ll->y_offset + y;
                        ledit_buffer_scroll(buffer, buffer->display_offset + (new_abs_pos - abs_pos));
       t@@ -806,7 +806,7 @@ move_half_screen(ledit_buffer *buffer, int movement) {
                /* try to keep current x position of cursor */
                int x, softline;
                /* FIXME: properly document what uses PANGO_SCALE and what not */
       -        ledit_pos_to_x_softline(ll, buffer->cur_index, &x, &softline);
       +        ledit_pos_to_x_softline(buffer, buffer->cur_line, buffer->cur_index, &x, &softline);
                ledit_xy_to_line_byte(
                    buffer, x / PANGO_SCALE, y, 0,
                    &buffer->cur_line, &buffer->cur_index
       t@@ -1355,7 +1355,8 @@ backspace(ledit_buffer *buffer, char *text, int len) {
                        int i = ledit_line_prev_utf8(l, buffer->cur_index);
                        delete_range(buffer, 0, 0, buffer->cur_line, buffer->cur_index, buffer->cur_line, i, 0);
                }
       -        ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
       +        /* FIXME: This was probably a mistake earlier, right?
       +        ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);*/
                return (struct action){ACTION_NONE, NULL};
        }
        
       t@@ -1374,7 +1375,8 @@ delete_key(ledit_buffer *buffer, char *text, int len) {
                        int i = ledit_line_next_utf8(cur_line, buffer->cur_index);
                        delete_range(buffer, 0, 0, buffer->cur_line, buffer->cur_index, buffer->cur_line, i, 0);
                }
       -        ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
       +        /* FIXME: This was probably a mistake earlier, right?
       +        ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);*/
                return (struct action){ACTION_NONE, NULL};
        }
        
       t@@ -1419,8 +1421,8 @@ move_to_eol(ledit_buffer *buffer, char *text, int len) {
                                    buffer, buffer->cur_line, buffer->cur_index
                                );
                        }
       +                ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
                }
       -        ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
                return (struct action){ACTION_NONE, NULL};
        }
        
       t@@ -1594,7 +1596,6 @@ escape_key(ledit_buffer *buffer, char *text, int len) {
                        }
                        buffer->sel.line1 = buffer->sel.line2 = -1;
                        buffer->sel.byte1 = buffer->sel.byte2 = -1;
       -                /* FIXME: optimize this to avoid first wiping and then setting the attrs */
                        ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
                }
                return (struct action){ACTION_NONE, NULL};
       t@@ -1629,16 +1630,14 @@ move_cursor_up_down(ledit_buffer *buffer, int dir) {
                    num, &new_line, &new_softline
                );
        
       -        ledit_line *cur_lline = ledit_buffer_get_line(buffer, buffer->cur_line);
       -        ledit_line *new_lline = ledit_buffer_get_line(buffer, new_line);
                if (cb != NULL) {
                        int start, end;
                        ledit_buffer_get_softline_bounds(buffer, new_line, new_softline, &start, &end);
                        cb(buffer, new_line, start, KEY_MOTION_LINE);
                } else {
                        int lineno, x;
       -                ledit_pos_to_x_softline(cur_lline, buffer->cur_index, &x, &lineno);
       -                ledit_x_softline_to_pos(new_lline, x, new_softline, &buffer->cur_index);
       +                ledit_pos_to_x_softline(buffer, buffer->cur_line, buffer->cur_index, &x, &lineno);
       +                ledit_x_softline_to_pos(buffer, new_line, x, new_softline, &buffer->cur_index);
                        if (buffer->cur_line != new_line)
                                ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line);
                        buffer->cur_line = new_line;
       t@@ -1746,13 +1745,12 @@ cursor_to_first_non_ws(ledit_buffer *buffer, char *text, int len) {
                if (num != 0)
                        return err_invalid_key(buffer);
                int new_index = 0;
       -        ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
                if (hard_line_based) {
       -                new_index = ledit_line_next_non_whitespace(ll, 0);
       +                new_index = ledit_line_next_non_whitespace(buffer, buffer->cur_line, 0);
                } else {
                        int start, end;
                        ledit_buffer_get_pos_softline_bounds(buffer, buffer->cur_line, buffer->cur_index, &start, &end);
       -                new_index = ledit_line_next_non_whitespace(ll, start);
       +                new_index = ledit_line_next_non_whitespace(buffer, buffer->cur_line, start);
                        /* next non-whitespace might be on next softline */
                        if (new_index >= end) {
                                new_index = ledit_buffer_prev_cursor_pos(
   DIR diff --git a/keys_command.c b/keys_command.c
       t@@ -305,7 +305,6 @@ edit_submit(ledit_buffer *buffer, char *key_text, int len) {
        /* FIXME: support visual mode, i.e. change selection to new place? */
        void
        search_next(ledit_buffer *buffer) {
       -        /* FIXME: avoid this when line doesn't change */
                ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line);
                enum ledit_search_state ret = ledit_search_next(buffer, &buffer->cur_line, &buffer->cur_index);
                ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
       t@@ -315,7 +314,6 @@ search_next(ledit_buffer *buffer) {
        
        void
        search_prev(ledit_buffer *buffer) {
       -        /* FIXME: avoid this when line doesn't change */
                ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line);
                enum ledit_search_state ret = ledit_search_prev(buffer, &buffer->cur_line, &buffer->cur_index);
                ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
   DIR diff --git a/memory.c b/memory.c
       t@@ -1,7 +1,9 @@
        #include <stdio.h>
       +#include <stdint.h>
        #include <stdlib.h>
        #include <string.h>
        
       +/* FIXME: clean up on exit */
        static void
        fatal_err(const char *msg) {
                fprintf(stderr, "%s", msg);
       t@@ -64,3 +66,24 @@ ledit_strcat(const char *str1, const char *str2) {
        
                return ret;
        }
       +
       +/*
       + * This is from OpenBSD (adapted to exit on error):
       + * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
       + */
       +
       +/*
       + * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
       + * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
       + */
       +#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
       +
       +void *
       +ledit_reallocarray(void *optr, size_t nmemb, size_t size)
       +{
       +        if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
       +            nmemb > 0 && SIZE_MAX / nmemb < size) {
       +                fatal_err("Out of memory.\n");
       +        }
       +        return realloc(optr, size * nmemb);
       +}
   DIR diff --git a/memory.h b/memory.h
       t@@ -4,3 +4,4 @@ void *ledit_malloc(size_t size);
        void *ledit_calloc(size_t nmemb, size_t size);
        void *ledit_realloc(void *ptr, size_t size);
        char *ledit_strcat(const char *str1, const char *str2);
       +void *ledit_reallocarray(void *optr, size_t nmemb, size_t size);