URI: 
       tSplit code into several files and clean up a bit - 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 824365813f86fcdc01434477f39a8734183a8b18
   DIR parent 88b7f6c08a1e2a47f33aa5b4d33f18df60b5011f
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Sun, 11 Apr 2021 22:05:27 +0200
       
       Split code into several files and clean up a bit
       
       Diffstat:
         M .gitignore                          |       1 -
         M Makefile                            |      19 +++++++++++++------
         A buffer.c                            |     269 +++++++++++++++++++++++++++++++
         A buffer.h                            |      40 +++++++++++++++++++++++++++++++
         A cache.c                             |      98 +++++++++++++++++++++++++++++++
         A cache.h                             |      12 ++++++++++++
         A common.h                            |      31 +++++++++++++++++++++++++++++++
         M ledit.c                             |    1050 +++++++++++++------------------
         A memory.c                            |      41 +++++++++++++++++++++++++++++++
         A memory.h                            |       4 ++++
       
       10 files changed, 951 insertions(+), 614 deletions(-)
       ---
   DIR diff --git a/.gitignore b/.gitignore
       t@@ -1,5 +1,4 @@
        tmp
        ledit
       -ledit_old
        *.core
        *.o
   DIR diff --git a/Makefile b/Makefile
       t@@ -7,18 +7,25 @@ PREFIX = /usr/local
        MANPREFIX = ${PREFIX}/man
        
        BIN = ${NAME}
       -SRC = ${BIN:=.c}
        MAN1 = ${BIN:=.1}
        
       -CFLAGS = -g -D_POSIX_C_SOURCE=200809L `pkg-config --cflags x11 xkbfile pangoxft xext`
       -LDFLAGS += `pkg-config --libs x11 xkbfile pangoxft xext` -lm
       +OBJ = ${BIN:=.o} cache.o buffer.o memory.o
       +HDR = cache.h buffer.h memory.h common.h
       +
       +CFLAGS_LEDIT = ${CFLAGS} -g -Wall -Wextra -D_POSIX_C_SOURCE=200809L `pkg-config --cflags x11 xkbfile pangoxft xext`
       +LDFLAGS_LEDIT = ${LDFLAGS} `pkg-config --libs x11 xkbfile pangoxft xext` -lm
        
        all: ${BIN}
        
       -.c:
       -        ${CC} ${CFLAGS} ${LDFLAGS} -o $@ $<
       +${OBJ} : ${HDR}
       +
       +.c.o:
       +        ${CC} -c -o $@ $< ${CFLAGS_LEDIT}
       +
       +${BIN}: ${OBJ}
       +        ${CC} -o $@ ${OBJ} ${LDFLAGS_LEDIT}
        
        clean:
       -        rm -f ${BIN}
       +        rm -f ${BIN} ${OBJ}
        
        .PHONY: all clean
   DIR diff --git a/buffer.c b/buffer.c
       t@@ -0,0 +1,269 @@
       +#include <X11/Xlib.h>
       +#include <X11/Xutil.h>
       +#include <pango/pangoxft.h>
       +#include <X11/extensions/Xdbe.h>
       +
       +#include "memory.h"
       +#include "common.h"
       +#include "buffer.h"
       +#include "cache.h"
       +
       +static PangoAttrList *basic_attrs = NULL;
       +
       +static void init_line(ledit_buffer *buffer, ledit_line *line);
       +static void recalc_line_size_absolute(ledit_buffer *buffer);
       +static void recalc_single_line_size(ledit_buffer *buffer, int line_index);
       +
       +/* FIXME: destroy basic_attrs somewhere */
       +ledit_buffer *
       +ledit_create_buffer(ledit_common_state *state) {
       +        if (basic_attrs == NULL) {
       +                basic_attrs = pango_attr_list_new();
       +                PangoAttribute *no_hyphens = pango_attr_insert_hyphens_new(FALSE);
       +                pango_attr_list_insert(basic_attrs, no_hyphens);
       +        }
       +
       +        ledit_buffer *buffer = ledit_malloc(sizeof(ledit_buffer));
       +        buffer->state = state;
       +        buffer->lines = NULL;
       +        buffer->lines_num = 0;
       +        buffer->lines_cap = 0;
       +        buffer->cur_line = 0;
       +        buffer->cur_index = 0;
       +        buffer->trailing = 0;
       +        buffer->total_height = 0;
       +        buffer->display_offset = 0;
       +        ledit_append_line(buffer, -1, -1);
       +
       +        return buffer;
       +}
       +
       +void
       +ledit_destroy_buffer(ledit_buffer *buffer) {
       +        for (int i = 0; i < buffer->lines_num; i++) {
       +                g_object_unref(buffer->lines[i].layout);
       +                free(buffer->lines[i].text);
       +        }
       +        free(buffer->lines);
       +        free(buffer);
       +}
       +
       +void
       +ledit_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index) {
       +        if (buffer->state->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(buffer->lines[line].layout, list);
       +        } else {
       +                pango_layout_set_attributes(buffer->lines[line].layout, basic_attrs);
       +        }
       +        buffer->lines[line].dirty = 1;
       +}
       +
       +void
       +ledit_wipe_line_cursor_attrs(ledit_buffer *buffer, int line) {
       +        pango_layout_set_attributes(buffer->lines[line].layout, basic_attrs);
       +        buffer->lines[line].dirty = 1;
       +}
       +
       +void
       +ledit_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, int len) {
       +        ledit_line *line = &buffer->lines[line_index];
       +        if (len == -1)
       +                len = strlen(text);
       +        if (line->len + len > line->cap) {
       +                line->cap *= 2;
       +                if (line->cap == 0)
       +                        line->cap = 2;
       +                line->text = ledit_realloc(line->text, line->cap);
       +        }
       +        memmove(line->text + index + len, line->text + index, line->len - index);
       +        memcpy(line->text + index, text, len);
       +        line->len += len;
       +        pango_layout_set_text(line->layout, line->text, line->len);
       +        recalc_single_line_size(buffer, line_index);
       +        line->dirty = 1;
       +}
       +
       +void
       +ledit_render_line(ledit_buffer *buffer, int line_index) {
       +        /* FIXME: check for <= 0 on size */
       +        ledit_line *line = &buffer->lines[line_index];
       +        if (line->cache_index == -1)
       +                ledit_assign_free_cache_index(buffer, line_index);
       +        ledit_cache_pixmap *pix = ledit_get_cache_pixmap(line->cache_index);
       +        /* FIXME: sensible default pixmap sizes here */
       +        if (pix->pixmap == None || pix->draw == NULL) {
       +                pix->pixmap = XCreatePixmap(
       +                    buffer->state->dpy, buffer->state->drawable,
       +                    line->w + 10, line->h + 10, buffer->state->depth
       +                );
       +                pix->w = line->w + 10;
       +                pix->h = line->h + 10;
       +                pix->draw = XftDrawCreate(
       +                    buffer->state->dpy, pix->pixmap,
       +                    buffer->state->vis, buffer->state->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;
       +                XFreePixmap(buffer->state->dpy, pix->pixmap);
       +                pix->pixmap = XCreatePixmap(
       +                    buffer->state->dpy, buffer->state->drawable,
       +                    new_w, new_h, buffer->state->depth
       +                );
       +                pix->w = new_w;
       +                pix->h = new_h;
       +                XftDrawChange(pix->draw, pix->pixmap);
       +        }
       +        XftDrawRect(pix->draw, &buffer->state->bg, 0, 0, line->w, line->h);
       +        pango_xft_render_layout(pix->draw, &buffer->state->fg, line->layout, 0, 0);
       +        line->dirty = 0;
       +}
       +
       +/* FIXME: use proper "viewport width" instead of just subtracting 10 */
       +static void
       +init_line(ledit_buffer *buffer, ledit_line *line) {
       +        line->parent_buffer = buffer;
       +        line->layout = pango_layout_new(buffer->state->context);
       +        pango_layout_set_width(line->layout, (buffer->state->w - 10) * PANGO_SCALE);
       +        pango_layout_set_font_description(line->layout, buffer->state->font);
       +        pango_layout_set_wrap(line->layout, PANGO_WRAP_WORD_CHAR);
       +        line->text = NULL;
       +        line->cap = line->len = 0;
       +        line->cache_index = -1;
       +        line->dirty = 1;
       +        /* FIXME: does this set line height reasonably when no text yet? */
       +        pango_layout_get_pixel_size(line->layout, &line->w, &line->h);
       +        line->y_offset = 0;
       +}
       +
       +/* FIXME: error checking (index out of bounds, etc.) */
       +void
       +ledit_append_line(ledit_buffer *buffer, int line_index, int text_index) {
       +        if (buffer->lines_num >= buffer->lines_cap) {
       +                buffer->lines_cap *= 2;
       +                if (buffer->lines_cap == 0)
       +                        buffer->lines_cap = 2;
       +                buffer->lines = ledit_realloc(
       +                    buffer->lines, buffer->lines_cap * sizeof(ledit_line)
       +                );
       +        }
       +        memmove(
       +            buffer->lines + line_index + 2,
       +            buffer->lines + line_index + 1,
       +            (buffer->lines_num - (line_index + 1)) * sizeof(ledit_line)
       +        );
       +        ledit_line *new_l = &buffer->lines[line_index + 1];
       +        init_line(buffer, new_l);
       +        buffer->lines_num++;
       +        if (text_index != -1) {
       +                ledit_line *l = &buffer->lines[line_index];
       +                int len = l->len - text_index;
       +                new_l->len = len;
       +                new_l->cap = len + 10;
       +                new_l->text = ledit_malloc(new_l->cap);
       +                memcpy(new_l->text, l->text + text_index, len);
       +                l->len = text_index;
       +                pango_layout_set_text(new_l->layout, new_l->text, new_l->len);
       +                pango_layout_set_text(l->layout, l->text, l->len);
       +                /* FIXME: set height here */
       +        }
       +        recalc_line_size_absolute(buffer);
       +}
       +
       +void
       +ledit_delete_line_entry(ledit_buffer *buffer, int index) {
       +        g_object_unref(buffer->lines[index].layout);
       +        free(buffer->lines[index].text);
       +        if (index < buffer->lines_num - 1)
       +                memmove(
       +                    buffer->lines + index, buffer->lines + index + 1,
       +                    (buffer->lines_num - index - 1) * sizeof(ledit_line)
       +                );
       +        buffer->lines_num--;
       +        recalc_line_size_absolute(buffer);
       +}
       +
       +/* FIXME: use some sort of gap buffer (that would make this function
       +   slightly more useful...) */
       +ledit_line *
       +ledit_get_line(ledit_buffer *buffer, int index) {
       +        return &buffer->lines[index];
       +}
       +
       +static void
       +recalc_single_line_size(ledit_buffer *buffer, int line_index) {
       +        int w, h;
       +        ledit_line *line = &buffer->lines[line_index];
       +        pango_layout_get_pixel_size(line->layout, &w, &h);
       +        line->w = w;
       +        /* if height changed, set height of current line
       +         * and adjust offsets of all lines following it */
       +        if (line->h != h) {
       +                int delta = h - line->h;
       +                line->h = h;
       +                buffer->total_height += delta;
       +                if (buffer->total_height < 0)
       +                        buffer->total_height = 0;
       +                for (int i = line_index + 1; i < buffer->lines_num; i++) {
       +                        buffer->lines[i].y_offset += delta;
       +                        if (buffer->lines[i].y_offset < 0)
       +                                buffer->lines[i].y_offset = 0;
       +                }
       +        }
       +}
       +
       +static void
       +recalc_line_size_absolute(ledit_buffer *buffer) {
       +        int w, h;
       +        buffer->total_height = 0;
       +        /* completely recalculate line sizes and offsets from scratch */
       +        for (int i = 0; i < buffer->lines_num; i++) {
       +                buffer->lines[i].y_offset = buffer->total_height;
       +                pango_layout_get_pixel_size(buffer->lines[i].layout, &w, &h);
       +                buffer->total_height += h;
       +                buffer->lines[i].w = w;
       +                buffer->lines[i].h = h;
       +        }
       +}
       +
       +int
       +ledit_line_visible(ledit_buffer *buffer, int index) {
       +        ledit_line *line = &buffer->lines[index];
       +        return line->y_offset < buffer->display_offset + buffer->state->h &&
       +               line->y_offset + line->h > buffer->display_offset;
       +}
       +
       +int
       +ledit_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index, int dir) {
       +        ledit_line *l = ledit_get_line(buffer, line_index);
       +        int new_index = byte_index;
       +        if (dir < 0) {
       +                int i = buffer->cur_index - 1;
       +                /* find valid utf8 char - this probably needs to be improved */
       +                while (i > 0 && ((l->text[i] & 0xC0) == 0x80))
       +                        i--;
       +                memmove(l->text + i, l->text + byte_index, l->len - byte_index);
       +                l->len -= byte_index - i;
       +                new_index = i;
       +        } else {
       +                int i = byte_index + 1;
       +                while (i < l->len && ((l->text[i] & 0xC0) == 0x80))
       +                        i++;
       +                memmove(l->text + byte_index, l->text + i, l->len - i);
       +                l->len -= i - byte_index;
       +        }
       +        pango_layout_set_text(l->layout, l->text, l->len);
       +        recalc_single_line_size(buffer, line_index);
       +        return new_index;
       +}
   DIR diff --git a/buffer.h b/buffer.h
       t@@ -0,0 +1,40 @@
       +typedef struct ledit_buffer ledit_buffer;
       +
       +typedef struct {
       +        PangoLayout *layout;
       +        char *text;
       +        ledit_buffer *parent_buffer;
       +        int cap; /* allocated space for text */
       +        int len; /* actual length of text */
       +        int w;
       +        int h;
       +        long y_offset; /* pixel offset starting at the top of the file */
       +        int cache_index; /* index of pixmap in cache, or -1 if not assigned */
       +        char dirty; /* whether line needs to be rendered before being draw */
       +} ledit_line;
       +
       +struct ledit_buffer {
       +        ledit_common_state *state; /* general state, e.g. display, window, etc. */
       +        ledit_line *lines; /* array of lines */
       +        int lines_cap; /* number of lines allocated in array */
       +        int lines_num; /* number of used lines */
       +        int cur_line; /* current line */
       +        int cur_index; /* current byte index in line */
       +        int trailing; /* used by pango for determining if index is at
       +                       * beginning or end of character */
       +        long total_height; /* total pixel height of all lines */
       +        double display_offset; /* current pixel offset of viewport - this
       +                                * is a double to make scrolling smoother */
       +};
       +
       +ledit_buffer *ledit_create_buffer(ledit_common_state *state);
       +void ledit_destroy_buffer(ledit_buffer *buffer);
       +void ledit_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index);
       +void ledit_wipe_line_cursor_attrs(ledit_buffer *buffer, int line);
       +void ledit_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, int len);
       +void ledit_render_line(ledit_buffer *buffer, int line_index);
       +void ledit_append_line(ledit_buffer *buffer, int line_index, int text_index);
       +void ledit_delete_line_entry(ledit_buffer *buffer, int index);
       +ledit_line *ledit_get_line(ledit_buffer *buffer, int index);
       +int ledit_line_visible(ledit_buffer *buffer, int index);
       +int ledit_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index, int dir);
   DIR diff --git a/cache.c b/cache.c
       t@@ -0,0 +1,98 @@
       +#include <stdlib.h>
       +#include <X11/Xlib.h>
       +#include <X11/Xutil.h>
       +#include <pango/pangoxft.h>
       +#include <X11/extensions/Xdbe.h>
       +
       +#include "common.h"
       +#include "memory.h"
       +#include "buffer.h"
       +#include "cache.h"
       +
       +
       +static struct {
       +        ledit_common_state *state;
       +        ledit_cache_pixmap *entries;
       +        int entries_num;
       +        int cur_replace_index;
       +} cache = {NULL, NULL, 0, -1};
       +
       +void
       +ledit_init_cache(ledit_common_state *state) {
       +        cache.state = state;
       +        /* FIXME: prevent overflow */
       +        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.entries_num = 20;
       +        cache.cur_replace_index = -1;
       +}
       +
       +void
       +ledit_flush_cache(void) {
       +        for (int i = 0; i < cache.entries_num; i++) {
       +                cache.entries[i].line = -1;
       +        }
       +}
       +
       +void
       +ledit_destroy_cache(void) {
       +        for (int i = 0; i < cache.entries_num; i++) {
       +                if (cache.entries[i].pixmap != None)
       +                        XFreePixmap(cache.state->dpy, cache.entries[i].pixmap);
       +                if (cache.entries[i].draw != NULL)
       +                        XftDrawDestroy(cache.entries[i].draw);
       +        }
       +        free(cache.entries);
       +        cache.state = NULL;
       +        cache.entries = NULL;
       +        cache.entries_num = 0;
       +        cache.cur_replace_index = -1;
       +}
       +
       +void
       +ledit_assign_free_cache_index(ledit_buffer *buffer, int new_line_index) {
       +        int entry_index;
       +        int line_index;
       +        ledit_line *line;
       +        /* 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;
       +                /* replace line when entry isn't assigned or currently assigned line is not visible */
       +                if (line_index == -1 ||
       +                    (line_index >= 0 &&
       +                     !ledit_line_visible(buffer, line_index))) {
       +                        if (line_index >= 0) {
       +                                line = ledit_get_line(buffer, line_index);
       +                                line->cache_index = -1;
       +                        }
       +                        cache.entries[entry_index].line = new_line_index;
       +                        cache.cur_replace_index = entry_index;
       +                        line = ledit_get_line(buffer, new_line_index);
       +                        line->cache_index = 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;
       +        }
       +        cache.entries_num *= 2;
       +        cache.entries[entry_index].line = new_line_index;
       +        line = ledit_get_line(buffer, new_line_index);
       +        line->cache_index = entry_index;
       +}
       +
       +ledit_cache_pixmap *
       +ledit_get_cache_pixmap(int index) {
       +        return &cache.entries[index];
       +}
   DIR diff --git a/cache.h b/cache.h
       t@@ -0,0 +1,12 @@
       +typedef struct {
       +        Pixmap pixmap;
       +        XftDraw *draw;
       +        int w, h;
       +        int line;
       +} ledit_cache_pixmap;
       +
       +void ledit_init_cache(ledit_common_state *state);
       +void ledit_flush_cache(void);
       +void ledit_destroy_cache(void);
       +ledit_cache_pixmap *ledit_get_cache_pixmap(int index);
       +void ledit_assign_free_cache_index(ledit_buffer *buffer, int line);
   DIR diff --git a/common.h b/common.h
       t@@ -0,0 +1,31 @@
       +enum ledit_mode {
       +        NORMAL = 1,
       +        INSERT = 2,
       +        VISUAL = 4
       +};
       +
       +typedef struct {
       +        Display *dpy;
       +        PangoFontMap *fontmap;
       +        PangoContext *context;
       +        PangoFontDescription *font;
       +        Visual *vis;
       +        GC gc;
       +        Window win;
       +        XdbeBackBuffer back_buf;
       +        Drawable drawable;
       +        Colormap cm;
       +        int screen;
       +        int depth;
       +        int w;
       +        int h;
       +        int scroll_dragging;
       +        int scroll_grab_handle;
       +        enum ledit_mode mode;
       +        XIM xim;
       +        XIC xic;
       +        XftColor fg;
       +        XftColor bg;
       +        XftColor scroll_bg;
       +        Atom wm_delete_msg;
       +} ledit_common_state;
   DIR diff --git a/ledit.c b/ledit.c
       t@@ -1,4 +1,5 @@
        /* FIXME: horizontal scrolling (also need cache to avoid too large pixmaps) */
       +/* FIXME: sort out types for indices (currently just int, but that might overflow) */
        #include <math.h>
        #include <stdio.h>
        #include <errno.h>
       t@@ -19,74 +20,47 @@
        #include <X11/extensions/XKBrules.h>
        #include <X11/extensions/Xdbe.h>
        
       -static enum mode {
       -        NORMAL = 1,
       -        INSERT = 2,
       -        VISUAL = 4
       -} cur_mode = INSERT;
       +#include "memory.h"
       +#include "common.h"
       +#include "buffer.h"
       +#include "cache.h"
        
        struct key {
       -        char *text;         /* for keys that correspond with text */
       -        KeySym keysym;      /* for other keys, e.g. arrow keys */
       -        enum mode modes;    /* modes in which this keybinding is functional */
       -        void (*func)(void); /* callback function */
       +        char *text;            /* for keys that correspond with text */
       +        KeySym keysym;         /* for other keys, e.g. arrow keys */
       +        enum ledit_mode modes; /* modes in which this keybinding is functional */
       +        void (*func)(void);    /* callback function */
        };
        
       -static struct {
       -        Display *dpy;
       -        GC gc;
       -        Window win;
       -        XdbeBackBuffer back_buf;
       -        Visual *vis;
       -        PangoFontMap *fontmap;
       -        PangoContext *context;
       -        PangoFontDescription *font;
       -        Colormap cm;
       -        int screen;
       -        int depth;
       -        XIM xim;
       -        XIC xic;
       -        int w;
       -        int h;
       -        XftColor fg;
       -        XftColor bg;
       -
       -        Atom wm_delete_msg;
       -} state;
       -
       -struct cache_pixmap {
       -        Pixmap pixmap;
       -        XftDraw *draw;
       -        int w, h;
       -        int line;
       -};
       -
       -/* FIXME: possibly use at least 32 bits int? */
       -static struct {
       -        struct cache_pixmap *entries;
       -        int entries_num;
       -        int cur_replace_index;
       -} cache;
       -
       -static size_t lines_num = 0;
       -static size_t lines_cap = 0;
       -
       -static int cur_line = 0;
       -static int cur_subline = 0;
       -static int cur_index = 0;
       -static int trailing = 0;
       -static long total_height = 0;
       -static double cur_display_offset = 0;
       -
       +static ledit_common_state state;
       +static ledit_buffer *buffer;
        
       +static void set_scroll_pos(double pos);
       +static void get_scroll_pos_height(double *pos, double *height);
       +static void resize_window(int w, int h);
        static void mainloop(void);
        static void setup(int argc, char *argv[]);
        static void cleanup(void);
        static void redraw(void);
       -static void drag_motion(XEvent event);
       +static int button_press(XEvent *event);
       +static int button_release(XEvent *event);
       +static int drag_motion(XEvent *event);
       +static void ensure_cursor_shown(void);
        static void resize_window(int w, int h);
       -static void button_release(void);
       -static void button_press(XEvent event);
       +
       +static void backspace(void);
       +static void delete_key(void);
       +static void move_cursor(int dir);
       +static void cursor_left(void);
       +static void cursor_right(void);
       +static void return_key(void);
       +static void escape_key(void);
       +static void i_key(void);
       +static void line_down(void);
       +static void line_up(void);
       +static void zero_key(void);
       +
       +static void change_keyboard(char *lang);
        static void key_press(XEvent event);
        
        int
       t@@ -98,208 +72,20 @@ main(int argc, char *argv[]) {
                return 0;
        }
        
       -static struct line {
       -        PangoLayout *layout;
       -        char *text;
       -        size_t cap;
       -        size_t len;
       -        int w;
       -        int h;
       -        long y_offset;
       -        int cache_index;
       -        char dirty;
       -} *lines = NULL;
       -
       -static void
       -init_cache(void) {
       -        /* FIXME: prevent overflow */
       -        cache.entries = malloc(20 * sizeof(struct cache_pixmap));
       -        if (!cache.entries) exit(1);
       -        for (int i = 0; i < 20; i++) {
       -                cache.entries[i].pixmap = None;
       -                cache.entries[i].line = -1;
       -        }
       -        cache.entries_num = 20;
       -        cache.cur_replace_index = -1;
       -}
       -
       -static void
       -assign_free_cache_index(int line) {
       -        int found = 0;
       -        int real_index;
       -        int tmp_line;
       -        /* 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++) {
       -                real_index = (i + cache.cur_replace_index) % cache.entries_num;
       -                tmp_line = cache.entries[real_index].line;
       -                /* replace line when entry isn't assigned or currently assigned line is not visible */
       -                if (tmp_line == -1 ||
       -                    (tmp_line >= 0 &&
       -                     (lines[tmp_line].y_offset >= cur_display_offset + state.h ||
       -                      lines[tmp_line].y_offset + lines[tmp_line].h <= cur_display_offset))) {
       -                        if (tmp_line >= 0)
       -                                lines[tmp_line].cache_index = -1;
       -                        cache.entries[real_index].line = line;
       -                        cache.cur_replace_index = real_index;
       -                        lines[line].cache_index = real_index;
       -                        return;
       -                }
       -        }
       -
       -        /* no free entry found, increase cache size */
       -        cache.entries = realloc(cache.entries, cache.entries_num * 2 * sizeof(struct cache_pixmap));
       -        if (!cache.entries) exit(1);
       -        real_index = cache.entries_num;
       -        for (size_t i = cache.entries_num + 1; i < cache.entries_num * 2; i++) {
       -                cache.entries[i].line = -1;
       -        }
       -        cache.entries_num *= 2;
       -        cache.entries[real_index].line = line;
       -        lines[line].cache_index = real_index;
       -}
       -
       -static void
       -init_line(struct line *l) {
       -        /* FIXME: check that layout created properly */
       -        l->layout = pango_layout_new(state.context);
       -        pango_layout_set_width(l->layout, (state.w - 10) * PANGO_SCALE);
       -        pango_layout_set_font_description(l->layout, state.font);
       -        pango_layout_set_wrap(l->layout, PANGO_WRAP_WORD_CHAR);
       -        l->text = NULL;
       -        l->cap = l->len = 0;
       -        l->cache_index = -1;
       -        l->dirty = 1;
       -        /* FIXME: does this set line height reasonably when no text yet? */
       -        pango_layout_get_pixel_size(l->layout, &l->w, &l->h);
       -        l->y_offset = 0;
       -}
       -
       -static void recalc_cur_line_size(void);
       -static void recalc_line_size_absolute(void);
       -
       -static void
       -insert_text(struct line *l, int index, char *text, int len) {
       -        if (len == -1)
       -                len = strlen(text);
       -        if (l->len + len > l->cap) {
       -                l->cap *= 2;
       -                if (l->cap == 0)
       -                        l->cap = 2;
       -                l->text = realloc(l->text, l->cap);
       -                if (!l->text) exit(1);
       -        }
       -        memmove(l->text + index + len, l->text + index, l->len - index);
       -        memcpy(l->text + index, text, len);
       -        l->len += len;
       -        pango_layout_set_text(l->layout, l->text, l->len);
       -        recalc_cur_line_size();
       -        l->dirty = 1;
       -}
       -
       -static void insert_line_entry(int index);
       -
       -static void
       -render_line(int line) {
       -        /* FIXME: check for <= 0 on size */
       -        struct line *l = &lines[line];
       -        if (l->cache_index == -1)
       -                assign_free_cache_index(line);
       -        struct cache_pixmap *pix = &cache.entries[l->cache_index];
       -        if (pix->pixmap == None) {
       -                pix->pixmap = XCreatePixmap(state.dpy, state.back_buf, l->w + 10, l->h + 10, state.depth);
       -                pix->w = l->w + 10;
       -                pix->h = l->h + 10;
       -                pix->draw = XftDrawCreate(state.dpy, pix->pixmap, state.vis, state.cm);
       -        } else if (pix->w < l->w || pix->h < l->h) {
       -                int new_w = l->w > pix->w ? l->w + 10 : pix->w + 10;
       -                int new_h = l->h > pix->h ? l->h + 10 : pix->h + 10;
       -                XFreePixmap(state.dpy, pix->pixmap);
       -                pix->pixmap = XCreatePixmap(state.dpy, state.back_buf, new_w, new_h, state.depth);
       -                pix->w = new_w;
       -                pix->h = new_h;
       -                XftDrawChange(pix->draw, pix->pixmap);
       -        }
       -        XftDrawRect(pix->draw, &state.bg, 0, 0, l->w, l->h);
       -        pango_xft_render_layout(pix->draw, &state.fg, l->layout, 0, 0);
       -        l->dirty = 0;
       -}
       -
       -static void
       -append_line(int text_index, int line_index) {
       -        if (lines_num >= lines_cap) {
       -                lines_cap *= 2;
       -                if (lines_cap == 0)
       -                        lines_cap = 2;
       -                lines = realloc(lines, lines_cap * sizeof(struct line));
       -                if (!lines) exit(1);
       -        }
       -        memmove(lines + line_index + 2, lines + line_index + 1, (lines_num - (line_index + 1)) * sizeof(struct line));
       -        struct line *new_l = &lines[line_index + 1];
       -        init_line(new_l);
       -        lines_num++;
       -        if (text_index != -1) {
       -                struct line *l = &lines[line_index];
       -                int len = l->len - text_index;
       -                new_l->len = len;
       -                new_l->cap = len + 10;
       -                new_l->text = malloc(new_l->cap);
       -                if (!new_l->text) exit(1);
       -                memcpy(new_l->text, l->text + text_index, len);
       -                l->len = text_index;
       -                pango_layout_set_text(new_l->layout, new_l->text, new_l->len);
       -                pango_layout_set_text(l->layout, l->text, l->len);
       -                /* FIXME: set height here */
       -        }
       -        /* FIXME: update line heights, etc. */
       -}
       -
       -static void change_keyboard(char *lang);
       -
       -PangoAttrList *basic_attrs;
       -
        static void
        get_scroll_pos_height(double *pos, double *height) {
       -        *height = ((double)state.h / total_height) * state.h;
       -        *pos = (cur_display_offset / (total_height - state.h)) * (state.h - *height);
       +        *height = ((double)state.h / buffer->total_height) * state.h;
       +        *pos = (buffer->display_offset /
       +               (buffer->total_height - state.h)) * (state.h - *height);
        }
        
        static void
        set_scroll_pos(double pos) {
       -        cur_display_offset = pos * (total_height / (double)state.h);
       -        if (cur_display_offset < 0)
       -                cur_display_offset = 0;
       -        if (cur_display_offset + state.h > total_height)
       -                cur_display_offset = total_height - state.h;
       -}
       -
       -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);
       -        }
       -        lines[line].dirty = 1;
       -}
       -
       -static void
       -wipe_line_cursor_attrs(int line) {
       -        pango_layout_set_attributes(lines[line].layout, basic_attrs);
       -        lines[line].dirty = 1;
       +        buffer->display_offset = pos * (buffer->total_height / (double)state.h);
       +        if (buffer->display_offset < 0)
       +                buffer->display_offset = 0;
       +        if (buffer->display_offset + state.h > buffer->total_height)
       +                buffer->display_offset = buffer->total_height - state.h;
        }
        
        static void
       t@@ -313,33 +99,22 @@ mainloop(void) {
                }
                printf("XKB (%d.%d) supported.\n", major, minor);
                /* This should select the events when the keyboard mapping changes.
       -           When e.g. 'setxkbmap us' is executed, two events are sent, but I haven't figured out how to
       -           change that. When the xkb layout switching is used (e.g. 'setxkbmap -option grp:shifts_toggle'),
       -           this issue does not occur because only a state event is sent. */
       -        XkbSelectEvents(state.dpy, XkbUseCoreKbd, XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask);
       -        XkbSelectEventDetails(state.dpy, XkbUseCoreKbd, XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask);
       +         * When e.g. 'setxkbmap us' is executed, two events are sent, but I
       +         * haven't figured out how to change that. When the xkb layout
       +         * switching is used (e.g. 'setxkbmap -option grp:shifts_toggle'),
       +         * this issue does not occur because only a state event is sent. */
       +        XkbSelectEvents(
       +            state.dpy, XkbUseCoreKbd,
       +            XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask
       +        );
       +        XkbSelectEventDetails(
       +            state.dpy, XkbUseCoreKbd, XkbStateNotify,
       +            XkbAllStateComponentsMask, XkbGroupStateMask
       +        );
                XSync(state.dpy, False);
                int running = 1;
                int change_kbd = 0;
        
       -
       -        /*draw = XftDrawCreate(state.dpy, state.back_buf, state.vis, state.cm);*/
       -        state.fontmap = pango_xft_get_font_map(state.dpy, state.screen);
       -        state.context = pango_font_map_create_context(state.fontmap);
       -
       -        state.font = pango_font_description_from_string("Monospace");
       -        pango_font_description_set_size(state.font, 16 * PANGO_SCALE);
       -
       -        basic_attrs = pango_attr_list_new();
       -        PangoAttribute *no_hyphens = pango_attr_insert_hyphens_new(FALSE);
       -        pango_attr_list_insert(basic_attrs, no_hyphens);
       -
       -        append_line(-1, -1);
       -
       -        XftColorAllocName(state.dpy, state.vis, state.cm, "#000000", &state.fg);
       -        XftColorAllocName(state.dpy, state.vis, state.cm, "#FFFFFF", &state.bg);
       -        XftColor scroll_bg;
       -        XftColorAllocName(state.dpy, state.vis, state.cm, "#CCCCCC", &scroll_bg);
                int need_redraw = 0;
                redraw();
        
       t@@ -358,63 +133,20 @@ mainloop(void) {
                                        need_redraw = 1;
                                        break;
                                case ConfigureNotify:
       -                                resize_window(event.xconfigure.width, event.xconfigure.height);
       -                                if (cur_display_offset > 0 && cur_display_offset + state.h >= total_height) {
       -                                        cur_display_offset = total_height - state.h;
       -                                        if (cur_display_offset < 0)
       -                                                cur_display_offset = 0;
       -                                }
       -                                redraw();
       +                                resize_window(
       +                                    event.xconfigure.width,
       +                                    event.xconfigure.height
       +                                );
                                        need_redraw = 1;
                                        break;
                                case ButtonPress:
       -                                switch (event.xbutton.button) {
       -                                        case Button1:
       -                                                button_press(event);
       -                                                double scroll_h, scroll_y;
       -                                                get_scroll_pos_height(&scroll_y, &scroll_h);
       -                                                int x = event.xbutton.x;
       -                                                int y = event.xbutton.y;
       -                                                if (x >= state.w - 10) {
       -                                                        scroll_dragging = 1;
       -                                                        scroll_grab_handle = y;
       -                                                        if (y < scroll_y || y > scroll_y + scroll_h) {
       -                                                                double new_scroll_y = y - scroll_h / 2;
       -                                                                set_scroll_pos(new_scroll_y);
       -                                                        }
       -                                                }
       -                                                break;
       -                                        case Button4:
       -                                                cur_display_offset -= 10;
       -                                                if (cur_display_offset < 0)
       -                                                        cur_display_offset = 0;
       -                                                break;
       -                                        case Button5:
       -                                                if (cur_display_offset + state.h < total_height) {
       -                                                        cur_display_offset += 10;
       -                                                        if (cur_display_offset + state.h > total_height)
       -                                                                cur_display_offset = total_height - state.h;
       -                                                }
       -                                                break;
       -                                }
       -                                need_redraw = 1;
       +                                need_redraw |= button_press(&event);
                                        break;
                                case ButtonRelease:
       -                                if (event.xbutton.button == Button1) {
       -                                        button_release();
       -                                        scroll_dragging = 0;
       -                                }
       +                                need_redraw |= button_release(&event);
                                        break;
                                case MotionNotify:
       -                                drag_motion(event);
       -                                if (scroll_dragging) {
       -                                        double scroll_h, scroll_y;
       -                                        get_scroll_pos_height(&scroll_y, &scroll_h);
       -                                        scroll_y += event.xbutton.y - scroll_grab_handle;
       -                                        scroll_grab_handle = event.xbutton.y;
       -                                        set_scroll_pos(scroll_y);
       -                                }
       -                                need_redraw = 1;
       +                                need_redraw |= drag_motion(&event);
                                        break;
                                case KeyPress:
                                        need_redraw = 1;
       t@@ -432,90 +164,21 @@ mainloop(void) {
                                change_kbd = 0;
                                XkbStateRec s;
                                XkbGetState(state.dpy, XkbUseCoreKbd, &s);
       -                        XkbDescPtr desc = XkbGetKeyboard(state.dpy, XkbAllComponentsMask, XkbUseCoreKbd);
       -                        char *group = XGetAtomName(state.dpy, desc->names->groups[s.group]);
       +                        XkbDescPtr desc = XkbGetKeyboard(
       +                            state.dpy, XkbAllComponentsMask, XkbUseCoreKbd
       +                        );
       +                        char *group = XGetAtomName(
       +                            state.dpy, desc->names->groups[s.group]
       +                        );
                                change_keyboard(group);
       -                        /*char *symbols = XGetAtomName(state.dpy, desc->names->symbols);*/
                                XFree(group);
       -                        /*XFree(symbols);*/
                                XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
                        }
                        if (need_redraw) {
       -                        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;
       -
       -                        /*int cur_line_height = 0;*/
       -                        int tmp_w, tmp_h;
       -                        int cur_line_y = 0;
       -                        int cursor_displayed = 0;
       -                        for (int i = 0; i < lines_num; i++) {
       -                                if (h + lines[i].h > cur_display_offset) {
       -                                        if (lines[i].dirty || lines[i].cache_index == -1) {
       -                                                render_line(i);
       -                                        }
       -                                        int final_y = 0;
       -                                        int dest_y = h - cur_display_offset;
       -                                        int final_h = lines[i].h;
       -                                        if (h < cur_display_offset) {
       -                                                dest_y = 0;
       -                                                final_y = cur_display_offset - h;
       -                                                final_h -= cur_display_offset - h;
       -                                        }
       -                                        if (dest_y + final_h > state.h) {
       -                                                final_h -= final_y + final_h - cur_display_offset - state.h;
       -                                        }
       -                                        XCopyArea(state.dpy, cache.entries[lines[i].cache_index].pixmap, state.back_buf, state.gc, 0, final_y, lines[i].w, final_h, 0, dest_y);
       -                                        if (i == cur_line) {
       -                                                cur_line_y = h - cur_display_offset;
       -                                                cursor_displayed = 1;
       -                                        }
       -                                }
       -                                if (h + lines[i].h >= cur_display_offset + state.h)
       -                                        break;
       -                                h += lines[i].h;
       -                        }
       +                        redraw();
                                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);
       -                        /* FIXME: long, int, etc. */
       -                        int cursor_y = strong.y / PANGO_SCALE + cur_line_y;
       -                        if (cursor_displayed && cursor_y >= 0) {
       -                                if (cur_mode == NORMAL && cur_index == lines[cur_line].len) {
       -                                        XFillRectangle(
       -                                            state.dpy, state.back_buf, state.gc,
       -                                            strong.x / PANGO_SCALE, cursor_y,
       -                                            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
       -                                        );
       -                                }
       -                        }
       -                        if (total_height > state.h) {
       -                                XSetForeground(state.dpy, state.gc, scroll_bg.pixel);
       -                                XFillRectangle(state.dpy, state.back_buf, state.gc, state.w - 10, 0, 10, state.h);
       -                                XSetForeground(state.dpy, state.gc, state.fg.pixel);
       -                                double scroll_h, scroll_y;
       -                                get_scroll_pos_height(&scroll_y, &scroll_h);
       -                                XFillRectangle(state.dpy, state.back_buf, state.gc, state.w - 10, (int)round(scroll_y), 10, (int)round(scroll_h));
       -                        }
       -
       -                        XdbeSwapInfo swap_info;
       -                        swap_info.swap_window = state.win;
       -                        swap_info.swap_action = XdbeBackground;
       -
       -                        if (!XdbeSwapBuffers(state.dpy, &swap_info, 1))
       -                                exit(1);
       -                        XFlush(state.dpy);
                        }
                }
       -        pango_attr_list_unref(basic_attrs);
        }
        
        static void
       t@@ -525,6 +188,8 @@ setup(int argc, char *argv[]) {
                XSetWindowAttributes attrs;
                XGCValues gcv;
        
       +        state.scroll_dragging = 0;
       +        state.scroll_grab_handle = 0;
                state.w = 500;
                state.h = 500;
                state.dpy = XOpenDisplay(NULL);
       t@@ -533,23 +198,35 @@ setup(int argc, char *argv[]) {
                /* based on http://wili.cc/blog/xdbe.html */
                int major, minor;
                if (XdbeQueryExtension(state.dpy, &major, &minor)) {
       -                printf("Xdbe (%d.%d) supported, using double buffering.\n", major, minor);
       +                printf(
       +                    "Xdbe (%d.%d) supported, using double buffering.\n",
       +                    major, minor
       +                );
                        int num_screens = 1;
                        Drawable screens[] = { DefaultRootWindow(state.dpy) };
       -                XdbeScreenVisualInfo *info = XdbeGetVisualInfo(state.dpy, screens, &num_screens);
       +                XdbeScreenVisualInfo *info = XdbeGetVisualInfo(
       +                    state.dpy, screens, &num_screens
       +                );
                        if (!info || num_screens < 1 || info->count < 1) {
                                fprintf(stderr, "No visuals support Xdbe.\n");
                                exit(1);
                        }
                        XVisualInfo xvisinfo_templ;
       -                xvisinfo_templ.visualid = info->visinfo[0].visual; // We know there's at least one
       +                /* we know there's at least one */
       +                xvisinfo_templ.visualid = info->visinfo[0].visual;
                        xvisinfo_templ.screen = 0;
                        xvisinfo_templ.depth = info->visinfo[0].depth;
                        int matches;
       -                XVisualInfo *xvisinfo_match =
       -                        XGetVisualInfo(state.dpy, VisualIDMask|VisualScreenMask|VisualDepthMask, &xvisinfo_templ, &matches);
       +                XVisualInfo *xvisinfo_match = XGetVisualInfo(
       +                    state.dpy,
       +                    VisualIDMask | VisualScreenMask | VisualDepthMask,
       +                    &xvisinfo_templ, &matches
       +                );
                        if (!xvisinfo_match || matches < 1) {
       -                        fprintf(stderr, "Couldn't match a Visual with double buffering\n");
       +                        fprintf(
       +                            stderr,
       +                            "Couldn't match a Visual with double buffering\n"
       +                        );
                                exit(1);
                        }
                        state.vis = xvisinfo_match->visual;
       t@@ -567,18 +244,38 @@ setup(int argc, char *argv[]) {
                /* 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.win = XCreateWindow(
       +            state.dpy, DefaultRootWindow(state.dpy), 0, 0,
                    state.w, state.h, 0, state.depth,
       -            InputOutput, state.vis, CWBackPixel | CWColormap | CWBitGravity, &attrs);
       +            InputOutput, state.vis,
       +            CWBackPixel | CWColormap | CWBitGravity, &attrs
       +        );
        
       -        state.back_buf = XdbeAllocateBackBufferName(state.dpy, state.win, XdbeBackground);
       -        init_cache();
       +        state.back_buf = XdbeAllocateBackBufferName(
       +            state.dpy, state.win, XdbeBackground
       +        );
       +        state.drawable = state.back_buf;
        
                memset(&gcv, 0, sizeof(gcv));
                gcv.line_width = 1;
                state.gc = XCreateGC(state.dpy, state.back_buf, GCLineWidth, &gcv);
        
       -        XSelectInput(state.dpy, state.win, StructureNotifyMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask);
       +        state.fontmap = pango_xft_get_font_map(state.dpy, state.screen);
       +        state.context = pango_font_map_create_context(state.fontmap);
       +
       +        state.font = pango_font_description_from_string("Monospace");
       +        pango_font_description_set_size(state.font, 12 * PANGO_SCALE);
       +
       +        XftColorAllocName(state.dpy, state.vis, state.cm, "#000000", &state.fg);
       +        XftColorAllocName(state.dpy, state.vis, state.cm, "#FFFFFF", &state.bg);
       +        XftColorAllocName(state.dpy, state.vis, state.cm, "#CCCCCC", &state.scroll_bg);
       +
       +        XSelectInput(
       +            state.dpy, state.win,
       +            StructureNotifyMask | KeyPressMask |
       +            ButtonPressMask | ButtonReleaseMask |
       +            PointerMotionMask | ExposureMask
       +        );
        
                state.wm_delete_msg = XInternAtom(state.dpy, "WM_DELETE_WINDOW", False);
                XSetWMProtocols(state.dpy, state.win, &state.wm_delete_msg, 1);
       t@@ -589,203 +286,320 @@ setup(int argc, char *argv[]) {
                        if ((state.xim =  XOpenIM(state.dpy, NULL, NULL, NULL)) == NULL) {
                                XSetLocaleModifiers("@im=");
                                if ((state.xim = XOpenIM(state.dpy, NULL, NULL, NULL)) == NULL) {
       -                                fprintf(stderr, "XOpenIM failed. Could not open input device.\n");
       +                                fprintf(
       +                                    stderr,
       +                                    "XOpenIM failed. Could not open input device.\n"
       +                                );
                                        exit(1);
                                }
                        }
                }
       -        state.xic = XCreateIC(state.xim, XNInputStyle, XIMPreeditNothing
       -                                           | XIMStatusNothing, XNClientWindow, state.win,
       -                                           XNFocusWindow, state.win, NULL);
       +        state.xic = XCreateIC(
       +            state.xim, XNInputStyle,
       +            XIMPreeditNothing | XIMStatusNothing,
       +            XNClientWindow, state.win,
       +            XNFocusWindow, state.win, NULL
       +        );
                if (state.xic == NULL) {
       -                fprintf(stderr, "XCreateIC failed. Could not obtain input method.\n");
       +                fprintf(
       +                    stderr,
       +                    "XCreateIC failed. Could not obtain input method.\n"
       +                );
                        exit(1);
                }
                XSetICFocus(state.xic);
        
       +        state.mode = INSERT;
       +
                XMapWindow(state.dpy, state.win);
       +
       +        ledit_init_cache(&state);
       +        buffer = ledit_create_buffer(&state);
                redraw();
        }
        
        static void
        cleanup(void) {
       +        /* FIXME: cleanup everything else */
       +        ledit_destroy_cache();
       +        ledit_destroy_buffer(buffer);
                XDestroyWindow(state.dpy, state.win);
                XCloseDisplay(state.dpy);
        }
        
        static void
        redraw(void) {
       -        XSetForeground(state.dpy, state.gc, BlackPixel(state.dpy, state.screen));
       -        XFillRectangle(state.dpy, state.back_buf, state.gc, 0, 0, state.w, state.h);
       -}
       +        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;
       +        int cur_line_y = 0;
       +        int cursor_displayed = 0;
       +        for (int i = 0; i < buffer->lines_num; i++) {
       +                ledit_line *line = ledit_get_line(buffer, i);
       +                if (h + line->h > buffer->display_offset) {
       +                        if (line->dirty || line->cache_index == -1) {
       +                                ledit_render_line(buffer, i);
       +                        }
       +                        int final_y = 0;
       +                        int dest_y = h - buffer->display_offset;
       +                        int final_h = line->h;
       +                        if (h < buffer->display_offset) {
       +                                dest_y = 0;
       +                                final_y = buffer->display_offset - h;
       +                                final_h -= buffer->display_offset - h;
       +                        }
       +                        if (dest_y + final_h > state.h) {
       +                                final_h -= final_y + final_h -
       +                                           buffer->display_offset - state.h;
       +                        }
       +                        ledit_cache_pixmap *pix = ledit_get_cache_pixmap(
       +                            line->cache_index
       +                        );
       +                        XCopyArea(
       +                            state.dpy, pix->pixmap,
       +                            state.drawable, state.gc,
       +                            0, final_y, line->w, final_h, 0, dest_y
       +                        );
       +                        if (i == buffer->cur_line) {
       +                                cur_line_y = h - buffer->display_offset;
       +                                cursor_displayed = 1;
       +                        }
       +                }
       +                if (h + line->h >= buffer->display_offset + state.h)
       +                        break;
       +                h += line->h;
       +        }
        
       -static void
       -button_press(XEvent event) {
       -}
       +        XSetForeground(state.dpy, state.gc, state.fg.pixel);
       +        PangoRectangle strong, weak;
       +        ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line);
       +        pango_layout_get_cursor_pos(
       +            cur_line->layout, buffer->cur_index, &strong, &weak
       +        );
       +        /* FIXME: long, int, etc. */
       +        int cursor_y = strong.y / PANGO_SCALE + cur_line_y;
       +        if (cursor_displayed && cursor_y >= 0) {
       +                if (state.mode == NORMAL && buffer->cur_index == cur_line->len) {
       +                        XFillRectangle(
       +                            state.dpy, state.drawable, state.gc,
       +                            strong.x / PANGO_SCALE, cursor_y,
       +                            10, strong.height / PANGO_SCALE
       +                        );
       +                } else if (state.mode == INSERT) {
       +                        XDrawLine(
       +                            state.dpy, state.drawable, state.gc,
       +                            strong.x / PANGO_SCALE, cursor_y,
       +                            strong.x / PANGO_SCALE,
       +                            (strong.y + strong.height) / PANGO_SCALE + cur_line_y
       +                        );
       +                }
       +        }
       +        if (buffer->total_height > state.h) {
       +                XSetForeground(state.dpy, state.gc, state.scroll_bg.pixel);
       +                XFillRectangle(
       +                    state.dpy, state.drawable, state.gc,
       +                    state.w - 10, 0, 10, state.h
       +                );
       +                XSetForeground(state.dpy, state.gc, state.fg.pixel);
       +                double scroll_h, scroll_y;
       +                get_scroll_pos_height(&scroll_y, &scroll_h);
       +                XFillRectangle(
       +                    state.dpy, state.drawable, state.gc,
       +                    state.w - 10, (int)round(scroll_y), 10, (int)round(scroll_h)
       +                );
       +        }
        
       -static void
       -button_release(void) {
       +        XdbeSwapInfo swap_info;
       +        swap_info.swap_window = state.win;
       +        swap_info.swap_action = XdbeBackground;
       +
       +        if (!XdbeSwapBuffers(state.dpy, &swap_info, 1))
       +                exit(1);
       +        XFlush(state.dpy);
       +}
       +
       +static int
       +button_press(XEvent *event) {
       +        int x, y;
       +        double scroll_h, scroll_y;
       +        switch (event->xbutton.button) {
       +                case Button1:
       +                        get_scroll_pos_height(&scroll_y, &scroll_h);
       +                        x = event->xbutton.x;
       +                        y = event->xbutton.y;
       +                        if (x >= state.w - 10) {
       +                                state.scroll_dragging = 1;
       +                                state.scroll_grab_handle = y;
       +                                if (y < scroll_y || y > scroll_y + scroll_h) {
       +                                        double new_scroll_y = y - scroll_h / 2;
       +                                        set_scroll_pos(new_scroll_y);
       +                                }
       +                                return 1;
       +                        }
       +                        break;
       +                case Button4:
       +                        buffer->display_offset -= 10;
       +                        if (buffer->display_offset < 0)
       +                                buffer->display_offset = 0;
       +                        return 1;
       +                case Button5:
       +                        if (buffer->display_offset + state.h <
       +                            buffer->total_height) {
       +                                buffer->display_offset += 10;
       +                                if (buffer->display_offset + state.h >
       +                                    buffer->total_height)
       +                                        buffer->display_offset =
       +                                            buffer->total_height - state.h;
       +                        }
       +                        return 1;
       +        }
       +        return 0;
        }
        
       -static void
       -ensure_cursor_shown(void) {
       -        PangoRectangle strong, weak;
       -        pango_layout_get_cursor_pos(lines[cur_line].layout, cur_index, &strong, &weak);
       -        long cursor_y = strong.y / PANGO_SCALE + lines[cur_line].y_offset;
       -        if (cursor_y < cur_display_offset) {
       -                cur_display_offset = cursor_y;
       -        } else if (cursor_y + strong.height / PANGO_SCALE > cur_display_offset + state.h) {
       -                cur_display_offset = cursor_y - state.h + strong.height / PANGO_SCALE;
       +static int
       +button_release(XEvent *event) {
       +        if (event->xbutton.button == Button1) {
       +                state.scroll_dragging = 0;
       +                return 1;
                }
       +        return 0;
        }
        
       -static void
       -recalc_cur_line_size(void) {
       -        int w, h;
       -        pango_layout_get_pixel_size(lines[cur_line].layout, &w, &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 int
       +drag_motion(XEvent *event) {
       +        if (state.scroll_dragging) {
       +                double scroll_h, scroll_y;
       +                get_scroll_pos_height(&scroll_y, &scroll_h);
       +                scroll_y += event->xbutton.y - state.scroll_grab_handle;
       +                state.scroll_grab_handle = event->xbutton.y;
       +                set_scroll_pos(scroll_y);
       +                return 1;
                }
       +        return 0;
        }
        
        static 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;
       +ensure_cursor_shown(void) {
       +        PangoRectangle strong, weak;
       +        ledit_line *line = ledit_get_line(buffer, buffer->cur_line);
       +        pango_layout_get_cursor_pos(
       +            line->layout, buffer->cur_index, &strong, &weak
       +        );
       +        long cursor_y = strong.y / PANGO_SCALE + line->y_offset;
       +        if (cursor_y < buffer->display_offset) {
       +                buffer->display_offset = cursor_y;
       +        } else if (cursor_y + strong.height / PANGO_SCALE >
       +                   buffer->display_offset + state.h) {
       +                buffer->display_offset =
       +                    cursor_y - state.h + strong.height / PANGO_SCALE;
                }
        }
        
       +/* FIXME: move the recalculation part of this to buffer.c */
        static void
        resize_window(int w, int h) {
                state.w = w;
                state.h = h;
       -        total_height = 0;
       +        buffer->total_height = 0;
                int tmp_w, tmp_h;
       -        for (int i = 0; i < lines_num; i++) {
       +        for (int i = 0; i < buffer->lines_num; i++) {
       +                ledit_line *line = ledit_get_line(buffer, i);
                        /* 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);
       -                lines[i].h = tmp_h;
       -                lines[i].w = tmp_w;
       -                lines[i].y_offset = total_height;
       -                lines[i].dirty = 1;
       -                total_height += tmp_h;
       +                pango_layout_set_width(line->layout, (w - 10) * PANGO_SCALE);
       +                pango_layout_get_pixel_size(line->layout, &tmp_w, &tmp_h);
       +                line->h = tmp_h;
       +                line->w = tmp_w;
       +                line->y_offset = buffer->total_height;
       +                line->dirty = 1;
       +                buffer->total_height += tmp_h;
       +        }
       +        if (buffer->display_offset > 0 &&
       +            buffer->display_offset + state.h >= buffer->total_height) {
       +                buffer->display_offset = buffer->total_height - state.h;
       +                if (buffer->display_offset < 0)
       +                        buffer->display_offset = 0;
                }
       -}
       -
       -static void
       -drag_motion(XEvent event) {
       -}
       -
       -static void
       -delete_line_entry(int index) {
       -        if (index < lines_num - 1)
       -                memmove(lines + index, lines + index + 1, (lines_num - index - 1) * sizeof(struct line));
       -        lines_num--;
        }
        
        static void
        backspace(void) {
       -        if (cur_index == 0) {
       -                if (cur_line != 0) {
       -                        struct line *l1 = &lines[cur_line - 1];
       -                        struct line *l2 = &lines[cur_line];
       +        if (buffer->cur_index == 0) {
       +                if (buffer->cur_line != 0) {
       +                        ledit_line *l1 = ledit_get_line(buffer, buffer->cur_line - 1);
       +                        ledit_line *l2 = ledit_get_line(buffer, buffer->cur_line);
                                int old_len = l1->len;
       -                        insert_text(l1, l1->len, l2->text, l2->len);
       -                        delete_line_entry(cur_line);
       -                        cur_line--;
       -                        cur_index = old_len;
       -                        //total_height -= cur_line_height();
       -                        //set_cur_line_height();
       +                        ledit_insert_text(
       +                            buffer, buffer->cur_line - 1,
       +                            l1->len, l2->text, l2->len
       +                        );
       +                        ledit_delete_line_entry(buffer, buffer->cur_line);
       +                        buffer->cur_line--;
       +                        buffer->cur_index = old_len;
                        }
                } else {
       -                int i = cur_index - 1;
       -                struct line *l = &lines[cur_line];
       -                /* find valid utf8 char - this probably needs to be improved */
       -                while (i > 0 && ((l->text[i] & 0xC0) == 0x80))
       -                        i--;
       -                memmove(l->text + i, l->text + cur_index, l->len - cur_index);
       -                l->len -= cur_index - i;
       -                cur_index = i;
       -                pango_layout_set_text(l->layout, l->text, l->len);
       +                buffer->cur_index = ledit_delete_unicode_char(
       +                    buffer, buffer->cur_line, buffer->cur_index, -1
       +                );
                }
       -        set_line_cursor_attrs(cur_line, cur_index);
       -        recalc_cur_line_size();
       +        ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
        }
        
        static void
        delete_key(void) {
       -        if (cur_index == lines[cur_line].len) {
       -                if (cur_line != lines_num - 1) {
       -                        struct line *l1 = &lines[cur_line];
       -                        struct line *l2 = &lines[cur_line + 1];
       -                        int old_len = l1->len;
       -                        insert_text(l1, l1->len, l2->text, l2->len);
       -                        delete_line_entry(cur_line + 1);
       -                        cur_index = old_len;
       -                        /*total_height -= cur_line_height();
       -                        set_cur_line_height();*/
       +        ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line);
       +        if (buffer->cur_index == cur_line->len) {
       +                if (buffer->cur_line != buffer->lines_num - 1) {
       +                        ledit_line *next_line = ledit_get_line(
       +                            buffer, buffer->cur_line + 1
       +                        );
       +                        int old_len = cur_line->len;
       +                        ledit_insert_text(
       +                            buffer, buffer->cur_line, cur_line->len,
       +                            next_line->text, next_line->len
       +                        );
       +                        ledit_delete_line_entry(buffer, buffer->cur_line + 1);
       +                        buffer->cur_index = old_len;
                        }
                } else {
       -                int i = cur_index + 1;
       -                struct line *l = &lines[cur_line];
       -                while (i < lines[cur_line].len && ((lines[cur_line].text[i] & 0xC0) == 0x80))
       -                        i++;
       -                memmove(l->text + cur_index, l->text + i, l->len - i);
       -                l->len -= i - cur_index;
       -                pango_layout_set_text(l->layout, l->text, l->len);
       +                buffer->cur_index = ledit_delete_unicode_char(
       +                    buffer, buffer->cur_line, buffer->cur_index, 1
       +                );
                }
       -        set_line_cursor_attrs(cur_line, cur_index);
       -        recalc_cur_line_size();
       +        ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
        }
        
        static void
        move_cursor(int dir) {
       -        int last_index = cur_index;
       -        pango_layout_move_cursor_visually(lines[cur_line].layout, TRUE, cur_index, trailing, dir, &cur_index, &trailing);
       +        int last_index = buffer->cur_index;
       +        ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line);
       +        pango_layout_move_cursor_visually(
       +            cur_line->layout, TRUE,
       +            buffer->cur_index, buffer->trailing, dir,
       +            &buffer->cur_index, &buffer->trailing
       +        );
                /* we don't currently support a difference between the cursor being at
                   the end of a soft line and the beginning of the next line */
       -        while (trailing > 0) {
       -                trailing--;
       -                cur_index++;
       -                while (cur_index < lines[cur_line].len && ((lines[cur_line].text[cur_index] & 0xC0) == 0x80))
       -                        cur_index++;
       +        while (buffer->trailing > 0) {
       +                buffer->trailing--;
       +                buffer->cur_index++;
       +                while (buffer->cur_index < cur_line->len &&
       +                       ((cur_line->text[buffer->cur_index] & 0xC0) == 0x80))
       +                        buffer->cur_index++;
                }
       -        if (cur_index < 0)
       -                cur_index = 0;
       +        if (buffer->cur_index < 0)
       +                buffer->cur_index = 0;
                /* when in normal mode, the cursor cannot be at the very end
                   of the line because it's always covering a character */
       -        if (cur_index >= lines[cur_line].len) {
       -                if (cur_mode == NORMAL)
       -                        cur_index = last_index;
       +        if (buffer->cur_index >= cur_line->len) {
       +                if (state.mode == NORMAL)
       +                        buffer->cur_index = last_index;
                        else
       -                        cur_index = lines[cur_line].len;
       +                        buffer->cur_index = cur_line->len;
                }
       -        set_line_cursor_attrs(cur_line, cur_index);
       +        ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
        }
        
        static void
       t@@ -800,112 +614,129 @@ cursor_right(void) {
        
        static void
        return_key(void) {
       -        append_line(cur_index, cur_line);
       +        ledit_append_line(buffer, buffer->cur_line, buffer->cur_index);
                /* 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++;
       -        set_line_cursor_attrs(cur_line, cur_index);
       -        cur_index = 0;
       -        recalc_line_size_absolute();
       +        ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line);
       +        buffer->cur_line++;
       +        ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
       +        buffer->cur_index = 0;
        }
        
        static void
        escape_key(void) {
       -        cur_mode = NORMAL;
       +        state.mode = NORMAL;
                PangoDirection dir = PANGO_DIRECTION_RTL;
       -        int tmp_index = cur_index;
       -        if (cur_index >= lines[cur_line].len)
       +        int tmp_index = buffer->cur_index;
       +        ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line);
       +        if (buffer->cur_index >= cur_line->len)
                        tmp_index--;
                if (tmp_index >= 0)
       -                dir = pango_layout_get_direction(lines[cur_line].layout, tmp_index);
       +                dir = pango_layout_get_direction(cur_line->layout, tmp_index);
                if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_WEAK_RTL) {
                        cursor_right();
                } else {
                        cursor_left();
                }
       -        set_line_cursor_attrs(cur_line, cur_index);
       -        /*
       -        if (cur_index > 0)
       -                cursor_left();
       -        */
       +        ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
        }
        
        static void
        i_key(void) {
       -        cur_mode = INSERT;
       -        /*
       -        for (int i = 0; i < lines_num; i++) {
       -                pango_layout_set_attributes(lines[i].layout, NULL);
       -        }
       -        */
       -        wipe_line_cursor_attrs(cur_line);
       +        state.mode = INSERT;
       +        ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line);
        }
        
        static void
        line_down(void) {
       -        int lineno, x, trailing;
       -        pango_layout_index_to_line_x(lines[cur_line].layout, cur_index, 0, &lineno, &x);
       -        int maxlines = pango_layout_get_line_count(lines[cur_line].layout);
       -        PangoLayoutLine *line = pango_layout_get_line_readonly(lines[cur_line].layout, lineno);
       +        int lineno, x;
       +        ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line);
       +        pango_layout_index_to_line_x(
       +            cur_line->layout, buffer->cur_index, 0, &lineno, &x
       +        );
       +        int maxlines = pango_layout_get_line_count(cur_line->layout);
                if (lineno == maxlines - 1) {
       -                wipe_line_cursor_attrs(cur_line);
       +                ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line);
                        /* move to the next hard line */
       -                if (cur_line < lines_num - 1) {
       -                        cur_line++;
       -                        PangoLayoutLine *nextline = pango_layout_get_line_readonly(lines[cur_line].layout, 0);
       -                        if (pango_layout_line_x_to_index(nextline, x, &cur_index, &trailing) == FALSE) {
       +                if (buffer->cur_line < buffer->lines_num - 1) {
       +                        buffer->cur_line++;
       +                        cur_line = ledit_get_line(buffer, buffer->cur_line);
       +                        PangoLayoutLine *nextline =
       +                            pango_layout_get_line_readonly(cur_line->layout, 0);
       +                        if (pango_layout_line_x_to_index(
       +                            nextline, x, &buffer->cur_index,
       +                            &buffer->trailing) == FALSE) {
                                        /* set it to *after* the last index of the line */
       -                                cur_index = nextline->start_index + nextline->length;
       +                                buffer->cur_index =
       +                                    nextline->start_index + nextline->length;
                                }
                        }
                } else {
                        /* move to the next soft line */
       -                PangoLayoutLine *nextline = pango_layout_get_line_readonly(lines[cur_line].layout, lineno + 1);
       -                if (pango_layout_line_x_to_index(nextline, x, &cur_index, &trailing) == FALSE) {
       +                PangoLayoutLine *nextline =
       +                    pango_layout_get_line_readonly(cur_line->layout, lineno + 1);
       +                if (pango_layout_line_x_to_index(
       +                    nextline, x, &buffer->cur_index,
       +                    &buffer->trailing) == FALSE) {
                                /* set it to *after* the last index of the line */
       -                        cur_index = nextline->start_index + nextline->length;
       +                        buffer->cur_index =
       +                            nextline->start_index + nextline->length;
                        }
                }
       -        if (cur_index > 0 && cur_mode == NORMAL && cur_index >= lines[cur_line].len)
       +        if (buffer->cur_index > 0 &&
       +            state.mode == NORMAL &&
       +            buffer->cur_index >= cur_line->len)
                        cursor_left();
       -        set_line_cursor_attrs(cur_line, cur_index);
       +        ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
        }
        
        static void
        line_up(void) {
       -        int lineno, x, trailing;
       -        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);
       +        int lineno, x;
       +        ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line);
       +        pango_layout_index_to_line_x(
       +            cur_line->layout, buffer->cur_index, 0, &lineno, &x
       +        );
                if (lineno == 0) {
       -                wipe_line_cursor_attrs(cur_line);
       +                ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line);
                        /* move to the previous hard line */
       -                if (cur_line > 0) {
       -                        cur_line--;
       -                        int maxlines = pango_layout_get_line_count(lines[cur_line].layout);
       -                        PangoLayoutLine *prevline = pango_layout_get_line_readonly(lines[cur_line].layout, maxlines - 1);
       -                        if (pango_layout_line_x_to_index(prevline, x, &cur_index, &trailing) == FALSE) {
       +                if (buffer->cur_line > 0) {
       +                        buffer->cur_line--;
       +                        cur_line = ledit_get_line(buffer, buffer->cur_line);
       +                        int maxlines = pango_layout_get_line_count(cur_line->layout);
       +                        PangoLayoutLine *prevline =
       +                            pango_layout_get_line_readonly(cur_line->layout, maxlines - 1);
       +                        if (pango_layout_line_x_to_index(
       +                            prevline, x, &buffer->cur_index,
       +                            &buffer->trailing) == FALSE) {
                                        /* set it to *after* the last index of the line */
       -                                cur_index = prevline->start_index + prevline->length;
       +                                buffer->cur_index =
       +                                    prevline->start_index + prevline->length;
                                }
                        }
                } else {
                        /* move to the previous soft line */
       -                PangoLayoutLine *prevline = pango_layout_get_line_readonly(lines[cur_line].layout, lineno - 1);
       -                if (pango_layout_line_x_to_index(prevline, x, &cur_index, &trailing) == FALSE) {
       +                PangoLayoutLine *prevline =
       +                    pango_layout_get_line_readonly(cur_line->layout, lineno - 1);
       +                if (pango_layout_line_x_to_index(
       +                    prevline, x, &buffer->cur_index,
       +                    &buffer->trailing) == FALSE) {
                                /* set it to *after* the last index of the line */
       -                        cur_index = prevline->start_index + prevline->length;
       +                        buffer->cur_index =
       +                            prevline->start_index + prevline->length;
                        }
                }
       -        if (cur_index > 0 && cur_mode == NORMAL && cur_index >= lines[cur_line].len)
       +        if (buffer->cur_index > 0 &&
       +            state.mode == NORMAL &&
       +            buffer->cur_index >= cur_line->len)
                        cursor_left();
       -        set_line_cursor_attrs(cur_line, cur_index);
       +        ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
        }
        
        static void
        zero_key(void) {
       -        cur_index = 0;
       -        set_line_cursor_attrs(cur_line, cur_index);
       +        buffer->cur_index = 0;
       +        ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
        }
        
        static struct key keys_en[] = {
       t@@ -959,7 +790,7 @@ static struct key keys_hi[] = {
                {"0",  0, NORMAL, &zero_key}
        };
        
       -#define LENGTH(X) (sizeof X / sizeof X[0])
       +#define LENGTH(X) (sizeof(X) / sizeof(X[0]))
        
        struct lang_keys {
                char *lang;
       t@@ -978,7 +809,7 @@ static struct lang_keys *cur_keys = &keys[0];
        
        static void change_keyboard(char *lang) {
                printf("%s\n", lang);
       -        for (int i = 0; i < LENGTH(keys); i++) {
       +        for (size_t i = 0; i < LENGTH(keys); i++) {
                        if (!strcmp(keys[i].lang, lang)) {
                                cur_keys = &keys[i];
                                break;
       t@@ -988,27 +819,32 @@ static void change_keyboard(char *lang) {
        
        static void
        key_press(XEvent event) {
       -        XWindowAttributes attrs;
                char buf[32];
                KeySym sym;
                /* FIXME: X_HAVE_UTF8_STRING See XmbLookupString(3) */
       -        int n = Xutf8LookupString(state.xic, &event.xkey, buf, sizeof(buf), &sym, NULL);
       +        int n = Xutf8LookupString(
       +            state.xic, &event.xkey, buf, sizeof(buf), &sym, NULL
       +        );
                int found = 0;
                for (int i = 0; i < cur_keys->num_keys; i++) {
                        if (cur_keys->keys[i].text) {
       -                        if (n > 0 && (cur_keys->keys[i].modes & cur_mode) && !strncmp(cur_keys->keys[i].text, buf, n)) {
       +                        if (n > 0 &&
       +                            (cur_keys->keys[i].modes & state.mode) &&
       +                             !strncmp(cur_keys->keys[i].text, buf, n)) {
                                        cur_keys->keys[i].func();
                                        found = 1;
                                }
       -                } else if ((cur_keys->keys[i].modes & cur_mode) && cur_keys->keys[i].keysym == sym) {
       +                } else if ((cur_keys->keys[i].modes & state.mode) &&
       +                            cur_keys->keys[i].keysym == sym) {
                                cur_keys->keys[i].func();
                                found = 1;
                        }
                }
       -        if (cur_mode == INSERT && !found && n > 0) {
       -                insert_text(&lines[cur_line], cur_index, buf, n);
       -                cur_index += n;
       -                recalc_cur_line_size();
       +        if (state.mode == INSERT && !found && n > 0) {
       +                ledit_insert_text(
       +                    buffer, buffer->cur_line, buffer->cur_index, buf, n
       +                );
       +                buffer->cur_index += n;
                }
                ensure_cursor_shown();
        }
   DIR diff --git a/memory.c b/memory.c
       t@@ -0,0 +1,41 @@
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +
       +static void
       +fatal_err(const char *msg) {
       +        fprintf(stderr, "%s", msg);
       +        exit(1);
       +}
       +
       +char *
       +ledit_strdup(const char *s) {
       +        char *str = strdup(s);
       +        if (!str)
       +                fatal_err("Out of memory.\n");
       +        return str;
       +}
       +
       +void *
       +ledit_malloc(size_t size) {
       +        void *ptr = malloc(size);
       +        if (!ptr)
       +                fatal_err("Out of memory.\n");
       +        return ptr;
       +}
       +
       +void *
       +ledit_calloc(size_t nmemb, size_t size) {
       +        void *ptr = calloc(nmemb, size);
       +        if (!ptr)
       +                fatal_err("Out of memory.\n");
       +        return ptr;
       +}
       +
       +void *
       +ledit_realloc(void *ptr, size_t size) {
       +        void *new_ptr = realloc(ptr, size);
       +        if (!new_ptr)
       +                fatal_err("Out of memory.\n");
       +        return new_ptr;
       +}
   DIR diff --git a/memory.h b/memory.h
       t@@ -0,0 +1,4 @@
       +char *ledit_strdup(const char *s);
       +void *ledit_malloc(size_t size);
       +void *ledit_calloc(size_t nmemb, size_t size);
       +void *ledit_realloc(void *ptr, size_t size);