URI: 
       tCache pre-rendered text - ledit - Text editor (WIP)
  HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ledit.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit e9c86a9b95b0a349d3f34f2a15dd3afffafc3b52
   DIR parent 155d5f07022e7d2fa4de2374b4ba6c1e9023e124
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Sun,  4 Apr 2021 22:29:52 +0200
       
       Cache pre-rendered text
       
       Diffstat:
         M .gitignore                          |       1 +
         M ledit.c                             |     296 ++++++++++++++++++++++++-------
       
       2 files changed, 231 insertions(+), 66 deletions(-)
       ---
   DIR diff --git a/.gitignore b/.gitignore
       t@@ -1,4 +1,5 @@
        tmp
        ledit
       +ledit_old
        *.core
        *.o
   DIR diff --git a/ledit.c b/ledit.c
       t@@ -1,3 +1,4 @@
       +/* FIXME: horizontal scrolling (also need cache to avoid too large pixmaps) */
        #include <math.h>
        #include <stdio.h>
        #include <errno.h>
       t@@ -31,6 +32,8 @@ struct key {
                void (*func)(void); /* callback function */
        };
        
       +#define MAX_CACHE_PIXELS 100
       +
        static struct {
                Display *dpy;
                GC gc;
       t@@ -53,6 +56,17 @@ static struct {
                Atom wm_delete_msg;
        } state;
        
       +/* FIXME: possibly use at least 32 bits int? */
       +static struct {
       +        Pixmap pix;
       +        XftDraw *draw;
       +        int pix_w, pix_h;
       +        long start_offset;
       +        int valid_height;
       +        int begin_line, begin_softline;
       +        int dirty;
       +} cache;
       +
        static void mainloop(void);
        static void setup(int argc, char *argv[]);
        static void cleanup(void);
       t@@ -74,17 +88,21 @@ main(int argc, char *argv[]) {
        
        static struct line {
                PangoLayout *layout;
       -        XftDraw *draw;
                char *text;
                size_t cap;
                size_t len;
       -        Pixmap pix;
                int w;
                int h;
       -        int pix_w;
       -        int pix_h;
       +        long y_offset;
       +        /*
       +        XftDraw *draw;
       +        Pixmap pix;
       +        unsigned int pix_w;
       +        unsigned int pix_h;
       +        */
                char dirty;
        } *lines = NULL;
       +
        static size_t lines_num = 0;
        static size_t lines_cap = 0;
        
       t@@ -92,7 +110,7 @@ static int cur_line = 0;
        static int cur_subline = 0;
        static int cur_index = 0;
        static int trailing = 0;
       -static int total_height = 0;
       +static long total_height = 0;
        static double cur_display_offset = 0;
        
        static void
       t@@ -104,13 +122,96 @@ init_line(struct line *l) {
                pango_layout_set_wrap(l->layout, PANGO_WRAP_WORD_CHAR);
                l->text = NULL;
                l->cap = l->len = 0;
       -        l->pix = None;
       +        /*l->pix = None;*/
                /* FIXME: does this set line height reasonably when no text yet? */
                pango_layout_get_pixel_size(l->layout, &l->w, &l->h);
       -        l->dirty = 1;
       +        l->y_offset = 0;
       +        //l->dirty = 1;
        }
        
       -static void recalc_height_absolute(void);
       +static void recalc_cur_line_size(void);
       +static void recalc_line_size_absolute(void);
       +
       +enum CachePosition {
       +        CACHE_NONE,
       +        CACHE_BOTH,
       +        CACHE_TOP,
       +        CACHE_BOTTOM
       +};
       +
       +static void redraw_cache_complete(enum CachePosition pos);
       +static void redraw_cache_after_cur_line(void);
       +static void redraw_cache_only_cur_line(void);
       +
       +#define MAX_INT(a, b) ((a) > (b) ? (a) : (b))
       +#define MIN_INT(a, b) ((a) < (b) ? (a) : (b))
       +
       +/* FIXME: Implement pos */
       +static void
       +redraw_cache_complete(enum CachePosition pos) {
       +        long start = cur_display_offset > MAX_CACHE_PIXELS ? (long)(cur_display_offset) - MAX_CACHE_PIXELS : 0;
       +        long end = (long)(cur_display_offset) + state.h + MAX_CACHE_PIXELS;
       +        int start_line = 0;
       +        int end_line = 0;
       +
       +        /* FIXME: make this more efficient - we can't start at cur_line
       +         * because that may be off screen */
       +        /*
       +        for (start_line = cur_line;
       +             lines[start_line].y_offset > cur_display_offset;
       +             start_line--) {
       +        }
       +
       +        for (end_line = cur_line;
       +             end_line < lines_num &&
       +             lines[end_line].y_offset + lines[end_line].h < cur_display_offset + state.h;
       +             end_line++) {
       +        }
       +        */
       +
       +        for (start_line = 0;
       +             start_line < lines_num - 1 &&
       +             lines[start_line].y_offset + lines[start_line].h <= cur_display_offset;
       +             start_line++) {
       +                /* NOP */
       +        }
       +        for (end_line = start_line;
       +             end_line < lines_num - 1 &&
       +             lines[end_line].y_offset + lines[end_line].h < cur_display_offset + state.h;
       +             end_line++) {
       +                /* NOP */
       +        }
       +
       +        if (lines[start_line].y_offset < start) {
       +                printf("FIX THIS CODE 1!\n");
       +        }
       +        if (lines[end_line].y_offset + lines[end_line].h > end) {
       +                printf("FIX THIS CODE 2!\n");
       +        }
       +
       +        /* FIXME: only wipe what is necessary */
       +        XftDrawRect(cache.draw, &state.bg, 0, 0, cache.pix_w, cache.pix_h);
       +        int cur_y = 0;
       +        for (int i = start_line; i <= end_line; i++) {
       +                if (lines[i].w > cache.pix_w || cur_y + lines[i].h > cache.pix_h) {
       +                        break; /* should never happen */
       +                }
       +                pango_xft_render_layout(cache.draw, &state.fg, lines[i].layout, 0, cur_y * PANGO_SCALE);
       +                cur_y += lines[i].h;
       +        }
       +        cache.valid_height = cur_y;
       +        cache.start_offset = lines[start_line].y_offset;
       +        cache.begin_line = start_line;
       +        cache.dirty = 0;
       +}
       +
       +static void
       +redraw_cache_after_cur_line(void) {
       +}
       +
       +static void
       +redraw_cache_only_cur_line(void) {
       +}
        
        static void
        insert_text(struct line *l, int index, char *text, int len) {
       t@@ -127,8 +228,8 @@ insert_text(struct line *l, int index, char *text, int len) {
                memcpy(l->text + index, text, len);
                l->len += len;
                pango_layout_set_text(l->layout, l->text, l->len);
       -        recalc_height_absolute();
       -        l->dirty = 1;
       +        recalc_cur_line_size();
       +        cache.dirty = 1;
        }
        
        static void insert_line_entry(int index);
       t@@ -136,6 +237,7 @@ static void insert_line_entry(int index);
        static void
        render_line(struct line *l) {
                /* FIXME: check for <= 0 on size */
       +        /*
                if (l->pix == None) {
                        l->pix = XCreatePixmap(state.dpy, state.back_buf, l->w + 10, l->h + 10, state.depth);
                        l->pix_w = l->w + 10;
       t@@ -153,6 +255,7 @@ render_line(struct line *l) {
                XftDrawRect(l->draw, &state.bg, 0, 0, l->w, l->h);
                pango_xft_render_layout(l->draw, &state.fg, l->layout, 0, 0);
                l->dirty = 0;
       +        */
        }
        
        static void
       t@@ -171,7 +274,7 @@ append_line(int text_index, int line_index) {
                if (text_index != -1) {
                        struct line *l = &lines[line_index];
                        int len = l->len - text_index;
       -                new_l->pix = None;
       +                //new_l->pix = None;
                        new_l->len = len;
                        new_l->cap = len + 10;
                        new_l->text = malloc(new_l->cap);
       t@@ -182,6 +285,7 @@ append_line(int text_index, int line_index) {
                        pango_layout_set_text(l->layout, l->text, l->len);
                        /* FIXME: set height here */
                }
       +        /* FIXME: update line heights, etc. */
        }
        
        static void change_keyboard(char *lang);
       t@@ -207,6 +311,31 @@ static int scroll_dragging = 0;
        static int scroll_grab_handle = 0;
        
        static void
       +set_line_cursor_attrs(int line, int index) {
       +        if (cur_mode == NORMAL) {
       +                PangoAttribute *attr0 = pango_attr_background_new(0, 0, 0);
       +                PangoAttribute *attr1 = pango_attr_foreground_new(65535, 65535, 65535);
       +                attr0->start_index = index;
       +                attr0->end_index = index + 1;
       +                attr1->start_index = index;
       +                attr1->end_index = index + 1;
       +                PangoAttribute *attr2 = pango_attr_insert_hyphens_new(FALSE);
       +                PangoAttrList *list = pango_attr_list_new();
       +                pango_attr_list_insert(list, attr0);
       +                pango_attr_list_insert(list, attr1);
       +                pango_attr_list_insert(list, attr2);
       +                pango_layout_set_attributes(lines[line].layout, list);
       +        } else {
       +                pango_layout_set_attributes(lines[line].layout, basic_attrs);
       +        }
       +}
       +
       +static void
       +wipe_line_cursor_attrs(int line) {
       +        pango_layout_set_attributes(lines[line].layout, basic_attrs);
       +}
       +
       +static void
        mainloop(void) {
                XEvent event;
                int xkb_event_type;
       t@@ -348,7 +477,10 @@ mainloop(void) {
                                XSetForeground(state.dpy, state.gc, state.bg.pixel);
                                XFillRectangle(state.dpy, state.back_buf, state.gc, 0, 0, state.w, state.h);
                                int h = 0;
       +                        if (cache.dirty)
       +                                redraw_cache_complete(CACHE_BOTH);
                                /*int cur_line_height = 0;*/
       +                        /*
                                int tmp_w, tmp_h;
                                int cur_line_y = 0;
                                int cursor_displayed = 0;
       t@@ -394,24 +526,32 @@ mainloop(void) {
                                                break;
                                        h += lines[i].h;
                                }
       +                        */
       +                        double offset = cur_display_offset - cache.start_offset;
       +                        if (offset < 0) {
       +                                printf("FIX THIS CODE 3!\n");
       +                                offset = 0;
       +                        }
       +                        XCopyArea(state.dpy, cache.pix, state.back_buf, state.gc, 0, (int)offset, state.w - 10, state.h, 0, 0);
                                need_redraw = 0;
        
                                XSetForeground(state.dpy, state.gc, state.fg.pixel);
                                PangoRectangle strong, weak;
                                pango_layout_get_cursor_pos(lines[cur_line].layout, cur_index, &strong, &weak);
       -                        int cursor_y = strong.y / PANGO_SCALE + cur_line_y;
       -                        if (cursor_displayed && cursor_y >= 0) {
       +                        /* FIXME: long, int, etc. */
       +                        long cursor_y = strong.y / PANGO_SCALE + lines[cur_line].y_offset;
       +                        if (cursor_y >= cur_display_offset && cursor_y < cur_display_offset + state.h) {
                                        if (cur_mode == NORMAL && cur_index == lines[cur_line].len) {
                                                XFillRectangle(
                                                    state.dpy, state.back_buf, state.gc,
       -                                            strong.x / PANGO_SCALE, cursor_y,
       +                                            strong.x / PANGO_SCALE, cursor_y - (long)cur_display_offset,
                                                    10, strong.height / PANGO_SCALE
                                                );
                                        } else if (cur_mode == INSERT) {
                                                XDrawLine(
                                                    state.dpy, state.back_buf, state.gc,
       -                                            strong.x / PANGO_SCALE, cursor_y,
       -                                            strong.x / PANGO_SCALE, (strong.y + strong.height) / PANGO_SCALE + cur_line_y
       +                                            strong.x / PANGO_SCALE, cursor_y - (long)cur_display_offset,
       +                                            strong.x / PANGO_SCALE, (strong.y + strong.height) / PANGO_SCALE + lines[cur_line].y_offset - (long)cur_display_offset
                                                );
                                        }
                                }
       t@@ -480,13 +620,20 @@ setup(int argc, char *argv[]) {
                state.cm = DefaultColormap(state.dpy, state.screen);
        
                memset(&attrs, 0, sizeof(attrs));
       -        attrs.background_pixmap = None;
       +        attrs.background_pixel = BlackPixel(state.dpy, state.screen);
                attrs.colormap = state.cm;
       +        /* this causes the window contents to be kept
       +         * when it is resized, leading to less flicker */
       +        attrs.bit_gravity = NorthWestGravity;
                state.win = XCreateWindow(state.dpy, DefaultRootWindow(state.dpy), 0, 0,
                    state.w, state.h, 0, state.depth,
       -            InputOutput, state.vis, CWBackPixmap | CWColormap, &attrs);
       +            InputOutput, state.vis, CWBackPixel | CWColormap | CWBitGravity, &attrs);
        
                state.back_buf = XdbeAllocateBackBufferName(state.dpy, state.win, XdbeBackground);
       +        cache.pix = XCreatePixmap(state.dpy, state.back_buf, 500, 500, state.depth);
       +        cache.pix_w = cache.pix_h = 500;
       +        cache.draw = XftDrawCreate(state.dpy, cache.pix, state.vis, state.cm);
       +        cache.dirty = 1;
        
                memset(&gcv, 0, sizeof(gcv));
                gcv.line_width = 1;
       t@@ -543,59 +690,56 @@ button_release(void) {
        
        static void
        ensure_cursor_shown(void) {
       -        int cur_line_y = -1;
       -        int h = 0;
       -        /* FIXME: cache this to avoid useless recalculation */
       -        for (int i = 0; i < lines_num; i++) {
       -                if (cur_line == i) {
       -                        cur_line_y = h;
       -                        break;
       -                }
       -                h += lines[i].h;
       -        }
       -        if (cur_line_y < 0)
       -                return;
                PangoRectangle strong, weak;
                pango_layout_get_cursor_pos(lines[cur_line].layout, cur_index, &strong, &weak);
       -        int cursor_y = strong.y / PANGO_SCALE + cur_line_y;
       +        long cursor_y = strong.y / PANGO_SCALE + lines[cur_line].y_offset;
                if (cursor_y < cur_display_offset) {
                        cur_display_offset = cursor_y;
       +                cache.dirty = 1;
                } else if (cursor_y + strong.height / PANGO_SCALE > cur_display_offset + state.h) {
                        cur_display_offset = cursor_y - state.h + strong.height / PANGO_SCALE;
       +                cache.dirty = 1;
                }
        }
        
        static void
       -recalc_height(void) {
       -        /*
       -        int w, h;
       -        pango_layout_get_pixel_size(lines[cur_line].layout, &w, &h);
       -        total_height += (h - cur_line_height);
       -        */
       -        if (total_height < 0)
       -                total_height = 0; /* should never actually happen */
       -        /*cur_line_height = h;*/
       -}
       -
       -static void
       -set_cur_line_height(void) {
       +recalc_cur_line_size(void) {
                int w, h;
                pango_layout_get_pixel_size(lines[cur_line].layout, &w, &h);
       -        lines[cur_line].h = h;
       -        /*cur_line_height = h;*/
       +        lines[cur_line].w = w;
       +        /* if height changed, set height of current line
       +         * and adjust offsets of all lines following it */
       +        if (lines[cur_line].h != h) {
       +                int delta = h - lines[cur_line].h;
       +                lines[cur_line].h = h;
       +                /* protect against underflow even though
       +                 * it should never happen anyways... */
       +                if (delta < 0 && total_height < -delta)
       +                        total_height = 0;
       +                else
       +                        total_height += delta;
       +                for (int i = cur_line + 1; i < lines_num; i++) {
       +                        /* yeah, maybe I should just use a signed type... */
       +                        if (delta < 0 && lines[i].y_offset < -delta)
       +                                lines[i].y_offset = 0;
       +                        else
       +                                lines[i].y_offset += delta;
       +                }
       +        }
        }
        
        static void
       -recalc_height_absolute(void) {
       +recalc_line_size_absolute(void) {
                int w, h;
                total_height = 0;
       +        /* completely recalculate line sizes and offsets from scratch */
                for (int i = 0; i < lines_num; i++) {
       +                lines[i].y_offset = total_height;
                        pango_layout_get_pixel_size(lines[i].layout, &w, &h);
                        total_height += h;
                        lines[i].w = w;
                        lines[i].h = h;
                }
       -        set_cur_line_height();
        }
        
        static void
       t@@ -608,11 +752,19 @@ resize_window(int w, int h) {
                        /* 10 pixels for scrollbar */
                        pango_layout_set_width(lines[i].layout, (w - 10) * PANGO_SCALE);
                        pango_layout_get_pixel_size(lines[i].layout, &tmp_w, &tmp_h);
       -                total_height += tmp_h;
                        lines[i].h = tmp_h;
       -                lines[i].dirty = 1;
       +                lines[i].w = tmp_w;
       +                lines[i].y_offset = total_height;
       +                total_height += tmp_h;
       +        }
       +        if (cache.pix_w < state.w - 10 || cache.pix_h < state.h + 2 * MAX_CACHE_PIXELS) {
       +                XFreePixmap(state.dpy, cache.pix);
       +                cache.pix = XCreatePixmap(state.dpy, state.back_buf, state.w, state.h + 2 * MAX_CACHE_PIXELS + 50, state.depth);
       +                cache.pix_w = state.w;
       +                cache.pix_h = state.h + 2 * MAX_CACHE_PIXELS + 50;
       +                XftDrawChange(cache.draw, cache.pix);
                }
       -        //set_cur_line_height();
       +        cache.dirty = 1;
        }
        
        static void
       t@@ -651,8 +803,9 @@ backspace(void) {
                        cur_index = i;
                        pango_layout_set_text(l->layout, l->text, l->len);
                }
       -        lines[cur_line].dirty = 1;
       -        recalc_height_absolute();
       +        set_line_cursor_attrs(cur_line, cur_index);
       +        cache.dirty = 1;
       +        recalc_cur_line_size();
        }
        
        static void
       t@@ -677,8 +830,9 @@ delete_key(void) {
                        l->len -= i - cur_index;
                        pango_layout_set_text(l->layout, l->text, l->len);
                }
       -        lines[cur_line].dirty = 1;
       -        recalc_height_absolute();
       +        set_line_cursor_attrs(cur_line, cur_index);
       +        cache.dirty = 1;
       +        recalc_cur_line_size();
        }
        
        static void
       t@@ -703,7 +857,8 @@ move_cursor(int dir) {
                        else
                                cur_index = lines[cur_line].len;
                }
       -        lines[cur_line].dirty = 1;
       +        set_line_cursor_attrs(cur_line, cur_index);
       +        cache.dirty = 1;
        }
        
        static void
       t@@ -719,11 +874,14 @@ cursor_right(void) {
        static void
        return_key(void) {
                append_line(cur_index, cur_line);
       -        lines[cur_line].dirty = 1;
       +        /* FIXME: these aren't needed, right? This only works in insert mode
       +         * anyways, so there's nothing to wipe */
       +        wipe_line_cursor_attrs(cur_line);
                cur_line++;
       -        lines[cur_line].dirty = 1;
       +        set_line_cursor_attrs(cur_line, cur_index);
       +        cache.dirty = 1;
                cur_index = 0;
       -        recalc_height_absolute();
       +        recalc_line_size_absolute();
        }
        
        static void
       t@@ -740,7 +898,8 @@ escape_key(void) {
                } else {
                        cursor_left();
                }
       -        lines[cur_line].dirty = 1;
       +        set_line_cursor_attrs(cur_line, cur_index);
       +        cache.dirty = 1;
                /*
                if (cur_index > 0)
                        cursor_left();
       t@@ -755,7 +914,8 @@ i_key(void) {
                        pango_layout_set_attributes(lines[i].layout, NULL);
                }
                */
       -        lines[cur_line].dirty = 1;
       +        wipe_line_cursor_attrs(cur_line);
       +        cache.dirty = 1;
        }
        
        static void
       t@@ -765,7 +925,7 @@ line_down(void) {
                int maxlines = pango_layout_get_line_count(lines[cur_line].layout);
                PangoLayoutLine *line = pango_layout_get_line_readonly(lines[cur_line].layout, lineno);
                if (lineno == maxlines - 1) {
       -                lines[cur_line].dirty = 1;
       +                wipe_line_cursor_attrs(cur_line);
                        /* move to the next hard line */
                        if (cur_line < lines_num - 1) {
                                cur_line++;
       t@@ -785,7 +945,8 @@ line_down(void) {
                }
                if (cur_index > 0 && cur_mode == NORMAL && cur_index >= lines[cur_line].len)
                        cursor_left();
       -        lines[cur_line].dirty = 1;
       +        set_line_cursor_attrs(cur_line, cur_index);
       +        cache.dirty = 1;
        }
        
        static void
       t@@ -794,7 +955,7 @@ line_up(void) {
                pango_layout_index_to_line_x(lines[cur_line].layout, cur_index, 0, &lineno, &x);
                PangoLayoutLine *line = pango_layout_get_line_readonly(lines[cur_line].layout, lineno);
                if (lineno == 0) {
       -                lines[cur_line].dirty = 1;
       +                wipe_line_cursor_attrs(cur_line);
                        /* move to the previous hard line */
                        if (cur_line > 0) {
                                cur_line--;
       t@@ -815,13 +976,15 @@ line_up(void) {
                }
                if (cur_index > 0 && cur_mode == NORMAL && cur_index >= lines[cur_line].len)
                        cursor_left();
       -        lines[cur_line].dirty = 1;
       +        set_line_cursor_attrs(cur_line, cur_index);
       +        cache.dirty = 1;
        }
        
        static void
        zero_key(void) {
                cur_index = 0;
       -        lines[cur_line].dirty = 1;
       +        set_line_cursor_attrs(cur_line, cur_index);
       +        cache.dirty = 1;
        }
        
        static struct key keys_en[] = {
       t@@ -924,6 +1087,7 @@ key_press(XEvent event) {
                if (cur_mode == INSERT && !found && n > 0) {
                        insert_text(&lines[cur_line], cur_index, buf, n);
                        cur_index += n;
       +                recalc_cur_line_size();
                }
                ensure_cursor_shown();
        }