URI: 
       tAdd support for config file - 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 1b405e16faddabd36b26297c27c746aa7788a2d4
   DIR parent aadae71b088e1d224e1f6615524d0cc0d51eae7a
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Thu, 26 May 2022 21:53:19 +0200
       
       Add support for config file
       
       Yay, it's another monster commit!
       
       Diffstat:
         M IDEAS                               |       5 +++++
         M LICENSE                             |       4 +++-
         M Makefile                            |      33 ++++++++++++++++++++-----------
         M README                              |      26 ++++++++++++++++++++++----
         M buffer.c                            |      23 ++++++++++++++---------
         M buffer.h                            |       3 +--
         M config.h                            |       9 +++++++++
         A configparser.c                      |    1810 +++++++++++++++++++++++++++++++
         A configparser.h                      |      80 +++++++++++++++++++++++++++++++
         M keys.c                              |      14 --------------
         M keys.h                              |      47 +++++++++++++++++++++++++++++++
         M keys_basic.c                        |     282 ++++++++++++++++++++++++++++---
         M keys_basic.h                        |       5 +++++
         D keys_basic_config.h                 |     457 -------------------------------
         M keys_command.c                      |     362 ++++++++++++++++++++-----------
         M keys_command.h                      |       8 ++++++++
         D keys_command_config.h               |     196 -------------------------------
         M keys_config.h                       |     203 +++++++++++++++++++++++++++----
         M ledit.1                             |      96 ++++++-------------------------
         M ledit.c                             |     141 ++++++++++++++++++++++++++-----
         A leditrc.5                           |     347 +++++++++++++++++++++++++++++++
         A leditrc.example                     |     300 +++++++++++++++++++++++++++++++
         M memory.c                            |      26 ++++++++++++++++++++++----
         M memory.h                            |       6 ++++++
         D theme.c                             |      57 -------------------------------
         D theme.h                             |      39 -------------------------------
         M theme_config.h                      |      20 +++++++++++++++++---
         M txtbuf.c                            |      59 +++++++++++++++++++++++++++++--
         M txtbuf.h                            |      30 ++++++++++++++++++++++++++++++
         A uglycrap.h                          |      15 +++++++++++++++
         M undo.h                              |       2 ++
         M util.c                              |      20 ++++++++++++++++++++
         M util.h                              |      28 ++++++++++++++++++++++++++++
         M view.c                              |      24 +++++++++++++-----------
         M view.h                              |      21 ++++-----------------
         M window.c                            |      33 ++++++++++++++++++++-----------
         M window.h                            |      10 +++++++---
       
       37 files changed, 3715 insertions(+), 1126 deletions(-)
       ---
   DIR diff --git a/IDEAS b/IDEAS
       t@@ -10,3 +10,8 @@
          -> I'm not sure it that's even possible in a portable way, though,
             since the keyboard layouts can be set in many different ways, so
             the entire state would somehow have to be saved to restore it again.
       +  -> Wouldn't it also make more sense to avoid the whole keyboard
       +     configuration and instead just temporarily switch to the default
       +     layout in order to map the keycodes to text? I'm not sure how to do
       +     that, though. It might not be possible at all since text can also
       +     be inserted through input methods.
   DIR diff --git a/LICENSE b/LICENSE
       t@@ -1,9 +1,11 @@
        Note 1: Some stuff is stolen from st (https://st.suckless.org)
        Note 2: Some stuff is stolen from OpenBSD (https://openbsd.org)
       +Note 3: pango-compat.{c,h} contains a bit of code copied from
       +        Pango in order to be compatible with older versions.
        
        ISC License
        
       -Copyright (c) 2022 lumidify <nobody@lumidify.org>
       +Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
        
        Permission to use, copy, modify, and/or distribute this software for any
        purpose with or without fee is hereby granted, provided that the above
   DIR diff --git a/Makefile b/Makefile
       t@@ -8,20 +8,24 @@ MANPREFIX = ${PREFIX}/man
        
        BIN = ${NAME}
        MAN1 = ${BIN:=.1}
       +MAN5 = leditrc.5
        MISCFILES = Makefile README LICENSE IDEAS NOTES TODO
        
       +DEBUG=0
       +SANITIZE=0
       +
        OBJ = \
                assert.o \
                buffer.o \
                view.o \
                cache.o \
                keys.o \
       +        configparser.o \
                keys_basic.o \
                keys_command.o \
                ledit.o \
                memory.o \
                search.o \
       -        theme.o \
                txtbuf.o \
                undo.o \
                util.o \
       t@@ -38,11 +42,11 @@ HDR = \
                cache.h \
                common.h \
                keys.h \
       +        configparser.h \
                keys_basic.h \
                keys_command.h \
                memory.h \
                search.h \
       -        theme.h \
                txtbuf.h \
                undo.h \
                util.h \
       t@@ -50,26 +54,27 @@ HDR = \
                window.h \
                cleanup.h \
                macros.h \
       -        pango-compat.h
       +        pango-compat.h \
       +        uglycrap.h
        
        CONFIGHDR = \
                config.h \
                theme_config.h \
       -        keys_basic_config.h \
       -        keys_command_config.h \
                keys_config.h
        
       -CFLAGS_LEDIT = ${CFLAGS} -g -Wall -Wextra -pedantic -D_POSIX_C_SOURCE=200809L `pkg-config --cflags x11 xkbfile pangoxft xext`
       -LDFLAGS_LEDIT = ${LDFLAGS} `pkg-config --libs x11 xkbfile pangoxft xext` -lm
       +EXTRA_CFLAGS_DEBUG0 = ${CFLAGS}
       +EXTRA_LDFLAGS_DEBUG0 = ${LDFLAGS}
       +EXTRA_CFLAGS_DEBUG1 = -DLEDIT_DEBUG -g
       +EXTRA_FLAGS_SANITIZE1 = -fsanitize=address
       +
       +CFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_CFLAGS_DEBUG${DEBUG}} -Wall -Wextra -pedantic -D_POSIX_C_SOURCE=200809L -std=c99 `pkg-config --cflags x11 xkbfile pangoxft xext`
       +LDFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_LDFLAGS_DEBUG${DEBUG}} `pkg-config --libs x11 xkbfile pangoxft xext` -lm
        
        all: ${BIN}
        
        # FIXME: make this nicer
        ledit.o window.o : config.h
       -theme.o : theme_config.h
       -keys_basic.o : keys_basic_config.h keys_config.h
       -keys_command.o : keys_command_config.h keys_config.h
       -keys.o : keys_config.h
       +configparser.o : keys_config.h theme_config.h
        
        ${OBJ} : ${HDR}
        
       t@@ -84,12 +89,16 @@ install: all
                cp -f ${BIN} "${DESTDIR}${PREFIX}/bin"
                for f in ${BIN}; do chmod 755 "${DESTDIR}${PREFIX}/bin/$$f"; done
                mkdir -p "${DESTDIR}${MANPREFIX}/man1"
       +        mkdir -p "${DESTDIR}${MANPREFIX}/man5"
                cp -f ${MAN1} "${DESTDIR}${MANPREFIX}/man1"
       +        cp -f ${MAN5} "${DESTDIR}${MANPREFIX}/man5"
                for m in ${MAN1}; do chmod 644 "${DESTDIR}${MANPREFIX}/man1/$$m"; done
       +        for m in ${MAN5}; do chmod 644 "${DESTDIR}${MANPREFIX}/man5/$$m"; done
        
        uninstall:
                for f in ${BIN}; do rm -f "${DESTDIR}${PREFIX}/bin/$$f"; done
                for m in ${MAN1}; do rm -f "${DESTDIR}${MANPREFIX}/man1/$$m"; done
       +        for m in ${MAN5}; do rm -f "${DESTDIR}${MANPREFIX}/man5/$$m"; done
        
        clean:
                rm -f ${BIN} ${OBJ}
       t@@ -97,7 +106,7 @@ clean:
        dist:
                rm -rf "${NAME}-${VERSION}"
                mkdir -p "${NAME}-${VERSION}"
       -        cp -f ${MAN1} ${SRC} ${HDR} ${CONFIGHDR} ${MISCFILES} "${NAME}-${VERSION}"
       +        cp -f ${MAN1} ${MAN5} ${SRC} ${HDR} ${CONFIGHDR} ${MISCFILES} "${NAME}-${VERSION}"
                tar cf - "${NAME}-${VERSION}" | gzip -c > "${NAME}-${VERSION}.tar.gz"
                rm -rf "${NAME}-${VERSION}"
        
   DIR diff --git a/README b/README
       t@@ -1,9 +1,6 @@
        WARNING: This is work in progress! A lot of bugs still need to be fixed
        before this can be used as a real text editor.
        
       -Note: The compiler flags currently still include -g to add debug symbols.
       -This will be removed when ledit is officially released (if I remember...).
       -
        ledit is a vi-like text editor for people who switch between keyboard
        layouts frequently and/or work with languages that require complex text
        layout.
       t@@ -18,10 +15,31 @@ On OpenBSD: pango
        On MX Linux: libpango1.0-dev, libx11-dev, libxkbfile-dev
        (this is just from memory, I need to test it with a fresh system sometime)
        
       -The documentation can be viewed in ledit.1 or at the following locations:
       +Installation:
       +Simply run 'make' and 'make install'
       +To compile with debugging symbols and some debug output, run 'make DEBUG=1'
       +To compile with fsanitize=address, run 'make SANITIZE=1'
       +
       +A sample configuration file can be found in leditrc.example.
       +Copy this to ~/.leditrc in order to use it.
       +
       +The documentation can be viewed in ledit.1 and leditrc.5 or at the following locations:
        
        gopher://lumidify.org/0/doc/ledit/ledit-current.txt
        gopher://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/0/doc/ledit/ledit-current.txt
        http://lumidify.org/doc/ledit/ledit-current.html
        http://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/doc/ledit/ledit-current.html
        https://lumidify.org/doc/ledit/ledit-current.html
       +
       +gopher://lumidify.org/0/doc/ledit/leditrc-current.txt
       +gopher://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/0/doc/ledit/leditrc-current.txt
       +http://lumidify.org/doc/ledit/leditrc-current.html
       +http://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/doc/ledit/leditrc-current.html
       +https://lumidify.org/doc/ledit/leditrc-current.html
       +
       +Note that the documentation is far from finished! None of the functions are
       +documented yet in leditrc.5 and ledit.1 is somewhat outdated. This will
       +be fixed sometime...
       +
       +Also note that nothing is stable at the moment. In particular, some of the
       +function names mentioned in leditrc.5 will probably be changed still.
   DIR diff --git a/buffer.c b/buffer.c
       t@@ -17,7 +17,6 @@
        #include "util.h"
        #include "undo.h"
        #include "cache.h"
       -#include "theme.h"
        #include "memory.h"
        #include "common.h"
        #include "txtbuf.h"
       t@@ -284,10 +283,10 @@ buffer_set_hard_line_based(ledit_buffer *buffer, int hl) {
        }
        
        void
       -buffer_add_view(ledit_buffer *buffer, ledit_theme *theme, ledit_mode mode, size_t line, size_t pos, long scroll_offset) {
       +buffer_add_view(ledit_buffer *buffer, ledit_mode mode, size_t line, size_t pos, long scroll_offset) {
                size_t new_num = add_sz(buffer->views_num, 1);
                buffer->views = ledit_reallocarray(buffer->views, new_num, sizeof(ledit_view *));
       -        buffer->views[buffer->views_num] = view_create(buffer, theme, mode, line, pos);
       +        buffer->views[buffer->views_num] = view_create(buffer, mode, line, pos);
                set_view_hard_line_text(buffer, buffer->views[buffer->views_num]);
                view_scroll(buffer->views[buffer->views_num], scroll_offset);
                buffer->views_num = new_num;
       t@@ -374,11 +373,11 @@ buffer_load_file(ledit_buffer *buffer, char *filename, size_t line, char **errst
                buffer->modified = 0;
                return 0;
        error:
       -        if (*errstr)
       +        if (errstr)
                        *errstr = strerror(errno);
                return 1;
        errorclose:
       -        if (*errstr)
       +        if (errstr)
                        *errstr = strerror(errno);
                fclose(file);
                return 1;
       t@@ -397,11 +396,11 @@ buffer_write_to_file(ledit_buffer *buffer, FILE *file, char **errstr) {
                buffer->modified = 0;
                return 0;
        error:
       -        if (*errstr)
       +        if (errstr)
                        *errstr = strerror(errno);
                return 1;
        errorclose:
       -        if (*errstr)
       +        if (errstr)
                        *errstr = strerror(errno);
                fclose(file);
                return 1;
       t@@ -413,7 +412,7 @@ buffer_write_to_fd(ledit_buffer *buffer, int fd, char **errstr) {
                if (!file) goto error;
                return buffer_write_to_file(buffer, file, errstr);
        error:
       -        if (*errstr)
       +        if (errstr)
                        *errstr = strerror(errno);
                /* catching errors on the close wouldn't
                   really make much sense anymore */
       t@@ -428,7 +427,7 @@ buffer_write_to_filename(ledit_buffer *buffer, char *filename, char **errstr) {
                if (!file) goto error;
                return buffer_write_to_file(buffer, file, errstr);
        error:
       -        if (*errstr)
       +        if (errstr)
                        *errstr = strerror(errno);
                return 1;
        }
       t@@ -445,6 +444,10 @@ buffer_destroy(ledit_buffer *buffer) {
                if (buffer->filename)
                        free(buffer->filename);
                marklist_destroy(buffer->marklist);
       +        for (size_t i = 0; i < buffer->views_num; i++) {
       +                view_destroy(buffer->views[i]);
       +        }
       +        free(buffer->views);
                free(buffer);
        }
        
       t@@ -854,6 +857,7 @@ line_byte_to_char(ledit_line *line, size_t byte) {
        static void
        buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length) {
                ledit_line *l = buffer_get_line(buffer, line);
       +        /* FIXME: somehow make sure this doesn't get optimized out? */
                (void)add_sz(start, length); /* just check that no overflow */
                ledit_assert(start + length <= l->len);
                if (start <= l->gap && start + length >= l->gap) {
       t@@ -979,6 +983,7 @@ buffer_delete_with_undo_base(
            size_t line_index2, size_t byte_index2,
            txtbuf *text_ret) {
                /* FIXME: global txtbuf to avoid allocating each time */
       +        /* actually, could just use stack variable here */
                txtbuf *buf = text_ret != NULL ? text_ret : txtbuf_new();
                sort_range(&line_index1, &byte_index1, &line_index2, &byte_index2);
                delete_range_base(
   DIR diff --git a/buffer.h b/buffer.h
       t@@ -79,8 +79,7 @@ void buffer_set_hard_line_based(ledit_buffer *buffer, int hl);
         * 'scroll_offset' is the initial pixel scroll offset.
         */
        void buffer_add_view(
       -    ledit_buffer *buffer, ledit_theme *theme,
       -    ledit_mode mode, size_t line, size_t pos, long scroll_offset
       +    ledit_buffer *buffer, ledit_mode mode, size_t line, size_t pos, long scroll_offset
        );
        
        /*
   DIR diff --git a/config.h b/config.h
       t@@ -1,3 +1,10 @@
       +#ifndef _CONFIG_H_
       +#define _CONFIG_H_
       +
       +/*************************
       + * General configuration *
       + *************************/
       +
        /* Note: These have to be less than one second */
        
        /*
       t@@ -17,3 +24,5 @@
         * events - events inbetween are discarded (nanoseconds)
         */
        #define RESIZE_TICK (long long)200000000
       +
       +#endif /* _CONFIG_H_ */
   DIR diff --git a/configparser.c b/configparser.c
       t@@ -0,0 +1,1810 @@
       +#ifdef LEDIT_DEBUG
       +#include <time.h>
       +#include "macros.h"
       +#endif
       +#include <stdio.h>
       +#include <ctype.h>
       +#include <errno.h>
       +#include <string.h>
       +#include <stdint.h>
       +#include <stdlib.h>
       +#include <limits.h>
       +
       +#include "util.h"
       +#include "memory.h"
       +#include "assert.h"
       +#include "configparser.h"
       +#include "theme_config.h"
       +#include "keys_config.h"
       +
       +/* FIXME: Replace this entire parser with something sensible.
       +   The current handwritten parser is mainly for the lulz. */
       +
       +/* FIXME: standardize error messages */
       +/* FIXME: it isn't entirely correct to give size_t as length for
       +   string in print_fmt (supposed to be int) */
       +
       +struct config {
       +        ledit_theme *theme;
       +        basic_key_array *basic_keys;
       +        command_key_array *command_keys;
       +        command_array *cmds;
       +        char **langs;
       +        size_t num_langs;
       +        size_t alloc_langs;
       +} config = {NULL};
       +
       +enum toktype {
       +        STRING,
       +        LBRACE,
       +        RBRACE,
       +        EQUALS,
       +        NEWLINE,
       +        ERROR,
       +        END
       +};
       +
       +static const char *
       +toktype_str(enum toktype type) {
       +        switch (type) {
       +        case STRING:
       +                return "string";
       +                break;
       +        case LBRACE:
       +                return "left brace";
       +                break;
       +        case RBRACE:
       +                return "right brace";
       +                break;
       +        case EQUALS:
       +                return "equals";
       +                break;
       +        case NEWLINE:
       +                return "newline";
       +                break;
       +        case ERROR:
       +                return "error";
       +                break;
       +        case END:
       +                return "end of file";
       +                break;
       +        default:
       +                return "unknown";
       +        }
       +}
       +
       +struct token {
       +        char *text;
       +        size_t len;
       +        enum toktype type;
       +        size_t line;        /* line in original input */
       +        size_t line_offset; /* offset from start of line */
       +};
       +
       +struct lexstate {
       +        char *text;
       +        size_t len;        /* length of text */
       +        size_t cur;        /* current byte position */
       +        size_t cur_line;   /* current line */
       +        size_t line_start; /* byte offset of start of current line */
       +};
       +
       +static struct token
       +next_token(struct lexstate *s) {
       +        char c;
       +        struct token tok;
       +        while (1) {
       +                if (s->cur >= s->len)
       +                        return (struct token){NULL, 0, END, s->cur_line, s->cur - s->line_start + 1};
       +                while (isspace(c = s->text[s->cur])) {
       +                        s->cur++;
       +                        if (c == '\n') {
       +                                struct token tok = (struct token){s->text + s->cur - 1, 1, NEWLINE, s->cur_line, s->cur - s->line_start};
       +                                s->cur_line++;
       +                                s->line_start = s->cur;
       +                                return tok;
       +                        }
       +                        if (s->cur >= s->len)
       +                                return (struct token){NULL, 0, END, s->cur_line, s->cur - s->line_start + 1};
       +                }
       +
       +                switch (s->text[s->cur]) {
       +                case '#':
       +                        s->cur++;
       +                        while (s->cur < s->len && s->text[s->cur] != '\n')
       +                                s->cur++;
       +                        continue;
       +                case '{':
       +                        tok = (struct token){s->text + s->cur, 1, LBRACE, s->cur_line, s->cur - s->line_start + 1};
       +                        s->cur++;
       +                        break;
       +                case '}':
       +                        tok = (struct token){s->text + s->cur, 1, RBRACE, s->cur_line, s->cur - s->line_start + 1};
       +                        s->cur++;
       +                        break;
       +                case '=':
       +                        tok = (struct token){s->text + s->cur, 1, EQUALS, s->cur_line, s->cur - s->line_start + 1};
       +                        s->cur++;
       +                        break;
       +                case '"':
       +                        /* FIXME: error if next char is not whitespace or end */
       +                        s->cur++;
       +                        tok = (struct token){s->text + s->cur, 0, STRING, s->cur_line, s->cur - s->line_start + 1};
       +                        size_t shift = 0, bs = 0;
       +                        int finished = 0;
       +                        while (s->cur < s->len) {
       +                                char c = s->text[s->cur];
       +                                if (c == '\n') {
       +                                        break;
       +                                } else if (c == '\\') {
       +                                        shift += bs;
       +                                        tok.len += bs;
       +                                        bs = (bs + 1) % 2;
       +                                } else if (c == '"') {
       +                                        if (bs) {
       +                                                shift++;
       +                                                tok.len++;
       +                                                bs = 0;
       +                                        } else {
       +                                                s->cur++;
       +                                                finished = 1;
       +                                                break;
       +                                        }
       +                                } else {
       +                                        tok.len++;
       +                                }
       +                                s->text[s->cur - shift] = s->text[s->cur];
       +                                s->cur++;
       +                        }
       +                        if (!finished) {
       +                                tok.text = "Unfinished string";
       +                                tok.len = strlen("Unfinished string");
       +                                tok.type = ERROR;
       +                        }
       +                        break;
       +                default:
       +                        tok = (struct token){s->text + s->cur, 1, STRING, s->cur_line, s->cur - s->line_start + 1};
       +                        s->cur++;
       +                        while (s->cur < s->len) {
       +                                char c = s->text[s->cur];
       +                                if (isspace(c) || c == '{' || c == '}' || c == '=') {
       +                                        break;
       +                                } else if (c == '"') {
       +                                        tok.text = "Unexpected start of string";
       +                                        tok.len = strlen("Unexpected start of string");
       +                                        tok.type = ERROR;
       +                                        tok.line_offset = s->cur - s->line_start + 1;
       +                                }
       +                                tok.len++;
       +                                s->cur++;
       +                        }
       +                }
       +                return tok;
       +        }
       +}
       +
       +typedef struct ast_obj ast_obj;
       +
       +typedef struct {
       +        ast_obj *objs;
       +        size_t len, cap;
       +} ast_list;
       +
       +typedef struct {
       +        struct token tok;
       +} ast_string;
       +
       +typedef struct {
       +        struct token tok;
       +        ast_obj *value;
       +} ast_assignment;
       +
       +typedef struct {
       +        struct token func_tok;
       +        struct token *args;
       +        size_t len, cap;
       +} ast_statement;
       +
       +enum objtype {
       +        OBJ_LIST,
       +        OBJ_STRING,
       +        OBJ_ASSIGNMENT,
       +        OBJ_STATEMENT
       +};
       +
       +struct ast_obj {
       +        struct token tok;
       +        union {
       +                ast_list list;
       +                ast_string str;
       +                ast_assignment assignment;
       +                ast_statement statement;
       +        } obj;
       +        enum objtype type;
       +};
       +
       +/* Note: These functions only free everything inside the object
       +   so they can be used with stack variables (or array elements)! */
       +
       +static void destroy_obj(ast_obj *obj);
       +
       +static void
       +destroy_list(ast_list *list) {
       +        if (!list)
       +                return;
       +        for (size_t i = 0; i < list->len; i++) {
       +                destroy_obj(&list->objs[i]);
       +        }
       +        free(list->objs);
       +        list->objs = NULL;
       +        list->len = list->cap = 0;
       +}
       +
       +static void
       +destroy_obj(ast_obj *obj) {
       +        if (!obj)
       +                return;
       +        switch (obj->type) {
       +                case OBJ_LIST:
       +                        destroy_list(&obj->obj.list);
       +                        break;
       +                case OBJ_ASSIGNMENT:
       +                        destroy_obj(obj->obj.assignment.value);
       +                        free(obj->obj.assignment.value);
       +                        obj->obj.assignment.value = NULL;
       +                        break;
       +                case OBJ_STATEMENT:
       +                        free(obj->obj.statement.args);
       +                        obj->obj.statement.args = NULL;
       +                        obj->obj.statement.len = obj->obj.statement.cap = 0;
       +                        break;
       +                default:
       +                        break;
       +        }
       +}
       +
       +/* FIXME: overflow */
       +static void
       +list_append(ast_list *list, ast_obj o) {
       +        list->cap = ideal_array_size(list->cap, add_sz(list->len, 1));
       +        list->objs = ledit_reallocarray(list->objs, list->cap, sizeof(ast_obj));
       +        list->objs[list->len++] = o;
       +}
       +
       +static void
       +statement_append(ast_statement *statement, struct token tok) {
       +        statement->cap = ideal_array_size(statement->cap, add_sz(statement->len, 1));
       +        statement->args = ledit_reallocarray(statement->args, statement->cap, sizeof(struct token));
       +        statement->args[statement->len++] = tok;
       +}
       +
       +/* FIXME: make this a bit nicer */
       +/* Note: A lot of the ugliness is because of the
       +   (failed) attempt to somewhat optimize everything */
       +
       +static int
       +parse_list(struct lexstate *s, ast_list *ret, int implicit_end, char *filename, char **errstr) {
       +        *ret = (ast_list){NULL, 0, 0};
       +        struct token tok = next_token(s);
       +        struct token tok2;
       +        while (1) {
       +                switch (tok.type) {
       +                case STRING:
       +                        tok2 = next_token(s);
       +                        if (tok2.type == STRING) {
       +                                ast_statement statement = {tok, NULL, 0, 0};
       +                                /* FIXME: maybe allow lists in statements? */
       +                                while (tok2.type == STRING) {
       +                                        statement_append(&statement, tok2);
       +                                        tok2 = next_token(s);
       +                                }
       +                                list_append(ret, (ast_obj){.tok = tok, .obj = {.statement = statement}, .type = OBJ_STATEMENT});
       +                                tok = tok2;
       +                        } else if (tok2.type == EQUALS) {
       +                                ast_assignment assignment = {tok, NULL};
       +                                assignment.value = ledit_malloc(sizeof(ast_obj));
       +                                tok2 = next_token(s);
       +                                assignment.value->tok = tok2;
       +                                struct token orig_tok = tok;
       +                                if (tok2.type == STRING) {
       +                                        assignment.value->obj.str = (ast_string){tok2};
       +                                        assignment.value->type = OBJ_STRING;
       +                                        tok = next_token(s);
       +                                        if (tok.type == STRING) {
       +                                                *errstr = print_fmt(
       +                                                    "%s: Invalid assignment at line %zu, offset %zu",
       +                                                    filename, tok.line, tok.line_offset
       +                                                );
       +                                                free(assignment.value);
       +                                                goto error;
       +                                        }
       +                                } else if (tok2.type == LBRACE) {
       +                                        assignment.value->type = OBJ_LIST;
       +                                        /* just in case */
       +                                        assignment.value->obj.list = (ast_list){NULL, 0, 0};
       +                                        if (parse_list(s, &assignment.value->obj.list, 0, filename, errstr)) {
       +                                                free(assignment.value);
       +                                                goto error;
       +                                        }
       +                                        tok = next_token(s);
       +                                        if (tok.type == STRING) {
       +                                                *errstr = print_fmt(
       +                                                    "%s: Invalid assignment at line %zu, offset %zu",
       +                                                    filename, tok.line, tok.line_offset
       +                                                );
       +                                                destroy_list(&assignment.value->obj.list);
       +                                                free(assignment.value);
       +                                                goto error;
       +                                        }
       +                                } else {
       +                                        *errstr = print_fmt(
       +                                            "%s: Invalid assignment at line %zu, offset %zu",
       +                                            filename, tok2.line, tok2.line_offset
       +                                        );
       +                                        free(assignment.value);
       +                                        goto error;
       +                                }
       +                                list_append(ret, (ast_obj){.tok = orig_tok, .obj = {.assignment = assignment}, .type = OBJ_ASSIGNMENT});
       +                        } else {
       +                                *errstr = print_fmt(
       +                                    "%s: Invalid token '%s' at line %zu, offset %zu",
       +                                    filename, toktype_str(tok2.type), tok2.line, tok2.line_offset
       +                                );
       +                                goto error;
       +                        }
       +                        break;
       +                case NEWLINE:
       +                        tok = next_token(s);
       +                        break;
       +                case RBRACE:
       +                        if (implicit_end) {
       +                                *errstr = print_fmt(
       +                                    "%s: Unexpected right brace at line %zu, offset %zu",
       +                                    filename, tok.line, tok.line_offset
       +                                );
       +                                goto error;
       +                        } else {
       +                                return 0;
       +                        }
       +                case END:
       +                        if (!implicit_end) {
       +                                *errstr = print_fmt(
       +                                    "%s: Unexpected end of file at line %zu, offset %zu",
       +                                    filename, tok.line, tok.line_offset
       +                                );
       +                                goto error;
       +                        } else {
       +                                return 0;
       +                        }
       +                case LBRACE:
       +                case EQUALS:
       +                case ERROR:
       +                default:
       +                        *errstr = print_fmt(
       +                            "%s: Unexpected token '%s' at line %zu, offset %zu",
       +                            filename, toktype_str(tok.type), tok.line, tok.line_offset
       +                        );
       +                        goto error;
       +                }
       +        }
       +        return 0;
       +error:
       +        destroy_list(ret);
       +        return 1;
       +}
       +
       +static char *
       +load_file(char *filename, size_t *len_ret, char **errstr) {
       +        long len;
       +        char *file_contents;
       +        FILE *file;
       +
       +        /* FIXME: https://wiki.sei.cmu.edu/confluence/display/c/FIO19-C.+Do+not+use+fseek()+and+ftell()+to+compute+the+size+of+a+regular+file */
       +        file = fopen(filename, "r");
       +        if (!file) goto error;
       +        if (fseek(file, 0, SEEK_END)) goto errorclose;
       +        len = ftell(file);
       +        if (len < 0) goto errorclose;
       +        if (fseek(file, 0, SEEK_SET)) goto errorclose;
       +        file_contents = ledit_malloc(add_sz((size_t)len, 1));
       +        clearerr(file);
       +        fread(file_contents, 1, (size_t)len, file);
       +        if (ferror(file)) goto errorclose;
       +        file_contents[len] = '\0';
       +        if (fclose(file)) goto error;
       +        *len_ret = (size_t)len;
       +        return file_contents;
       +error:
       +        if (errstr)
       +                *errstr = strerror(errno);
       +        return NULL;
       +errorclose:
       +        if (errstr)
       +                *errstr = strerror(errno);
       +        fclose(file);
       +        return NULL;
       +}
       +
       +/* FIXME: max recursion depth in parser */
       +
       +static int
       +parse_theme_color(
       +    ledit_common *common,
       +    void *obj, const char *val, size_t val_len, char *key,
       +    char *filename, size_t line, size_t line_offset, char **errstr) {
       +        XftColor *dst = (XftColor *)obj;
       +        char col[8]; /* 7 for '#' and 6 hex values + 1 for '\0' */
       +        if (val_len == 7 && val[0] == '#') {
       +                strncpy(col, val, val_len);
       +                col[val_len] = '\0';
       +        } else if (val_len == 6) {
       +                col[0] = '#';
       +                strncpy(col + 1, val, val_len);
       +                col[val_len + 1] = '\0';
       +        } else {
       +                goto error;
       +        }
       +        /* FIXME: XftColorAllocValue */
       +        if (!XftColorAllocName(common->dpy, common->vis, common->cm, col, dst))
       +                goto error;
       +        return 0;
       +error:
       +        *errstr = print_fmt(
       +            "%s: Unable to parse color specification "
       +            "'%.*s' for '%s' at line %zu, position %zu",
       +            filename, val_len, val, key, line, line_offset
       +        );
       +        return 1;
       +}
       +
       +static void
       +destroy_theme_color(ledit_common *common, void *obj) {
       +        XftColor *color = (XftColor *)obj;
       +        XftColorFree(common->dpy, common->vis, common->cm, color);
       +}
       +
       +/* based partially on OpenBSD's strtonum */
       +static int
       +parse_theme_number(
       +    ledit_common *common,
       +    void *obj, const char *val, size_t val_len, char *key,
       +    char *filename, size_t line, size_t line_offset, char **errstr) {
       +        (void)common;
       +        int *num = (int *)obj;
       +        /* the string needs to be nul-terminated
       +           if it contains more than 9 digits, it's illegal anyways */
       +        if (val_len > 9)
       +                goto error;
       +        char str[10];
       +        strncpy(str, val, val_len);
       +        str[val_len] = '\0';
       +        char *end;
       +        long l = strtol(str, &end, 10);
       +        if (str == end || *end != '\0' ||
       +            l < 0 || l > INT_MAX || ((l == LONG_MIN ||
       +            l == LONG_MAX) && errno == ERANGE)) {
       +                goto error;
       +        }
       +        *num = (int)l;
       +        return 0;
       +error:
       +        *errstr = print_fmt(
       +            "%s: Invalid number '%.*s' "
       +            "for '%s' at line %zu, position %zu",
       +            filename, val_len, val, key, line, line_offset
       +        );
       +        return 1;
       +}
       +
       +static void
       +destroy_theme_number(ledit_common *common, void *obj) {
       +        (void)common;
       +        (void)obj;
       +}
       +
       +static int
       +parse_theme_string(
       +    ledit_common *common,
       +    void *obj, const char *val, size_t val_len, char *key,
       +    char *filename, size_t line, size_t line_offset, char **errstr) {
       +        (void)common; (void)key;
       +        (void)filename; (void)line; (void)line_offset; (void)errstr;
       +
       +        char **obj_str = (char **)obj;
       +        *obj_str = ledit_strndup(val, val_len);
       +        return 0;
       +}
       +
       +static void
       +destroy_theme_string(ledit_common *common, void *obj) {
       +        (void)common;
       +        char **obj_str = (char **)obj;
       +        free(*obj_str);
       +}
       +
       +/* FIXME: This interface is absolutely horrible - it's mainly this way to reuse the
       +   theme array for the destroy function */
       +/* If theme is NULL, a new theme is loaded, else it is destroyed */
       +static ledit_theme *
       +load_destroy_theme(ledit_common *common, ast_list *theme_list, ledit_theme *theme, char *filename, char **errstr) {
       +        *errstr = NULL;
       +        int default_init = theme ? 1 : 0;
       +        if (!theme)
       +                theme = ledit_malloc(sizeof(ledit_theme));
       +
       +        struct {
       +                char *key;
       +                void *obj;
       +                int (*parse_func)(
       +                    ledit_common *common,
       +                    void *obj, const char *val, size_t val_len, char *key,
       +                    char *filename, size_t line, size_t line_offset, char **errstr
       +                );
       +                void (*destroy_func)(ledit_common *common, void *obj);
       +                const char *default_value;
       +                int initialized;
       +        } settings[] = {
       +                {"text-font", &theme->text_font, &parse_theme_string, &destroy_theme_string, TEXT_FONT, default_init},
       +                {"text-size", &theme->text_size, &parse_theme_number, &destroy_theme_number, TEXT_SIZE, default_init},
       +                {"scrollbar-width", &theme->scrollbar_width, &parse_theme_number, &destroy_theme_number, SCROLLBAR_WIDTH, default_init},
       +                {"scrollbar-step", &theme->scrollbar_step, &parse_theme_number, &destroy_theme_number, SCROLLBAR_STEP, default_init},
       +                {"text-fg", &theme->text_fg, &parse_theme_color, &destroy_theme_color, TEXT_FG, default_init},
       +                {"text-bg", &theme->text_bg, &parse_theme_color, &destroy_theme_color, TEXT_BG, default_init},
       +                {"cursor-fg", &theme->cursor_fg, &parse_theme_color, &destroy_theme_color, CURSOR_FG, default_init},
       +                {"cursor-bg", &theme->cursor_bg, &parse_theme_color, &destroy_theme_color, CURSOR_BG, default_init},
       +                {"selection-fg", &theme->selection_fg, &parse_theme_color, &destroy_theme_color, SELECTION_FG, default_init},
       +                {"selection-bg", &theme->selection_bg, &parse_theme_color, &destroy_theme_color, SELECTION_BG, default_init},
       +                {"bar-fg", &theme->bar_fg, &parse_theme_color, &destroy_theme_color, BAR_FG, default_init},
       +                {"bar-bg", &theme->bar_bg, &parse_theme_color, &destroy_theme_color, BAR_BG, default_init},
       +                {"bar-cursor", &theme->bar_cursor, &parse_theme_color, &destroy_theme_color, BAR_CURSOR, default_init},
       +                {"scrollbar-fg", &theme->scrollbar_fg, &parse_theme_color, &destroy_theme_color, SCROLLBAR_FG, default_init},
       +                {"scrollbar-bg", &theme->scrollbar_bg, &parse_theme_color, &destroy_theme_color, SCROLLBAR_BG, default_init},
       +        };
       +
       +        if (default_init)
       +                goto cleanup;
       +
       +        if (theme_list) {
       +                for (size_t i = 0; i < theme_list->len; i++) {
       +                        size_t line = theme_list->objs[i].tok.line;
       +                        size_t line_offset = theme_list->objs[i].tok.line_offset;
       +                        if (theme_list->objs[i].type != OBJ_ASSIGNMENT) {
       +                                *errstr = print_fmt(
       +                                    "%s: Invalid statement in theme configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto cleanup;
       +                        } else if (theme_list->objs[i].obj.assignment.value->type != OBJ_STRING) {
       +                                *errstr = print_fmt(
       +                                    "%s: Invalid assignment in theme configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto cleanup;
       +                        }
       +
       +                        char *key = theme_list->objs[i].obj.assignment.tok.text;
       +                        size_t key_len = theme_list->objs[i].obj.assignment.tok.len;
       +                        char *val = theme_list->objs[i].obj.assignment.value->obj.str.tok.text;
       +                        size_t val_len = theme_list->objs[i].obj.assignment.value->obj.str.tok.len;
       +
       +                        int found = 0;
       +                        /* FIXME: use binary search maybe */
       +                        for (size_t j = 0; j < LENGTH(settings); j++) {
       +                                if (str_array_equal(settings[j].key, key, key_len)) {
       +                                        /* FIXME: maybe just make this a warning? */
       +                                        if (settings[j].initialized) {
       +                                                *errstr = print_fmt(
       +                                                    "%s: Duplicate definition of "
       +                                                    "'%.*s' at line %zu, position %zu",
       +                                                    filename, key_len, key, line, line_offset
       +                                                );
       +                                                goto cleanup;
       +                                        }
       +                                        if (settings[j].parse_func(
       +                                            common, settings[j].obj, val, val_len,
       +                                            settings[j].key, filename, line, line_offset, errstr)) {
       +                                                goto cleanup;
       +                                        }
       +                                        settings[j].initialized = 1;
       +                                        found = 1;
       +                                        break;
       +                                }
       +                        }
       +                        if (!found) {
       +                                *errstr = print_fmt(
       +                                    "%s: Invalid theme setting "
       +                                    "'%.*s' at line %zu, position %zu",
       +                                    filename, key_len, key, line, line_offset
       +                                );
       +                                goto cleanup;
       +                        }
       +                }
       +        }
       +
       +        for (size_t i = 0; i < LENGTH(settings); i++) {
       +                if (!settings[i].initialized) {
       +                        /* FIXME: kind of inefficient to calculate strlen at runtime */
       +                        /* FIXME: line number doesn't make sense */
       +                        if (settings[i].parse_func(
       +                            common, settings[i].obj, settings[i].default_value,
       +                            strlen(settings[i].default_value), settings[i].key,
       +                            "default config", 0, 0, errstr)) {
       +                                goto cleanup;
       +                        }
       +                }
       +        }
       +
       +        return theme;
       +cleanup:
       +        for (size_t i = 0; i < LENGTH(settings); i++) {
       +                if (settings[i].initialized) {
       +                        settings[i].destroy_func(common, settings[i].obj);
       +                }
       +        }
       +        free(theme);
       +        return NULL;
       +}
       +
       +static ledit_theme *
       +load_theme(ledit_common *common, ast_list *theme_list, char *filename, char **errstr) {
       +        return load_destroy_theme(common, theme_list, NULL, filename, errstr);
       +}
       +
       +static void
       +destroy_theme(ledit_common *common, ledit_theme *theme) {
       +        char *errstr = NULL;
       +        if (!theme)
       +                return;
       +        (void)load_destroy_theme(common, NULL, theme, NULL, &errstr);
       +        /* shouldn't happen... */
       +        if (errstr)
       +                free(errstr);
       +}
       +
       +/* This only destroys the members inside 'cfg' since the config
       + * struct itself is usually not on the heap. */
       +static void
       +config_destroy(ledit_common *common, struct config *cfg) {
       +        if (cfg->theme)
       +                destroy_theme(common, cfg->theme);
       +        cfg->theme = NULL;
       +        for (size_t i = 0; i < cfg->num_langs; i++) {
       +                for (size_t j = 0; j < cfg->basic_keys[i].num_keys; j++) {
       +                        free(cfg->basic_keys[i].keys[j].text);
       +                }
       +                free(cfg->basic_keys[i].keys);
       +                for (size_t j = 0; j < cfg->command_keys[i].num_keys; j++) {
       +                        free(cfg->command_keys[i].keys[j].text);
       +                }
       +                free(cfg->command_keys[i].keys);
       +                for (size_t j = 0; j < cfg->cmds[i].num_cmds; j++) {
       +                        free(cfg->cmds[i].cmds[j].text);
       +                }
       +                free(cfg->cmds[i].cmds);
       +                free(cfg->langs[i]);
       +        }
       +        free(cfg->basic_keys);
       +        free(cfg->command_keys);
       +        free(cfg->cmds);
       +        free(cfg->langs);
       +        cfg->basic_keys = NULL;
       +        cfg->command_keys = NULL;
       +        cfg->cmds = NULL;
       +        cfg->langs = NULL;
       +        cfg->num_langs = cfg->alloc_langs = 0;
       +}
       +
       +void
       +config_cleanup(ledit_common *common) {
       +        config_destroy(common, &config);
       +}
       +
       +/* FIXME: which additional ones are needed here? */
       +static struct keysym_mapping {
       +        char *name;
       +        KeySym keysym;
       +} keysym_map[] = {
       +        {"backspace", XK_BackSpace},
       +        {"begin", XK_Begin},
       +        {"break", XK_Break},
       +        {"cancel", XK_Cancel},
       +        {"clear", XK_Clear},
       +        {"delete", XK_Delete},
       +        {"down", XK_Down},
       +        {"end", XK_End},
       +        {"escape", XK_Escape},
       +        {"execute", XK_Execute},
       +
       +        {"f1", XK_F1},
       +        {"f10", XK_F10},
       +        {"f11", XK_F11},
       +        {"f12", XK_F12},
       +        {"f13", XK_F13},
       +        {"f14", XK_F14},
       +        {"f15", XK_F15},
       +        {"f16", XK_F16},
       +        {"f17", XK_F17},
       +        {"f18", XK_F18},
       +        {"f19", XK_F19},
       +        {"f2", XK_F2},
       +        {"f20", XK_F20},
       +        {"f21", XK_F21},
       +        {"f22", XK_F22},
       +        {"f23", XK_F23},
       +        {"f24", XK_F24},
       +        {"f25", XK_F25},
       +        {"f26", XK_F26},
       +        {"f27", XK_F27},
       +        {"f28", XK_F28},
       +        {"f29", XK_F29},
       +        {"f3", XK_F3},
       +        {"f30", XK_F30},
       +        {"f31", XK_F31},
       +        {"f32", XK_F32},
       +        {"f33", XK_F33},
       +        {"f34", XK_F34},
       +        {"f35", XK_F35},
       +        {"f4", XK_F4},
       +        {"f5", XK_F5},
       +        {"f6", XK_F6},
       +        {"f7", XK_F7},
       +        {"f8", XK_F8},
       +        {"f9", XK_F9},
       +
       +        {"find", XK_Find},
       +        {"help", XK_Help},
       +        {"home", XK_Home},
       +        {"insert", XK_Insert},
       +
       +        {"kp-0", XK_KP_0},
       +        {"kp-1", XK_KP_1},
       +        {"kp-2", XK_KP_2},
       +        {"kp-3", XK_KP_3},
       +        {"kp-4", XK_KP_4},
       +        {"kp-5", XK_KP_5},
       +        {"kp-6", XK_KP_6},
       +        {"kp-7", XK_KP_7},
       +        {"kp-8", XK_KP_8},
       +        {"kp-9", XK_KP_9},
       +        {"kp-add", XK_KP_Add},
       +        {"kp-begin", XK_KP_Begin},
       +        {"kp-decimal", XK_KP_Decimal},
       +        {"kp-delete", XK_KP_Delete},
       +        {"kp-divide", XK_KP_Divide},
       +        {"kp-down", XK_KP_Down},
       +        {"kp-end", XK_KP_End},
       +        {"kp-enter", XK_KP_Enter},
       +        {"kp-equal", XK_KP_Equal},
       +        {"kp-f1", XK_KP_F1},
       +        {"kp-f2", XK_KP_F2},
       +        {"kp-f3", XK_KP_F3},
       +        {"kp-f4", XK_KP_F4},
       +        {"kp-home", XK_KP_Home},
       +        {"kp-insert", XK_KP_Insert},
       +        {"kp-left", XK_KP_Left},
       +        {"kp-multiply", XK_KP_Multiply},
       +        {"kp-next", XK_KP_Next},
       +        {"kp-page-down", XK_KP_Page_Down},
       +        {"kp-page-up", XK_KP_Page_Up},
       +        {"kp-prior", XK_KP_Prior},
       +        {"kp-right", XK_KP_Right},
       +        {"kp-separator", XK_KP_Separator},
       +        {"kp-space", XK_KP_Space},
       +        {"kp-subtract", XK_KP_Subtract},
       +        {"kp-tab", XK_KP_Tab},
       +        {"kp-up", XK_KP_Up},
       +
       +        {"l1", XK_L1},
       +        {"l10", XK_L10},
       +        {"l2", XK_L2},
       +        {"l3", XK_L3},
       +        {"l4", XK_L4},
       +        {"l5", XK_L5},
       +        {"l6", XK_L6},
       +        {"l7", XK_L7},
       +        {"l8", XK_L8},
       +        {"l9", XK_L9},
       +
       +        {"left", XK_Left},
       +        {"linefeed", XK_Linefeed},
       +        {"menu", XK_Menu},
       +        {"mode-switch", XK_Mode_switch},
       +        {"next", XK_Next},
       +        {"num-lock", XK_Num_Lock},
       +        {"page-down", XK_Page_Down},
       +        {"page-up", XK_Page_Up},
       +        {"pause", XK_Pause},
       +        {"print", XK_Print},
       +        {"prior", XK_Prior},
       +
       +        {"r1", XK_R1},
       +        {"r10", XK_R10},
       +        {"r11", XK_R11},
       +        {"r12", XK_R12},
       +        {"r13", XK_R13},
       +        {"r14", XK_R14},
       +        {"r15", XK_R15},
       +        {"r2", XK_R2},
       +        {"r3", XK_R3},
       +        {"r4", XK_R4},
       +        {"r5", XK_R5},
       +        {"r6", XK_R6},
       +        {"r7", XK_R7},
       +        {"r8", XK_R8},
       +        {"r9", XK_R9},
       +
       +        {"redo", XK_Redo},
       +        {"return", XK_Return},
       +        {"right", XK_Right},
       +        {"script-switch", XK_script_switch},
       +        {"scroll-lock", XK_Scroll_Lock},
       +        {"select", XK_Select},
       +        {"space", XK_space},
       +        {"sysreq", XK_Sys_Req},
       +        {"tab", XK_Tab},
       +        {"up", XK_Up},
       +        {"undo", XK_Undo},
       +};
       +
       +GEN_CB_MAP_HELPERS(keysym_map, struct keysym_mapping, name)
       +
       +static int
       +parse_keysym(char *keysym_str, size_t len, KeySym *sym) {
       +        struct keysym_mapping *km = keysym_map_get_entry(keysym_str, len);
       +        if (!km)
       +                return 1;
       +        *sym = km->keysym;
       +        return 0;
       +}
       +
       +static int
       +parse_modemask(char *modemask_str, size_t len, ledit_mode *mode_ret) {
       +        size_t cur = 0;
       +        *mode_ret = 0;
       +        while (cur < len) {
       +                if (str_array_equal("normal", modemask_str + cur, LEDIT_MIN(6, len - cur))) {
       +                        cur += 6;
       +                        *mode_ret |= NORMAL;
       +                } else if (str_array_equal("visual", modemask_str + cur, LEDIT_MIN(6, len - cur))) {
       +                        cur += 6;
       +                        *mode_ret |= VISUAL;
       +                } else if (str_array_equal("insert", modemask_str + cur, LEDIT_MIN(6, len - cur))) {
       +                        cur += 6;
       +                        *mode_ret |= INSERT;
       +                } else {
       +                        return 1;
       +                }
       +                if (cur < len && modemask_str[cur] != '|')
       +                        return 1;
       +                else
       +                        cur++;
       +        }
       +        return 0;
       +}
       +
       +static int
       +parse_modmask(char *modmask_str, size_t len, unsigned int *mask_ret) {
       +        size_t cur = 0;
       +        *mask_ret = 0;
       +        while (cur < len) {
       +                if (str_array_equal("shift", modmask_str + cur, LEDIT_MIN(5, len - cur))) {
       +                        cur += 5;
       +                        *mask_ret |= ShiftMask;
       +                } else if (str_array_equal("lock", modmask_str + cur, LEDIT_MIN(4, len - cur))) {
       +                        cur += 4;
       +                        *mask_ret |= LockMask;
       +                } else if (str_array_equal("control", modmask_str + cur, LEDIT_MIN(7, len - cur))) {
       +                        cur += 7;
       +                        *mask_ret |= ControlMask;
       +                } else if (str_array_equal("mod1", modmask_str + cur, LEDIT_MIN(4, len - cur))) {
       +                        cur += 4;
       +                        *mask_ret |= Mod1Mask;
       +                } else if (str_array_equal("mod2", modmask_str + cur, LEDIT_MIN(4, len - cur))) {
       +                        cur += 4;
       +                        *mask_ret |= Mod2Mask;
       +                } else if (str_array_equal("mod3", modmask_str + cur, LEDIT_MIN(4, len - cur))) {
       +                        cur += 4;
       +                        *mask_ret |= Mod3Mask;
       +                } else if (str_array_equal("mod4", modmask_str + cur, LEDIT_MIN(4, len - cur))) {
       +                        cur += 4;
       +                        *mask_ret |= Mod4Mask;
       +                } else if (str_array_equal("any", modmask_str + cur, LEDIT_MIN(3, len - cur))) {
       +                        cur += 3;
       +                        *mask_ret = UINT_MAX;
       +                } else {
       +                        return 1;
       +                }
       +                if (cur < len && modmask_str[cur] != '|')
       +                        return 1;
       +                else
       +                        cur++;
       +        }
       +        return 0;
       +}
       +
       +/* FIXME: it would probably be safer to not write the string lengths by hand... */
       +static int
       +parse_command_modemask(char *mode_str, size_t len, command_mode *mode_ret) {
       +        size_t cur = 0;
       +        *mode_ret = 0;
       +        /* IMPORTANT: these need to be sorted appropriately so e.g. edit doesn't mess with edit-search */
       +        while (cur < len) {
       +                if (str_array_equal("substitute", mode_str + cur, LEDIT_MIN(10, len - cur))) {
       +                        cur += 10;
       +                        *mode_ret |= CMD_SUBSTITUTE;
       +                } else if (str_array_equal("edit-search-backwards", mode_str + cur, LEDIT_MIN(21, len - cur))) {
       +                        cur += 21;
       +                        *mode_ret |= CMD_EDITSEARCHB;
       +                } else if (str_array_equal("edit-search", mode_str + cur, LEDIT_MIN(11, len - cur))) {
       +                        cur += 11;
       +                        *mode_ret |= CMD_EDITSEARCH;
       +                } else if (str_array_equal("edit", mode_str + cur, LEDIT_MIN(4, len - cur))) {
       +                        cur += 4;
       +                        *mode_ret |= CMD_EDIT;
       +                } else {
       +                        return 1;
       +                }
       +                if (cur < len && mode_str[cur] != '|') {
       +                        return 1;
       +                } else {
       +                        cur++;
       +                }
       +        }
       +        return 0;
       +}
       +
       +/* FIXME: generic dynamic array */
       +
       +static void
       +push_lang(struct config *cfg) {
       +        if (cfg->num_langs == cfg->alloc_langs) {
       +                cfg->alloc_langs = ideal_array_size(cfg->alloc_langs, add_sz(cfg->num_langs, 1));
       +                cfg->basic_keys = ledit_reallocarray(cfg->basic_keys, cfg->alloc_langs, sizeof(basic_key_array));
       +                cfg->command_keys = ledit_reallocarray(cfg->command_keys, cfg->alloc_langs, sizeof(command_key_array));
       +                cfg->cmds = ledit_reallocarray(cfg->cmds, cfg->alloc_langs, sizeof(command_array));
       +                cfg->langs = ledit_reallocarray(cfg->langs, cfg->alloc_langs, sizeof(char *));
       +        }
       +        basic_key_array *arr1 = &cfg->basic_keys[cfg->num_langs];
       +        arr1->keys = NULL;
       +        arr1->num_keys = arr1->alloc_keys = 0;
       +        command_key_array *arr2 = &cfg->command_keys[cfg->num_langs];
       +        arr2->keys = NULL;
       +        arr2->num_keys = arr2->alloc_keys = 0;
       +        command_array *arr3 = &cfg->cmds[cfg->num_langs];
       +        arr3->cmds = NULL;
       +        arr3->num_cmds = arr3->alloc_cmds = 0;
       +        cfg->langs[cfg->num_langs] = NULL;
       +        cfg->num_langs++;
       +}
       +
       +#define GEN_PARSE_STATEMENT(name, cb_type, mapping_type, mode_parse_func)                             \
       +static int                                                                                            \
       +name(ast_statement *st, mapping_type *m, char *filename, char **errstr) {                             \
       +        size_t line = st->func_tok.line;                                                              \
       +        size_t line_offset = st->func_tok.line_offset;                                                \
       +        m->cb = NULL;                                                                                 \
       +        m->text = NULL;                                                                               \
       +        m->mods = 0;                                                                                  \
       +        m->modes = 0;                                                                                 \
       +        m->keysym = 0;                                                                                \
       +        char *msg = NULL;                                                                             \
       +        if (!str_array_equal("bind", st->func_tok.text, st->func_tok.len) || st->len < 1) {           \
       +                msg = "Invalid statement";                                                            \
       +                goto error;                                                                           \
       +        }                                                                                             \
       +        m->cb = cb_type##_map_get_entry(st->args[0].text, st->args[0].len);                           \
       +        if (!m->cb) {                                                                                 \
       +                msg = "Invalid function specification";                                               \
       +                goto error;                                                                           \
       +        }                                                                                             \
       +        int text_init = 0, keysym_init = 0, modes_init = 0, mods_init = 0;                            \
       +        for (size_t i = 1; i < st->len; i++) {                                                        \
       +                line = st->args[i].line;                                                              \
       +                line_offset = st->args[i].line_offset;                                                \
       +                if (str_array_equal("mods", st->args[i].text, st->args[i].len)) {                     \
       +                        if (mods_init) {                                                              \
       +                                msg = "Duplicate mods specification";                                 \
       +                                goto error;                                                           \
       +                        } else if (i == st->len - 1) {                                                \
       +                                msg = "Unfinished statement";                                         \
       +                                goto error;                                                           \
       +                        }                                                                             \
       +                        i++;                                                                          \
       +                        if (parse_modmask(st->args[i].text, st->args[i].len, &m->mods)) {             \
       +                                msg = "Invalid mods specification";                                   \
       +                                goto error;                                                           \
       +                        }                                                                             \
       +                        mods_init = 1;                                                                \
       +                } else if (str_array_equal("modes", st->args[i].text, st->args[i].len)) {             \
       +                        if (modes_init) {                                                             \
       +                                msg = "Duplicate modes specification";                                \
       +                                goto error;                                                           \
       +                        } else if (i == st->len - 1) {                                                \
       +                                msg = "Unfinished statement";                                         \
       +                                goto error;                                                           \
       +                        }                                                                             \
       +                        i++;                                                                          \
       +                        if (mode_parse_func(st->args[i].text, st->args[i].len, &m->modes)) {          \
       +                                msg = "Invalid modes specification";                                  \
       +                                goto error;                                                           \
       +                        } else if (!cb_type##_modemask_is_valid(m->cb, m->modes)) {                   \
       +                                msg = "Function not defined for all given modes";                     \
       +                                goto error;                                                           \
       +                        }                                                                             \
       +                        modes_init = 1;                                                               \
       +                } else if (str_array_equal("keysym", st->args[i].text, st->args[i].len)) {            \
       +                        if (text_init) {                                                              \
       +                                msg = "Text already specified";                                       \
       +                                goto error;                                                           \
       +                        } else if (keysym_init) {                                                     \
       +                                msg = "Duplicate keysym specification";                               \
       +                                goto error;                                                           \
       +                        } else if (i == st->len - 1) {                                                \
       +                                msg = "Unfinished statement";                                         \
       +                                goto error;                                                           \
       +                        }                                                                             \
       +                        i++;                                                                          \
       +                        if (parse_keysym(st->args[i].text, st->args[i].len, &m->keysym)) {            \
       +                                msg = "Invalid keysym specification";                                 \
       +                                goto error;                                                           \
       +                        }                                                                             \
       +                        keysym_init = 1;                                                              \
       +                } else if (str_array_equal("text", st->args[i].text, st->args[i].len)) {              \
       +                        if (keysym_init) {                                                            \
       +                                msg = "Keysym already specified";                                     \
       +                                goto error;                                                           \
       +                        } else if (text_init) {                                                       \
       +                                msg = "Duplicate text specification";                                 \
       +                                goto error;                                                           \
       +                        } else if (i == st->len - 1) {                                                \
       +                                msg = "Unfinished statement";                                         \
       +                                goto error;                                                           \
       +                        }                                                                             \
       +                        i++;                                                                          \
       +                        m->text = ledit_strndup(st->args[i].text, st->args[i].len);                   \
       +                        text_init = 1;                                                                \
       +                } else if (str_array_equal("catchall", st->args[i].text, st->args[i].len)) {          \
       +                        if (keysym_init) {                                                            \
       +                                msg = "Keysym already specified";                                     \
       +                                goto error;                                                           \
       +                        } else if (text_init) {                                                       \
       +                                msg = "Duplicate text specification";                                 \
       +                                goto error;                                                           \
       +                        }                                                                             \
       +                        m->text = ledit_strdup("");                                                   \
       +                        text_init = 1;                                                                \
       +                } else {                                                                              \
       +                        msg = "Invalid statement";                                                    \
       +                        goto error;                                                                   \
       +                }                                                                                     \
       +        }                                                                                             \
       +        if (!text_init && !keysym_init) {                                                             \
       +                msg = "No text or keysym specified";                                                  \
       +                goto error;                                                                           \
       +        }                                                                                             \
       +        if (!modes_init) {                                                                            \
       +                msg = "No modes specified";                                                           \
       +                goto error;                                                                           \
       +        }                                                                                             \
       +        return 0;                                                                                     \
       +error:                                                                                                \
       +        if (msg) {                                                                                    \
       +                *errstr = print_fmt(                                                                  \
       +                    "%s, line %zu, offset %zu: %s", filename, line, line_offset, msg                  \
       +                 );                                                                                   \
       +        }                                                                                             \
       +        if (m->text)                                                                                  \
       +                free(m->text);                                                                        \
       +        return 1;                                                                                     \
       +}
       +
       +GEN_PARSE_STATEMENT(parse_basic_key_statement, basic_key_cb, basic_key_mapping, parse_modemask)
       +GEN_PARSE_STATEMENT(parse_command_key_statement, command_key_cb, command_key_mapping, parse_command_modemask)
       +
       +static int
       +parse_command_statement(ast_statement *st, command_mapping *m, char *filename, char **errstr) {
       +        size_t line = st->func_tok.line;
       +        size_t line_offset = st->func_tok.line_offset;
       +        m->cb = NULL;
       +        m->text = NULL;
       +        char *msg = NULL;
       +        if (!str_array_equal("bind", st->func_tok.text, st->func_tok.len) || st->len != 2) {
       +                msg = "Invalid statement";
       +                goto error;
       +        }
       +        m->cb = command_cb_map_get_entry(st->args[0].text, st->args[0].len);
       +        if (!m->cb) {
       +                msg = "Invalid function specification";
       +                goto error;
       +        }
       +        m->text = ledit_strndup(st->args[1].text, st->args[1].len);
       +        return 0;
       +error:
       +        if (msg) {
       +                *errstr = print_fmt(
       +                    "%s, line %zu, offset %zu: %s", filename, line, line_offset, msg
       +                );
       +        }
       +        /* I guess this is unnecessary */
       +        if (m->text)
       +                free(m->text);
       +        return 1;
       +}
       +
       +static void
       +push_basic_key_mapping(basic_key_array *arr, basic_key_mapping m) {
       +        if (arr->num_keys == arr->alloc_keys) {
       +                arr->alloc_keys = ideal_array_size(arr->alloc_keys, add_sz(arr->num_keys, 1));
       +                arr->keys = ledit_reallocarray(arr->keys, arr->alloc_keys, sizeof(basic_key_mapping));
       +        }
       +        arr->keys[arr->num_keys] = m;
       +        arr->num_keys++;
       +}
       +
       +static void
       +push_command_key_mapping(command_key_array *arr, command_key_mapping m) {
       +        if (arr->num_keys == arr->alloc_keys) {
       +                arr->alloc_keys = ideal_array_size(arr->alloc_keys, add_sz(arr->num_keys, 1));
       +                arr->keys = ledit_reallocarray(arr->keys, arr->alloc_keys, sizeof(command_key_mapping));
       +        }
       +        arr->keys[arr->num_keys] = m;
       +        arr->num_keys++;
       +}
       +
       +static void
       +push_command_mapping(command_array *arr, command_mapping m) {
       +        if (arr->num_cmds == arr->alloc_cmds) {
       +                arr->alloc_cmds = ideal_array_size(arr->alloc_cmds, add_sz(arr->num_cmds, 1));
       +                arr->cmds = ledit_reallocarray(arr->cmds, arr->alloc_cmds, sizeof(command_mapping));
       +        }
       +        arr->cmds[arr->num_cmds] = m;
       +        arr->num_cmds++;
       +}
       +
       +/* FIXME: This could be made a lot nicer and less repetitive */
       +static int
       +load_bindings(struct config *cfg, ast_list *list, char *filename, char **errstr) {
       +        int basic_keys_init = 0, command_keys_init = 0, commands_init = 0;
       +        size_t cur_lang = cfg->num_langs - 1; /* FIXME: ensure no underflow */
       +        for (size_t i = 0; i < list->len; i++) {
       +                size_t line = list->objs[i].tok.line;
       +                size_t line_offset = list->objs[i].tok.line_offset;
       +                if (list->objs[i].type != OBJ_ASSIGNMENT) {
       +                        *errstr = print_fmt(
       +                             "%s: Invalid statement in bindings configuration "
       +                             "at list %zu, offset %zu", filename, line, line_offset
       +                         );
       +                         goto error;
       +                }
       +                char *key = list->objs[i].obj.assignment.tok.text;
       +                size_t key_len = list->objs[i].obj.assignment.tok.len;
       +                if (str_array_equal("language", key, key_len)) {
       +                        if (list->objs[i].obj.assignment.value->type != OBJ_STRING) {
       +                                *errstr = print_fmt(
       +                                    "%s: Invalid language setting in bindings configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto error;
       +                        } else if (cfg->langs[cur_lang]) {
       +                                *errstr = print_fmt(
       +                                    "%s: Duplicate language setting in bindings configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto error;
       +                        }
       +                        char *val = list->objs[i].obj.assignment.value->obj.str.tok.text;
       +                        size_t val_len = list->objs[i].obj.assignment.value->obj.str.tok.len;
       +                        cfg->langs[cur_lang] = ledit_strndup(val, val_len);
       +                } else if (str_array_equal("basic-keys", key, key_len)) {
       +                        if (list->objs[i].obj.assignment.value->type != OBJ_LIST) {
       +                                *errstr = print_fmt(
       +                                    "%s: Invalid basic-keys setting in bindings configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto error;
       +                        } else if (basic_keys_init) {
       +                                *errstr = print_fmt(
       +                                    "%s: Duplicate basic-keys setting in bindings configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto error;
       +                        }
       +                        ast_list *slist = &list->objs[i].obj.assignment.value->obj.list;
       +                        for (size_t j = 0; j < slist->len; j++) {
       +                                line = slist->objs[j].tok.line;
       +                                line_offset = slist->objs[j].tok.line_offset;
       +                                if (slist->objs[j].type != OBJ_STATEMENT) {
       +                                        *errstr = print_fmt(
       +                                            "%s: Invalid basic-keys setting in bindings configuration "
       +                                            "at line %zu, offset %zu", filename, line, line_offset
       +                                        );
       +                                        goto error;
       +                                }
       +                                basic_key_mapping m;
       +                                if (parse_basic_key_statement(&slist->objs[j].obj.statement, &m, filename, errstr))
       +                                        goto error;
       +                                push_basic_key_mapping(&cfg->basic_keys[0], m);
       +                        }
       +                        basic_keys_init = 1;
       +                } else if (str_array_equal("command-keys", key, key_len)) {
       +                        if (list->objs[i].obj.assignment.value->type != OBJ_LIST) {
       +                                *errstr = print_fmt(
       +                                    "%s: Invalid command-keys setting in bindings configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto error;
       +                        } else if (command_keys_init) {
       +                                *errstr = print_fmt(
       +                                    "%s: Duplicate command-keys setting in bindings configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto error;
       +                        }
       +                        ast_list *slist = &list->objs[i].obj.assignment.value->obj.list;
       +                        for (size_t j = 0; j < slist->len; j++) {
       +                                line = slist->objs[j].tok.line;
       +                                line_offset = slist->objs[j].tok.line_offset;
       +                                if (slist->objs[j].type != OBJ_STATEMENT) {
       +                                        *errstr = print_fmt(
       +                                            "%s: Invalid command-keys setting in bindings configuration "
       +                                            "at line %zu, offset %zu", filename, line, line_offset
       +                                        );
       +                                        goto error;
       +                                }
       +                                command_key_mapping m;
       +                                if (parse_command_key_statement(&slist->objs[j].obj.statement, &m, filename, errstr))
       +                                        goto error;
       +                                push_command_key_mapping(&cfg->command_keys[0], m);
       +                        }
       +                        command_keys_init = 1;
       +                } else if (str_array_equal("commands", key, key_len)) {
       +                        if (list->objs[i].obj.assignment.value->type != OBJ_LIST) {
       +                                *errstr = print_fmt(
       +                                    "%s: Invalid commands setting in bindings configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto error;
       +                        } else if (commands_init) {
       +                                *errstr = print_fmt(
       +                                    "%s: Duplicate commands setting in bindings configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto error;
       +                        }
       +                        ast_list *slist = &list->objs[i].obj.assignment.value->obj.list;
       +                        for (size_t j = 0; j < slist->len; j++) {
       +                                line = slist->objs[j].tok.line;
       +                                line_offset = slist->objs[j].tok.line_offset;
       +                                if (slist->objs[j].type != OBJ_STATEMENT) {
       +                                        *errstr = print_fmt(
       +                                            "%s: Invalid commands setting in bindings configuration "
       +                                            "at line %zu, offset %zu", filename, line, line_offset
       +                                        );
       +                                        goto error;
       +                                }
       +                                command_mapping m;
       +                                if (parse_command_statement(&slist->objs[j].obj.statement, &m, filename, errstr))
       +                                        goto error;
       +                                push_command_mapping(&cfg->cmds[0], m);
       +                        }
       +                        commands_init = 1;
       +                }
       +        }
       +
       +        /* FIXME: the behavior here is a bit weird - if there is nothing other than a language
       +           setting in the bindings configuration, all actual bindings are default, but the
       +           associated language is different */
       +        if (!cfg->langs[cur_lang]) {
       +                cfg->langs[cur_lang] = ledit_strdup(language_default);
       +        }
       +        /* FIXME: avoid calling strlen */
       +        if (!basic_keys_init) {
       +                ledit_debug("No basic keys configured in bindings; loading defaults\n");
       +                basic_key_mapping m;
       +                for (size_t i = 0; i < LENGTH(basic_keys_default); i++) {
       +                        m.cb = basic_key_cb_map_get_entry(basic_keys_default[i].func_name, strlen(basic_keys_default[i].func_name));
       +                        if (!m.cb) {
       +                                *errstr = print_fmt("default config: Invalid basic key function name '%s'", basic_keys_default[i].func_name);
       +                                goto error;
       +                        } else if (!basic_key_cb_modemask_is_valid(m.cb, basic_keys_default[i].modes)) {
       +                                *errstr = print_fmt("default config: Function '%s' not defined for all given modes", basic_keys_default[i].func_name);
       +                                goto error;
       +                        }
       +                        m.text = basic_keys_default[i].text ? ledit_strdup(basic_keys_default[i].text) : NULL;
       +                        m.mods = basic_keys_default[i].mods;
       +                        m.modes = basic_keys_default[i].modes;
       +                        m.keysym = basic_keys_default[i].keysym;
       +                        push_basic_key_mapping(&cfg->basic_keys[0], m);
       +                }
       +        }
       +        if (!command_keys_init) {
       +                ledit_debug("No command keys configured in bindings; loading defaults\n");
       +                command_key_mapping m;
       +                for (size_t i = 0; i < LENGTH(command_keys_default); i++) {
       +                        m.cb = command_key_cb_map_get_entry(command_keys_default[i].func_name, strlen(command_keys_default[i].func_name));
       +                        if (!m.cb) {
       +                                *errstr = print_fmt("default config: Invalid command key function name '%s'", command_keys_default[i].func_name);
       +                                goto error;
       +                        } else if (!command_key_cb_modemask_is_valid(m.cb, command_keys_default[i].modes)) {
       +                                *errstr = print_fmt("default config: Function '%s' not defined for all given modes", command_keys_default[i].func_name);
       +                                goto error;
       +                        }
       +                        m.text = command_keys_default[i].text ? ledit_strdup(command_keys_default[i].text) : NULL;
       +                        m.mods = command_keys_default[i].mods;
       +                        m.modes = command_keys_default[i].modes;
       +                        m.keysym = command_keys_default[i].keysym;
       +                        push_command_key_mapping(&cfg->command_keys[0], m);
       +                }
       +        }
       +        /* FIXME: guard against NULL text in default config! */
       +        if (!commands_init) {
       +                ledit_debug("No commands configured in bindings; loading defaults\n");
       +                command_mapping m;
       +                for (size_t i = 0; i < LENGTH(commands_default); i++) {
       +                        m.cb = command_cb_map_get_entry(commands_default[i].func_name, strlen(commands_default[i].func_name));
       +                        if (!m.cb) {
       +                                *errstr = print_fmt("default config: Invalid command function name '%s'", commands_default[i].func_name);
       +                                goto error;
       +                        }
       +                        m.text = ledit_strdup(commands_default[i].text);
       +                        push_command_mapping(&cfg->cmds[0], m);
       +                }
       +        }
       +        return 0;
       +/* FIXME: simplify error handling by doing more here */
       +error:
       +        return 1;
       +}
       +
       +static int
       +load_mapping(struct config *cfg, ast_list *list, char *filename, char **errstr) {
       +        int key_mapping_init = 0, command_mapping_init = 0;
       +        size_t cur_lang = cfg->num_langs - 1; /* FIXME: ensure no underflow */
       +        for (size_t i = 0; i < list->len; i++) {
       +                size_t line = list->objs[i].tok.line;
       +                size_t line_offset = list->objs[i].tok.line_offset;
       +                if (list->objs[i].type != OBJ_ASSIGNMENT) {
       +                        *errstr = print_fmt(
       +                             "%s: Invalid statement in language mapping configuration "
       +                             "at list %zu, offset %zu", filename, line, line_offset
       +                         );
       +                         goto error;
       +                }
       +                char *key = list->objs[i].obj.assignment.tok.text;
       +                size_t key_len = list->objs[i].obj.assignment.tok.len;
       +                basic_key_array *bkmap = &cfg->basic_keys[cur_lang];
       +                command_key_array *ckmap = &cfg->command_keys[cur_lang];
       +                command_array *cmap = &cfg->cmds[cur_lang];
       +                if (str_array_equal("language", key, key_len)) {
       +                        if (list->objs[i].obj.assignment.value->type != OBJ_STRING) {
       +                                *errstr = print_fmt(
       +                                    "%s: Invalid language setting in language mapping configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto error;
       +                        } else if (cfg->langs[cur_lang]) {
       +                                *errstr = print_fmt(
       +                                    "%s: Duplicate language setting in language mapping configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto error;
       +                        }
       +                        char *val = list->objs[i].obj.assignment.value->obj.str.tok.text;
       +                        size_t val_len = list->objs[i].obj.assignment.value->obj.str.tok.len;
       +                        cfg->langs[cur_lang] = ledit_strndup(val, val_len);
       +                } else if (str_array_equal("key-mapping", key, key_len)) {
       +                        if (list->objs[i].obj.assignment.value->type != OBJ_LIST) {
       +                                *errstr = print_fmt(
       +                                    "%s: Invalid key-mapping setting in language mapping configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto error;
       +                        } else if (key_mapping_init) {
       +                                *errstr = print_fmt(
       +                                    "%s: Duplicate key-mapping setting in language mapping configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto error;
       +                        }
       +                        ast_list *slist = &list->objs[i].obj.assignment.value->obj.list;
       +                        for (size_t j = 0; j < slist->len; j++) {
       +                                line = slist->objs[j].tok.line;
       +                                line_offset = slist->objs[j].tok.line_offset;
       +                                if (slist->objs[j].type != OBJ_STATEMENT) {
       +                                        *errstr = print_fmt(
       +                                            "%s: Invalid key-mapping setting in language mapping configuration "
       +                                            "at line %zu, offset %zu", filename, line, line_offset
       +                                        );
       +                                        goto error;
       +                                }
       +                                ast_statement *st = &slist->objs[j].obj.statement;
       +                                if (!str_array_equal("map", st->func_tok.text, st->func_tok.len) || st->len != 2) {
       +                                        *errstr = print_fmt(
       +                                            "%s: Invalid key-mapping statement in language mapping configuration "
       +                                            "at line %zu, offset %zu", filename, line, line_offset
       +                                        );
       +                                        goto error;
       +                                }
       +                                /* FIXME: any way to speed this up? I guess once the keys can be binary-searched... */
       +                                for (size_t k = 0; k < bkmap->num_keys; k++) {
       +                                        if (bkmap->keys[k].text && str_array_equal(bkmap->keys[k].text, st->args[1].text, st->args[1].len)) {
       +                                                free(bkmap->keys[k].text);
       +                                                bkmap->keys[k].text = ledit_strndup(st->args[0].text, st->args[0].len);
       +                                        }
       +                                }
       +                                for (size_t k = 0; k < ckmap->num_keys; k++) {
       +                                        if (ckmap->keys[k].text && str_array_equal(ckmap->keys[k].text, st->args[1].text, st->args[1].len)) {
       +                                                free(ckmap->keys[k].text);
       +                                                ckmap->keys[k].text = ledit_strndup(st->args[0].text, st->args[0].len);
       +                                        }
       +                                }
       +                        }
       +                        key_mapping_init = 1;
       +                } else if (str_array_equal("command-mapping", key, key_len)) {
       +                        if (list->objs[i].obj.assignment.value->type != OBJ_LIST) {
       +                                *errstr = print_fmt(
       +                                    "%s: Invalid command-mapping setting in language mapping configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto error;
       +                        } else if (command_mapping_init) {
       +                                *errstr = print_fmt(
       +                                    "%s: Duplicate command-mapping setting in language mapping configuration "
       +                                    "at line %zu, offset %zu", filename, line, line_offset
       +                                );
       +                                goto error;
       +                        }
       +                        ast_list *slist = &list->objs[i].obj.assignment.value->obj.list;
       +                        for (size_t j = 0; j < slist->len; j++) {
       +                                line = slist->objs[j].tok.line;
       +                                line_offset = slist->objs[j].tok.line_offset;
       +                                if (slist->objs[j].type != OBJ_STATEMENT) {
       +                                        *errstr = print_fmt(
       +                                            "%s: Invalid command-mapping setting in language mapping configuration "
       +                                            "at line %zu, offset %zu", filename, line, line_offset
       +                                        );
       +                                        goto error;
       +                                }
       +                                ast_statement *st = &slist->objs[j].obj.statement;
       +                                if (!str_array_equal("map", st->func_tok.text, st->func_tok.len) || st->len != 2) {
       +                                        *errstr = print_fmt(
       +                                            "%s: Invalid command-mapping statement in language mapping configuration "
       +                                            "at line %zu, offset %zu", filename, line, line_offset
       +                                        );
       +                                        goto error;
       +                                }
       +                                for (size_t k = 0; k < cmap->num_cmds; k++) {
       +                                        if (str_array_equal(cmap->cmds[k].text, st->args[1].text, st->args[1].len)) {
       +                                                free(cmap->cmds[k].text);
       +                                                cmap->cmds[k].text = ledit_strndup(st->args[0].text, st->args[0].len);
       +                                        }
       +                                }
       +                        }
       +                        command_mapping_init = 1;
       +                }
       +        }
       +        if (!cfg->langs[cur_lang]) {
       +                /* FIXME: pass actual beginning line and offset so this doesn't have to
       +                   use the line and offset of the first list element */
       +                if (list->len > 0) {
       +                        *errstr = print_fmt(
       +                            "%s: Missing language setting in language mapping configuration "
       +                            "at line %zu, offset %zu", filename, list->objs[0].tok.line, list->objs[0].tok.line_offset
       +                        );
       +                } else {
       +                        *errstr = print_fmt("%s: Missing language setting in language mapping configuration", filename);
       +                }
       +                goto error;
       +        }
       +        return 0;
       +error:
       +        return 1;
       +}
       +
       +static void
       +append_mapping(struct config *cfg) {
       +        push_lang(cfg);
       +        ledit_assert(cfg->num_langs > 1);
       +
       +        /* first duplicate original mappings before replacing the text */
       +        /* FIXME: optimize this to avoid useless reallocations */
       +        size_t cur_lang = cfg->num_langs - 1;
       +        basic_key_array *arr1 = &cfg->basic_keys[cur_lang];
       +        arr1->num_keys = arr1->alloc_keys = cfg->basic_keys[0].num_keys;
       +        arr1->keys = ledit_reallocarray(NULL, arr1->num_keys, sizeof(basic_key_mapping));
       +        memmove(arr1->keys, cfg->basic_keys[0].keys, arr1->num_keys * sizeof(basic_key_mapping));
       +        for (size_t i = 0; i < arr1->num_keys; i++) {
       +                if (arr1->keys[i].text)
       +                        arr1->keys[i].text = ledit_strdup(arr1->keys[i].text);
       +        }
       +
       +
       +        command_key_array *arr2 = &cfg->command_keys[cur_lang];
       +        arr2->num_keys = arr2->alloc_keys = cfg->command_keys[0].num_keys;
       +        arr2->keys = ledit_reallocarray(NULL, arr2->num_keys, sizeof(command_key_mapping));
       +        memmove(arr2->keys, cfg->command_keys[0].keys, arr2->num_keys * sizeof(command_key_mapping));
       +        for (size_t i = 0; i < arr2->num_keys; i++) {
       +                if (arr2->keys[i].text)
       +                        arr2->keys[i].text = ledit_strdup(arr2->keys[i].text);
       +        }
       +
       +        command_array *arr3 = &cfg->cmds[cur_lang];
       +        arr3->num_cmds = arr3->alloc_cmds = cfg->cmds[0].num_cmds;
       +        arr3->cmds = ledit_reallocarray(NULL, arr3->num_cmds, sizeof(command_mapping));
       +        memmove(arr3->cmds, cfg->cmds[0].cmds, arr3->num_cmds * sizeof(command_mapping));
       +        for (size_t i = 0; i < arr3->num_cmds; i++) {
       +                arr3->cmds[i].text = ledit_strdup(arr3->cmds[i].text);
       +        }
       +}
       +
       +#ifdef LEDIT_DEBUG
       +static void
       +debug_print_obj(ast_obj *obj, int shiftwidth) {
       +        for (int i = 0; i < shiftwidth; i++) {
       +                fprintf(stderr, " ");
       +        }
       +        switch (obj->type) {
       +        case OBJ_STRING:
       +                fprintf(stderr, "STRING: %.*s\n", (int)obj->obj.str.tok.len, obj->obj.str.tok.text);
       +                break;
       +        case OBJ_STATEMENT:
       +                fprintf(stderr, "STATEMENT: %.*s ", (int)obj->obj.statement.func_tok.len, obj->obj.statement.func_tok.text);
       +                for (size_t i = 0; i < obj->obj.statement.len; i++) {
       +                        fprintf(stderr, "%.*s ", (int)obj->obj.statement.args[i].len, obj->obj.statement.args[i].text);
       +                }
       +                fprintf(stderr, "\n");
       +                break;
       +        case OBJ_ASSIGNMENT:
       +                fprintf(stderr, "ASSIGNMENT: %.*s =\n", (int)obj->obj.assignment.tok.len, obj->obj.assignment.tok.text);
       +                debug_print_obj(obj->obj.assignment.value, shiftwidth + 4);
       +                break;
       +        case OBJ_LIST:
       +                fprintf(stderr, "LIST:\n");
       +                for (size_t i = 0; i < obj->obj.list.len; i++) {
       +                        debug_print_obj(&obj->obj.list.objs[i], shiftwidth + 4);
       +                }
       +                break;
       +        }
       +}
       +#endif
       +
       +/* WARNING: *errstr must be freed! */
       +int
       +config_loadfile(ledit_common *common, char *filename, char **errstr) {
       +        #ifdef LEDIT_DEBUG
       +        struct timespec now, elapsed, last;
       +        clock_gettime(CLOCK_MONOTONIC, &last);
       +        #endif
       +        size_t len;
       +        *errstr = NULL;
       +        ast_list list = {.objs = NULL, .len = 0, .cap = 0};
       +        char *file_contents = NULL;
       +        if (filename) {
       +                file_contents = load_file(filename, &len, errstr);
       +                if (!file_contents) return 1;
       +                #ifdef LEDIT_DEBUG
       +                clock_gettime(CLOCK_MONOTONIC, &now);
       +                ledit_timespecsub(&now, &last, &elapsed);
       +                ledit_debug_fmt(
       +                    "Time to load config file: %lld seconds, %ld nanoseconds\n",
       +                    (long long)elapsed.tv_sec, elapsed.tv_nsec
       +                );
       +                last = now;
       +                #endif
       +                /* start at line 1 to make error messages more useful */
       +                struct lexstate s = {file_contents, len, 0, 1, 0};
       +                if (parse_list(&s, &list, 1, filename, errstr)) {
       +                        free(file_contents);
       +                        return 1;
       +                }
       +                #ifdef LEDIT_DEBUG
       +                clock_gettime(CLOCK_MONOTONIC, &now);
       +                ledit_timespecsub(&now, &last, &elapsed);
       +                ledit_debug_fmt(
       +                    "Time to parse config file: %lld seconds, %ld nanoseconds\n",
       +                    (long long)elapsed.tv_sec, elapsed.tv_nsec
       +                );
       +                #endif
       +        }
       +
       +        #ifdef LEDIT_DEBUG
       +        clock_gettime(CLOCK_MONOTONIC, &last);
       +        for (size_t i = 0; i < list.len; i++) {
       +                debug_print_obj(&list.objs[i], 0);
       +        }
       +        clock_gettime(CLOCK_MONOTONIC, &now);
       +        ledit_timespecsub(&now, &last, &elapsed);
       +        ledit_debug_fmt(
       +            "Time to print useless information: %lld seconds, %ld nanoseconds\n",
       +            (long long)elapsed.tv_sec, elapsed.tv_nsec
       +        );
       +        clock_gettime(CLOCK_MONOTONIC, &last);
       +        #endif
       +
       +        struct config cfg = {NULL};
       +        int theme_init = 0, bindings_init = 0, mappings_init = 0;
       +        ast_assignment *assignment;
       +        for (size_t i = 0; i < list.len; i++) {
       +                switch (list.objs[i].type) {
       +                case OBJ_ASSIGNMENT:
       +                        assignment = &list.objs[i].obj.assignment;
       +                        if (str_array_equal("theme", assignment->tok.text, assignment->tok.len)) {
       +                                if (theme_init) {
       +                                        *errstr = print_fmt(
       +                                            "%s: Duplicate theme definition at line %zu, offset %zu",
       +                                            filename, assignment->tok.line, assignment->tok.line_offset
       +                                        );
       +                                        goto error;
       +                                } else if (assignment->value->type != OBJ_LIST) {
       +                                        *errstr = print_fmt(
       +                                            "%s: Invalid theme definition at line %zu, offset %zu",
       +                                            filename, assignment->tok.line, assignment->tok.line_offset
       +                                        );
       +                                        goto error;
       +                                }
       +                                cfg.theme = load_theme(common, &assignment->value->obj.list, filename, errstr);
       +                                if (!cfg.theme)
       +                                        goto error;
       +                                theme_init = 1;
       +                        } else if (str_array_equal("bindings", assignment->tok.text, assignment->tok.len)) {
       +                                if (bindings_init) {
       +                                        *errstr = print_fmt(
       +                                            "%s: Duplicate definition of bindings at line %zu, offset %zu",
       +                                            filename, assignment->tok.line, assignment->tok.line_offset
       +                                        );
       +                                        goto error;
       +                                }
       +                                push_lang(&cfg);
       +                                if (assignment->value->type != OBJ_LIST) {
       +                                        *errstr = print_fmt(
       +                                            "%s: Invalid definition of bindings at line %zu, offset %zu",
       +                                            filename, assignment->tok.line, assignment->tok.line_offset
       +                                        );
       +                                        goto error;
       +                                }
       +                                if (load_bindings(&cfg, &assignment->value->obj.list, filename, errstr))
       +                                        goto error;
       +                                bindings_init = 1;
       +                        } else if (str_array_equal("language-mapping", assignment->tok.text, assignment->tok.len)) {
       +                                if (cfg.num_langs == 0) {
       +                                        ledit_debug("No key/command bindings configured; loading defaults\n");
       +                                        push_lang(&cfg);
       +                                        /* load default config */
       +                                        ast_list empty_list = {.objs = NULL, .len = 0, .cap = 0};
       +                                        /* shouldn't usually happen */
       +                                        if (load_bindings(&cfg, &empty_list, filename, errstr))
       +                                                goto error;
       +                                        bindings_init = 1;
       +                                } else if (assignment->value->type != OBJ_LIST) {
       +                                        *errstr = print_fmt(
       +                                            "%s: Invalid definition of language mapping at line %zu, offset %zu",
       +                                            filename, assignment->tok.line, assignment->tok.line_offset
       +                                        );
       +                                        goto error;
       +                                }
       +
       +                                append_mapping(&cfg);
       +
       +                                if (load_mapping(&cfg, &assignment->value->obj.list, filename, errstr))
       +                                        goto error;
       +                                mappings_init = 1;
       +                        } else {
       +                                *errstr = print_fmt(
       +                                    "%s: Invalid assignment at line %zu, offset %zu",
       +                                    filename, assignment->tok.line, assignment->tok.line_offset
       +                                );
       +                                goto error;
       +                        }
       +                        break;
       +                default:
       +                        *errstr = print_fmt(
       +                            "%s: Invalid statement at line %zu, offset %zu",
       +                            filename, list.objs[i].tok.line, list.objs[i].tok.line_offset
       +                        );
       +                        goto error;
       +                }
       +        }
       +        if (!theme_init) {
       +                ledit_debug("No theme configured; loading defaults\n");
       +                cfg.theme = load_theme(common, NULL, NULL, errstr);
       +                if (!cfg.theme)
       +                        goto error;
       +        }
       +        if (!bindings_init) {
       +                ledit_debug("No key/command bindings configured; loading defaults\n");
       +                push_lang(&cfg);
       +                /* load default config */
       +                ast_list empty_list = {.objs = NULL, .len = 0, .cap = 0};
       +                /* shouldn't usually happen */
       +                if (load_bindings(&cfg, &empty_list, NULL, errstr))
       +                        goto error;
       +        }
       +        if (!mappings_init) {
       +                ledit_debug("No key/command mappings configured; loading defaults\n");
       +                for (size_t i = 0; i < LENGTH(mappings_default); i++) {
       +                        append_mapping(&cfg);
       +                        size_t cur_lang = cfg.num_langs - 1;
       +                        cfg.langs[cur_lang] = ledit_strdup(mappings_default[i].lang);
       +                        basic_key_array *bkmap = &cfg.basic_keys[cur_lang];
       +                        command_key_array *ckmap = &cfg.command_keys[cur_lang];
       +                        command_array *cmap = &cfg.cmds[cur_lang];
       +                        /* FIXME: any way to speed this up? I guess once the keys can be binary-searched... */
       +                        /* FIXME: duplicated code from above */
       +                        for (size_t j = 0; j < mappings_default[i].keys_len; j++) {
       +                                for (size_t k = 0; k < bkmap->num_keys; k++) {
       +                                        if (bkmap->keys[k].text && !strcmp(bkmap->keys[k].text, mappings_default[i].keys[j].to)) {
       +                                                free(bkmap->keys[k].text);
       +                                                bkmap->keys[k].text = ledit_strdup(mappings_default[i].keys[j].from);
       +                                        }
       +                                }
       +                                for (size_t k = 0; k < ckmap->num_keys; k++) {
       +                                        if (ckmap->keys[k].text && !strcmp(ckmap->keys[k].text, mappings_default[i].keys[j].to)) {
       +                                                free(ckmap->keys[k].text);
       +                                                ckmap->keys[k].text = ledit_strdup(mappings_default[i].keys[j].from);
       +                                        }
       +                                }
       +                        }
       +                        for (size_t j = 0; j < mappings_default[i].cmds_len; j++) {
       +                                for (size_t k = 0; k < cmap->num_cmds; k++) {
       +                                        if (!strcmp(cmap->cmds[k].text, mappings_default[i].keys[j].to)) {
       +                                                free(cmap->cmds[k].text);
       +                                                cmap->cmds[k].text = ledit_strdup(mappings_default[i].keys[j].from);
       +                                        }
       +                                }
       +                        }
       +                }
       +        }
       +        destroy_list(&list);
       +        free(file_contents);
       +        config_destroy(common, &config);
       +        config = cfg;
       +        #ifdef LEDIT_DEBUG
       +        clock_gettime(CLOCK_MONOTONIC, &now);
       +        ledit_timespecsub(&now, &last, &elapsed);
       +        ledit_debug_fmt(
       +            "Time to interpret config file: %lld seconds, %ld nanoseconds\n",
       +            (long long)elapsed.tv_sec, elapsed.tv_nsec
       +        );
       +        #endif
       +        return 0;
       +error:
       +        destroy_list(&list);
       +        free(file_contents);
       +        config_destroy(common, &cfg);
       +        return 1;
       +}
       +
       +ledit_theme *
       +config_get_theme(void) {
       +        ledit_assert(config.theme != NULL);
       +        return config.theme;
       +}
       +
       +basic_key_array *
       +config_get_basic_keys(size_t lang_index) {
       +        ledit_assert(lang_index < config.num_langs);
       +        return &config.basic_keys[lang_index];
       +}
       +
       +command_key_array *
       +config_get_command_keys(size_t lang_index) {
       +        ledit_assert(lang_index < config.num_langs);
       +        return &config.command_keys[lang_index];
       +}
       +
       +command_array *
       +config_get_commands(size_t lang_index) {
       +        ledit_assert(lang_index < config.num_langs);
       +        return &config.cmds[lang_index];
       +}
       +
       +int
       +config_get_language_index(char *lang, size_t *idx_ret) {
       +        for (size_t i = 0; i < config.num_langs; i++) {
       +                if (!strcmp(lang, config.langs[i])) {
       +                        *idx_ret = i;
       +                        return 0;
       +                }
       +        }
       +        return 1;
       +}
   DIR diff --git a/configparser.h b/configparser.h
       t@@ -0,0 +1,80 @@
       +#ifndef _CONFIGPARSER_H_
       +#define _CONFIGPARSER_H_
       +
       +#include "common.h"
       +#include "uglycrap.h"
       +#include "keys_command.h"
       +#include "keys_basic.h"
       +
       +typedef struct {
       +        int scrollbar_width;
       +        int scrollbar_step;
       +        int text_size;
       +        XftColor text_fg;
       +        XftColor text_bg;
       +        XftColor cursor_fg;
       +        XftColor cursor_bg;
       +        XftColor selection_fg;
       +        XftColor selection_bg;
       +        XftColor bar_fg;
       +        XftColor bar_bg;
       +        XftColor bar_cursor;
       +        XftColor scrollbar_fg;
       +        XftColor scrollbar_bg;
       +        const char *text_font;
       +} ledit_theme;
       +
       +typedef struct {
       +        char *text;        /* for keys that correspond with text */
       +        unsigned int mods; /* modifier mask */
       +        KeySym keysym;     /* for other keys, e.g. arrow keys */
       +        ledit_mode modes;  /* modes in which this keybinding is functional */
       +        basic_key_cb *cb;  /* callback */
       +} basic_key_mapping;
       +
       +typedef struct {
       +        char *text;              /* for keys that correspond with text */
       +        unsigned int mods;       /* modifier mask */
       +        KeySym keysym;           /* for other keys, e.g. arrow keys */
       +        command_mode modes;      /* substitute, etc. */
       +        command_key_cb *cb;      /* callback */
       +} command_key_mapping;
       +
       +typedef struct {
       +        char *text;      /* text typed to call command */
       +        command_cb *cb;  /* callback */
       +} command_mapping;
       +
       +typedef struct {
       +        basic_key_mapping *keys;
       +        size_t num_keys;
       +        size_t alloc_keys;
       +} basic_key_array;
       +
       +typedef struct {
       +        command_key_mapping *keys;
       +        size_t num_keys;
       +        size_t alloc_keys;
       +} command_key_array;
       +
       +typedef struct {
       +        command_mapping *cmds;
       +        size_t num_cmds;
       +        size_t alloc_cmds;
       +} command_array;
       +
       +/* Note: The config is initialized immediately when ledit starts, so these
       + * should not return NULL (unless an invalid language index is given), but
       + * it's still better to check just in case. */
       +
       +/* Note: The returned pointers are invalidated if the config is reloaded. */
       +
       +ledit_theme *config_get_theme(void);
       +basic_key_array *config_get_basic_keys(size_t lang_index);
       +command_key_array *config_get_command_keys(size_t lang_index);
       +command_array *config_get_commands(size_t lang_index);
       +int config_get_language_index(char *lang, size_t *idx_ret);
       +int config_loadfile(ledit_common *common, char *filename, char **errstr);
       +void config_cleanup(ledit_common *common);
       +
       +#endif
   DIR diff --git a/keys.c b/keys.c
       t@@ -10,22 +10,8 @@
        #include "memory.h"
        #include "common.h"
        #include "txtbuf.h"
       -#include "theme.h"
        #include "window.h"
        #include "keys.h"
       -#include "keys_config.h"
       -
       -KEY_LANGS;
       -
       -int
       -get_language_index(char *lang) {
       -        for (size_t i = 0; i < LENGTH(key_langs); i++) {
       -                if (!strcmp(key_langs[i], lang)) {
       -                        return i;
       -                }
       -        }
       -        return -1;
       -}
        
        /* FIXME: Does this break anything? */
        /*static unsigned int importantmod = ShiftMask | ControlMask | Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask;*/
   DIR diff --git a/keys.h b/keys.h
       t@@ -19,4 +19,51 @@ void preprocess_key(
            char *buf_ret, int buf_size, int *buf_len_ret
        );
        
       +/* FIXME: documentation */
       +#define GEN_CB_MAP_HELPERS(name, typename, cmp_entry)                     \
       +                                                                          \
       +static int name##_sorted = 0;                                             \
       +                                                                          \
       +/*                                                                        \
       + * IMPORTANT: The text passed to *_get_entry may not be nul-terminated,   \
       + * so a txtbuf has to be used for the bsearch comparison helper.          \
       + */                                                                       \
       +                                                                          \
       +static int                                                                \
       +name##_search_helper(const void *keyv, const void *entryv) {              \
       +        txtbuf *key = (txtbuf *)keyv;                                     \
       +        typename *entry = (typename *)entryv;                             \
       +        int ret = strncmp(key->text, entry->cmp_entry, key->len);         \
       +        if (ret == 0) {                                                   \
       +                if (entry->cmp_entry[key->len] == '\0')                   \
       +                        return 0;                                         \
       +                else                                                      \
       +                        return -1;                                        \
       +        }                                                                 \
       +        return ret;                                                       \
       +}                                                                         \
       +                                                                          \
       +static int                                                                \
       +name##_sort_helper(const void *entry1v, const void *entry2v) {            \
       +        typename *entry1 = (typename *)entry1v;                           \
       +        typename *entry2 = (typename *)entry2v;                           \
       +        return strcmp(entry1->cmp_entry, entry2->cmp_entry);              \
       +}                                                                         \
       +                                                                          \
       +typename *                                                                \
       +name##_get_entry(char *text, size_t len) {                                \
       +        /* just in case */                                                \
       +        if (!name##_sorted) {                                             \
       +                qsort(                                                    \
       +                    name, LENGTH(name),                                   \
       +                    sizeof(name[0]), &name##_sort_helper);                \
       +                name##_sorted = 1;                                        \
       +        }                                                                 \
       +        txtbuf tmp = {.len = len, .cap = len, .text = text};              \
       +        return bsearch(                                                   \
       +            &tmp, name, LENGTH(name),                                     \
       +            sizeof(name[0]), &name##_search_helper                        \
       +        );                                                                \
       +}
       +
        #endif
   DIR diff --git a/keys_basic.c b/keys_basic.c
       t@@ -12,6 +12,8 @@
           they are now not allowed at all */
        /* FIXME: a lot of error checking in the individual functions may be redundant
           now that more checking is done beforehand for the allowed keys */
       +/* FIXME: sort functions a bit better, maybe split file */
       +/* FIXME: documentation */
        #include <stdio.h>
        #include <stdlib.h>
        
       t@@ -29,17 +31,216 @@
        #include "txtbuf.h"
        #include "undo.h"
        #include "cache.h"
       -#include "theme.h"
        #include "window.h"
        #include "buffer.h"
        #include "view.h"
        #include "search.h"
        
        #include "keys.h"
       -#include "keys_config.h"
        #include "keys_basic.h"
        #include "keys_command.h"
       -#include "keys_basic_config.h"
       +#include "configparser.h"
       +
       +/*************************************************************************
       + * Declarations for all functions that can be used in the configuration. *
       + *************************************************************************/
       +
       +static struct action backspace(ledit_view *view, char *text, size_t len);
       +static struct action cursor_left(ledit_view *view, char *text, size_t len);
       +static struct action cursor_right(ledit_view *view, char *text, size_t len);
       +static struct action cursor_up(ledit_view *view, char *text, size_t len);
       +static struct action cursor_down(ledit_view *view, char *text, size_t len);
       +static struct action return_key(ledit_view *view, char *text, size_t len);
       +static struct action delete_key(ledit_view *view, char *text, size_t len);
       +static struct action escape_key(ledit_view *view, char *text, size_t len);
       +static struct action enter_insert(ledit_view *view, char *text, size_t len);
       +static struct action cursor_to_beginning(ledit_view *view, char *text, size_t len);
       +static struct action key_0(ledit_view *view, char *text, size_t len);
       +static struct action push_0(ledit_view *view, char *text, size_t len);
       +static struct action push_1(ledit_view *view, char *text, size_t len);
       +static struct action push_2(ledit_view *view, char *text, size_t len);
       +static struct action push_3(ledit_view *view, char *text, size_t len);
       +static struct action push_4(ledit_view *view, char *text, size_t len);
       +static struct action push_5(ledit_view *view, char *text, size_t len);
       +static struct action push_6(ledit_view *view, char *text, size_t len);
       +static struct action push_7(ledit_view *view, char *text, size_t len);
       +static struct action push_8(ledit_view *view, char *text, size_t len);
       +static struct action push_9(ledit_view *view, char *text, size_t len);
       +static struct action delete(ledit_view *view, char *text, size_t len);
       +static struct action enter_visual(ledit_view *view, char *text, size_t len);
       +static struct action switch_selection_end(ledit_view *view, char *text, size_t len);
       +static struct action clipcopy(ledit_view *view, char *text, size_t len);
       +static struct action clippaste(ledit_view *view, char *text, size_t len);
       +static struct action show_line(ledit_view *view, char *text, size_t len);
       +static struct action enter_commandedit(ledit_view *view, char *text, size_t len);
       +static struct action enter_searchedit_backward(ledit_view *view, char *text, size_t len);
       +static struct action enter_searchedit_forward(ledit_view *view, char *text, size_t len);
       +static struct action key_search_next(ledit_view *view, char *text, size_t len);
       +static struct action key_search_prev(ledit_view *view, char *text, size_t len);
       +static struct action undo(ledit_view *view, char *text, size_t len);
       +static struct action redo(ledit_view *view, char *text, size_t len);
       +static struct action insert_mode_insert_text(ledit_view *view, char *text, size_t len);
       +static struct action repeat_command(ledit_view *view, char *text, size_t len);
       +static struct action screen_up(ledit_view *view, char *text, size_t len);
       +static struct action screen_down(ledit_view *view, char *text, size_t len);
       +static struct action scroll_with_cursor_up(ledit_view *view, char *text, size_t len);
       +static struct action scroll_with_cursor_down(ledit_view *view, char *text, size_t len);
       +static struct action scroll_lines_up(ledit_view *view, char *text, size_t len);
       +static struct action scroll_lines_down(ledit_view *view, char *text, size_t len);
       +static struct action move_to_line(ledit_view *view, char *text, size_t len);
       +static struct action paste_normal(ledit_view *view, char *text, size_t len);
       +static struct action paste_normal_backwards(ledit_view *view, char *text, size_t len);
       +static struct action change(ledit_view *view, char *text, size_t len);
       +static struct action move_to_eol(ledit_view *view, char *text, size_t len);
       +static struct action mark_line(ledit_view *view, char *text, size_t len);
       +static struct action jump_to_mark(ledit_view *view, char *text, size_t len);
       +static struct action next_word(ledit_view *view, char *text, size_t len);
       +static struct action next_word_end(ledit_view *view, char *text, size_t len);
       +static struct action next_bigword(ledit_view *view, char *text, size_t len);
       +static struct action next_bigword_end(ledit_view *view, char *text, size_t len);
       +static struct action prev_word(ledit_view *view, char *text, size_t len);
       +static struct action prev_bigword(ledit_view *view, char *text, size_t len);
       +static struct action append_after_eol(ledit_view *view, char *text, size_t len);
       +static struct action append_after_cursor(ledit_view *view, char *text, size_t len);
       +static struct action append_line_above(ledit_view *view, char *text, size_t len);
       +static struct action append_line_below(ledit_view *view, char *text, size_t len);
       +static struct action find_next_char_forwards(ledit_view *view, char *text, size_t len);
       +static struct action find_next_char_backwards(ledit_view *view, char *text, size_t len);
       +static struct action find_char_forwards(ledit_view *view, char *text, size_t len);
       +static struct action find_char_backwards(ledit_view *view, char *text, size_t len);
       +static struct action change_to_eol(ledit_view *view, char *text, size_t len);
       +static struct action delete_to_eol(ledit_view *view, char *text, size_t len);
       +static struct action delete_chars_forwards(ledit_view *view, char *text, size_t len);
       +static struct action delete_chars_backwards(ledit_view *view, char *text, size_t len);
       +static struct action yank(ledit_view *view, char *text, size_t len);
       +static struct action yank_lines(ledit_view *view, char *text, size_t len);
       +static struct action replace(ledit_view *view, char *text, size_t len);
       +static struct action cursor_to_first_non_ws(ledit_view *view, char *text, size_t len);
       +static struct action join_lines(ledit_view *view, char *text, size_t len);
       +static struct action insert_at_beginning(ledit_view *view, char *text, size_t len);
       +static struct action toggle_hard_line_based(ledit_view *view, char *text, size_t len);
       +
       +/***********************************************
       + * String-function mapping for config parsing. *
       + ***********************************************/
       +
       +/* FIXME: delete-backwards, delete-forwards should be renamed;
       +   *key functions should be renamed (they're very vague) */
       +
       +typedef enum {
       +        KEY_FLAG_NONE = 0,
       +        KEY_FLAG_JUMP_TO_CURSOR = 1,
       +        KEY_FLAG_LOCK_ALLOWED = 2
       +} basic_key_cb_flags;
       +
       +typedef struct action (*basic_key_cb_func)(ledit_view *, char *, size_t);
       +
       +struct basic_key_cb {
       +        char *text;
       +        basic_key_cb_func func;
       +        basic_key_cb_flags flags;
       +        ledit_mode allowed_modes;
       +};
       +
       +int
       +basic_key_cb_modemask_is_valid(basic_key_cb *cb, ledit_mode modes) {
       +        return (~cb->allowed_modes & modes) == 0;
       +}
       +
       +/* FIXME: make functions work in more modes (e.g. cursor-to-first-non-whitespace) */
       +static struct basic_key_cb basic_key_cb_map[] = {
       +        {"append-after-cursor", &append_after_cursor, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
       +        {"append-after-eol", &append_after_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
       +        {"append-line-above", &append_line_above, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
       +        {"append-line-below", &append_line_below, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
       +        {"backspace", &backspace, KEY_FLAG_JUMP_TO_CURSOR, INSERT},
       +        {"change", &change, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL},
       +        {"change-to-eol", &change_to_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
       +        {"clipboard-copy", &clipcopy, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL},
       +        {"clipboard-paste", &clippaste, KEY_FLAG_JUMP_TO_CURSOR, INSERT},
       +        {"cursor-down", &cursor_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL},
       +        {"cursor-left", &cursor_left, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL},
       +        {"cursor-right", &cursor_right, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL},
       +        {"cursor-to-beginning", &cursor_to_beginning, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"cursor-to-first-non-whitespace", &cursor_to_first_non_ws, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL},
       +        {"cursor-up", &cursor_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, VISUAL|INSERT|NORMAL},
       +        {"delete", &delete, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL},
       +        {"delete-backwards", &delete_chars_backwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
       +        {"delete-forwards", &delete_chars_forwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
       +        {"delete-key", &delete_key, KEY_FLAG_JUMP_TO_CURSOR, INSERT},
       +        {"delete-to-eol", &delete_to_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
       +        {"enter-commandedit", &enter_commandedit, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"enter-insert", &enter_insert, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL},
       +        {"enter-searchedit-backwards", &enter_searchedit_backward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL},
       +        {"enter-searchedit-forwards", &enter_searchedit_forward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL},
       +        {"enter-visual", &enter_visual, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
       +        {"escape-key", &escape_key, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
       +        {"find-char-backwards", &find_char_backwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"find-char-forwards", &find_char_forwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"find-next-char-backwards", &find_next_char_backwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"find-next-char-forwards", &find_next_char_forwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"insert-at-beginning", &insert_at_beginning, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
       +        {"insert-text", &insert_mode_insert_text, KEY_FLAG_JUMP_TO_CURSOR, INSERT},
       +        {"join-lines", &join_lines, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
       +        {"jump-to-mark", &jump_to_mark, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"key-0", &key_0, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"mark-line", &mark_line, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"move-to-eol", &move_to_eol, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"move-to-line", &move_to_line, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"next-bigword", &next_bigword, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"next-bigword-end", &next_bigword_end, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"next-word", &next_word, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"next-word-end", &next_word_end, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"paste-normal", &paste_normal, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
       +        {"paste-normal-backwards", &paste_normal_backwards, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
       +        {"previous-bigword", &prev_bigword, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"previous-word", &prev_word, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"push-0", &push_0, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"push-1", &push_1, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"push-2", &push_2, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"push-3", &push_3, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"push-4", &push_4, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"push-5", &push_5, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"push-6", &push_6, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"push-7", &push_7, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"push-8", &push_8, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"push-9", &push_9, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"redo", &redo, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
       +        {"repeat-command", &repeat_command, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
       +        {"replace", &replace, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
       +        {"return-key", &return_key, KEY_FLAG_JUMP_TO_CURSOR, INSERT},
       +        {"screen-down", &screen_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL},
       +        {"screen-up", &screen_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL},
       +        {"scroll-lines-down", &scroll_lines_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL},
       +        {"scroll-lines-up", &scroll_lines_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL},
       +        {"scroll-with-cursor-down", &scroll_with_cursor_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL},
       +        {"scroll-with-cursor-up", &scroll_with_cursor_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL},
       +        {"search-next", &key_search_next, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL},
       +        {"search-previous", &key_search_prev, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL},
       +        {"show-line", &show_line, KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"switch-selection-end", &switch_selection_end, KEY_FLAG_JUMP_TO_CURSOR, VISUAL},
       +        {"toggle-hard-line-based", &toggle_hard_line_based, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL}, /* FIXME: also in INSERT */
       +        {"undo", &undo, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
       +        {"yank", &yank, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL},
       +        {"yank-lines", &yank_lines, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL},
       +};
       +
       +GEN_CB_MAP_HELPERS(basic_key_cb_map, basic_key_cb, text)
       +
       +/***************************************************
       + * General global variables and utility functions. *
       + ***************************************************/
       +
       +enum key_type {
       +        KEY_INVALID = 0,
       +        KEY_MOTION_CHAR = 4,
       +        KEY_MOTION_LINE = 8,
       +        KEY_MOTION = 4|8,
       +        KEY_MOTIONALLOWED = 16,
       +        KEY_NUMBER = 32,
       +        KEY_NUMBERALLOWED = 64,
       +        KEY_ANY = 0xFF
       +};
        
        /* note: this is supposed to be global for all views/buffers */
        int paste_buffer_line_based = 0;
       t@@ -310,6 +511,10 @@ static void
        discard_repetition_stack(void) {
                if (repetition_stack.replaying)
                        return;
       +        for (size_t i = 0; i < repetition_stack.tmp_len; i++) {
       +                free(repetition_stack.tmp_stack[i].key_text);
       +                repetition_stack.tmp_stack[i].key_text = NULL;
       +        }
                repetition_stack.tmp_len = 0;
        }
        
       t@@ -323,6 +528,10 @@ finalize_repetition_stack(void) {
                if (repetition_stack.replaying)
                        return;
                size_t tmp;
       +        for (size_t i = 0; i < repetition_stack.len; i++) {
       +                free(repetition_stack.stack[i].key_text);
       +                repetition_stack.stack[i].key_text = NULL;
       +        }
                struct repetition_stack_elem *tmpstack;
                repetition_stack.len = repetition_stack.tmp_len;
                repetition_stack.tmp_len = 0;
       t@@ -485,6 +694,10 @@ delete_selection(ledit_view *view) {
                return 0;
        }
        
       +/********************************************
       + * Functions that were declared at the top. *
       + ********************************************/
       +
        /* FIXME: should these delete characters or graphemes? */
        static struct action
        delete_chars_forwards(ledit_view *view, char *text, size_t len) {
       t@@ -549,6 +762,7 @@ delete_chars_backwards(ledit_view *view, char *text, size_t len) {
        /* used to set cursor - I guess this is sort of a hack */
        static void
        push_undo_empty_insert(ledit_view *view, size_t line, size_t index, int start_group) {
       +        /* WARNING: Don't abuse txtbuf like this unless you're stupid like me. */
                txtbuf ins_buf = {.text = "", .len = 0, .cap = 0};
                ledit_range ins_range = {.line1 = line, .byte1 = index, .line2 = line, .byte2 = index};
                ledit_range cur_range = {.line1 = line, .byte1 = index, .line2 = line, .byte2 = index};
       t@@ -1271,6 +1485,18 @@ paste_normal_backwards(ledit_view *view, char *text, size_t len) {
                return (struct action){ACTION_NONE, NULL};
        }
        
       +static struct action
       +key_0(ledit_view *view, char *text, size_t len) {
       +        struct key_stack_elem *e = peek_key_stack();
       +        if (!e || (e->key & KEY_MOTIONALLOWED)) {
       +                return cursor_to_beginning(view, text, len);
       +        } else if (e->key & KEY_NUMBER) {
       +                return push_0(view, text, len);
       +        } else {
       +                return err_invalid_key(view);
       +        }
       +}
       +
        static void
        push_num(ledit_view *view, int num) {
                struct key_stack_elem *e = peek_key_stack();
       t@@ -2242,10 +2468,9 @@ toggle_hard_line_based(ledit_view *view, char *text, size_t len) {
        }
        
        static struct action
       -handle_key(ledit_view *view, char *key_text, size_t len, KeySym sym, unsigned int key_state, int lang_index, int *found, enum key_type *type) {
       -        struct key *cur_keys = keys[lang_index].keys;
       -        int num_keys = keys[lang_index].num_keys;
       -        struct key_stack_elem *e = peek_key_stack();
       +handle_key(ledit_view *view, char *key_text, size_t len, KeySym sym, unsigned int key_state, size_t lang_index, int *found, basic_key_cb_flags *flags) {
       +        basic_key_array *cur_keys = config_get_basic_keys(lang_index);
       +        size_t num_keys = cur_keys->num_keys;
                /* FIXME: check if control chars in text */
                /* FIXME: this is a bit of a hack because it's hardcoded */
                if (grab_char_cb && sym == XK_Escape) {
       t@@ -2253,31 +2478,32 @@ handle_key(ledit_view *view, char *key_text, size_t len, KeySym sym, unsigned in
                        return (struct action){ACTION_NONE, NULL};
                } else if (len > 0 && grab_char_cb) {
                        *found = 1;
       -                *type = 0;
                        return grab_char_cb(view, key_text, len);
                }
                *found = 0;
       -        for (int i = 0; i < num_keys; i++) {
       -                if (cur_keys[i].text) {
       +        for (size_t i = 0; i < num_keys; i++) {
       +                if (cur_keys->keys[i].text) {
                                if (len > 0 &&
       -                            (cur_keys[i].modes & view->mode) &&
       -                            (cur_keys[i].prev_keys == KEY_ANY || (!e && (cur_keys[i].prev_keys & KEY_NONE)) || (e && (e->key & cur_keys[i].prev_keys))) &&
       -                             ((!strncmp(cur_keys[i].text, key_text, len) &&
       -                               match_key(cur_keys[i].mods, key_state & ~ShiftMask)) ||
       -                              cur_keys[i].text[0] == '\0')) {
       +                            (cur_keys->keys[i].modes & view->mode) &&
       +                             ((!strncmp(cur_keys->keys[i].text, key_text, len) &&
       +                               match_key(cur_keys->keys[i].mods, key_state & ~ShiftMask)) ||
       +                              cur_keys->keys[i].text[0] == '\0')) {
                                        /* FIXME: seems a bit hacky to remove shift, but it
                                           is needed to make keys that use shift match */
       -                                *type = cur_keys[i].type;
       +                                *flags = cur_keys->keys[i].cb->flags;
                                        *found = 1;
       -                                return cur_keys[i].func(view, key_text, len);
       +                                if (!(*flags & KEY_FLAG_LOCK_ALLOWED) && view->lock_text)
       +                                        return view_locked_error(view);
       +                                return cur_keys->keys[i].cb->func(view, key_text, len);
                                }
       -                } else if ((cur_keys[i].modes & view->mode) &&
       -                           cur_keys[i].keysym == sym &&
       -                           (cur_keys[i].prev_keys == KEY_ANY || (!e && (cur_keys[i].prev_keys & KEY_NONE)) || (e && (e->key & cur_keys[i].prev_keys))) &&
       -                           match_key(cur_keys[i].mods, key_state)) {
       -                        *type = cur_keys[i].type;
       +                } else if ((cur_keys->keys[i].modes & view->mode) &&
       +                           cur_keys->keys[i].keysym == sym &&
       +                           match_key(cur_keys->keys[i].mods, key_state)) {
       +                        *flags = cur_keys->keys[i].cb->flags;
                                *found = 1;
       -                        return cur_keys[i].func(view, key_text, len);
       +                        if (!(*flags & KEY_FLAG_LOCK_ALLOWED) && view->lock_text)
       +                                return view_locked_error(view);
       +                        return cur_keys->keys[i].cb->func(view, key_text, len);
                        }
                }
                return (struct action){ACTION_NONE, NULL};
       t@@ -2299,13 +2525,13 @@ repeat_command(ledit_view *view, char *text, size_t len) {
                        return (struct action){ACTION_NONE, NULL};
                }
                int found;
       -        enum key_type type;
       +        basic_key_cb_flags flags;
                repetition_stack.replaying = 1;
                for (int i = 0; i < num; i++) {
                        unwind_repetition_stack();
                        struct repetition_stack_elem *e = get_cur_repetition_stack_elem();
                        while (e) {
       -                        (void)handle_key(view, e->key_text, e->len, e->sym, e->key_state, e->lang_index, &found, &type);
       +                        (void)handle_key(view, e->key_text, e->len, e->sym, e->key_state, e->lang_index, &found, &flags);
                                advance_repetition_stack();
                                e = get_cur_repetition_stack_elem();
                        }
       t@@ -2338,14 +2564,14 @@ basic_key_handler(ledit_view *view, XEvent *event, int lang_index) {
                int found = 0;
                int msg_shown = view->window->message_shown;
                view->window->message_shown = 0; /* FIXME: this is hacky */
       -        enum key_type type;
       -        struct action act = handle_key(view, buf, (size_t)n, sym, key_state, lang_index, &found, &type);
       +        basic_key_cb_flags flags;
       +        struct action act = handle_key(view, buf, (size_t)n, sym, key_state, lang_index, &found, &flags);
                if (found && n > 0 && !view->window->message_shown)
                        window_hide_message(view->window);
                else if (msg_shown)
                        view->window->message_shown = msg_shown;
        
       -        if (found && (type & KEY_ENSURE_CURSOR_SHOWN))
       +        if (found && (flags & KEY_FLAG_JUMP_TO_CURSOR))
                        view_ensure_cursor_shown(view);
                if (!found && n > 0) {
                        window_show_message(view->window, "Invalid key", -1);
   DIR diff --git a/keys_basic.h b/keys_basic.h
       t@@ -4,6 +4,11 @@
        #include <X11/Xlib.h>
        #include "view.h"
        
       +typedef struct basic_key_cb basic_key_cb;
       +
       +basic_key_cb *basic_key_cb_map_get_entry(char *text, size_t len);
       +int basic_key_cb_modemask_is_valid(basic_key_cb *cb, ledit_mode modes);
       +
        /* perform cleanup of global data */
        void basic_key_cleanup(void);
        struct action basic_key_handler(ledit_view *view, XEvent *event, int lang_index);
   DIR diff --git a/keys_basic_config.h b/keys_basic_config.h
       t@@ -1,457 +0,0 @@
       -/*
       - * These are all the regular keys used in normal, visual, and insert mode.
       - */
       -
       -/*
       - * Note: The key types are currently very inconsistent and don't always make
       - * sense. This will hopefully be fixed sometime. (FIXME)
       - */
       -
       -enum key_type {
       -        KEY_INVALID = 0,
       -        KEY_NONE = 2, /* FIXME: perhaps rather KEY_EMPTY? */
       -        KEY_MOTION_CHAR = 4,
       -        KEY_MOTION_LINE = 8,
       -        KEY_MOTION = 4|8,
       -        KEY_MOTIONALLOWED = 16,
       -        KEY_NUMBER = 32,
       -        KEY_NUMBERALLOWED = 64,
       -        KEY_ENSURE_CURSOR_SHOWN = 128, /* jump to cursor if it is off screen */ /* FIXME: maybe rather KEY_JUMP_TO_CURSOR? */
       -        KEY_ANY = 0xFF
       -};
       -
       -struct key {
       -        char *text;                                          /* for keys that correspond with text */
       -        unsigned int mods;                                   /* modifier mask */
       -        KeySym keysym;                                       /* for other keys, e.g. arrow keys */
       -        ledit_mode modes;                                    /* modes in which this keybinding is functional */
       -        enum key_type prev_keys;                             /* allowed previous keys */
       -        enum key_type type;                                  /* type of key - mainly used for ensure_cursor_shown */
       -        struct action (*func)(ledit_view *, char *, size_t); /* callback function */
       -};
       -
       -static struct action backspace(ledit_view *view, char *text, size_t len);
       -static struct action cursor_left(ledit_view *view, char *text, size_t len);
       -static struct action cursor_right(ledit_view *view, char *text, size_t len);
       -static struct action cursor_up(ledit_view *view, char *text, size_t len);
       -static struct action cursor_down(ledit_view *view, char *text, size_t len);
       -static struct action return_key(ledit_view *view, char *text, size_t len);
       -static struct action delete_key(ledit_view *view, char *text, size_t len);
       -static struct action escape_key(ledit_view *view, char *text, size_t len);
       -static struct action enter_insert(ledit_view *view, char *text, size_t len);
       -static struct action cursor_to_beginning(ledit_view *view, char *text, size_t len);
       -static struct action push_0(ledit_view *view, char *text, size_t len);
       -static struct action push_1(ledit_view *view, char *text, size_t len);
       -static struct action push_2(ledit_view *view, char *text, size_t len);
       -static struct action push_3(ledit_view *view, char *text, size_t len);
       -static struct action push_4(ledit_view *view, char *text, size_t len);
       -static struct action push_5(ledit_view *view, char *text, size_t len);
       -static struct action push_6(ledit_view *view, char *text, size_t len);
       -static struct action push_7(ledit_view *view, char *text, size_t len);
       -static struct action push_8(ledit_view *view, char *text, size_t len);
       -static struct action push_9(ledit_view *view, char *text, size_t len);
       -static struct action delete(ledit_view *view, char *text, size_t len);
       -static struct action enter_visual(ledit_view *view, char *text, size_t len);
       -static struct action switch_selection_end(ledit_view *view, char *text, size_t len);
       -static struct action clipcopy(ledit_view *view, char *text, size_t len);
       -static struct action clippaste(ledit_view *view, char *text, size_t len);
       -static struct action show_line(ledit_view *view, char *text, size_t len);
       -static struct action enter_commandedit(ledit_view *view, char *text, size_t len);
       -static struct action enter_searchedit_backward(ledit_view *view, char *text, size_t len);
       -static struct action enter_searchedit_forward(ledit_view *view, char *text, size_t len);
       -static struct action key_search_next(ledit_view *view, char *text, size_t len);
       -static struct action key_search_prev(ledit_view *view, char *text, size_t len);
       -static struct action undo(ledit_view *view, char *text, size_t len);
       -static struct action redo(ledit_view *view, char *text, size_t len);
       -static struct action insert_mode_insert_text(ledit_view *view, char *text, size_t len);
       -static struct action repeat_command(ledit_view *view, char *text, size_t len);
       -static struct action screen_up(ledit_view *view, char *text, size_t len);
       -static struct action screen_down(ledit_view *view, char *text, size_t len);
       -static struct action scroll_with_cursor_up(ledit_view *view, char *text, size_t len);
       -static struct action scroll_with_cursor_down(ledit_view *view, char *text, size_t len);
       -static struct action scroll_lines_up(ledit_view *view, char *text, size_t len);
       -static struct action scroll_lines_down(ledit_view *view, char *text, size_t len);
       -static struct action move_to_line(ledit_view *view, char *text, size_t len);
       -static struct action paste_normal(ledit_view *view, char *text, size_t len);
       -static struct action paste_normal_backwards(ledit_view *view, char *text, size_t len);
       -static struct action change(ledit_view *view, char *text, size_t len);
       -static struct action move_to_eol(ledit_view *view, char *text, size_t len);
       -static struct action mark_line(ledit_view *view, char *text, size_t len);
       -static struct action jump_to_mark(ledit_view *view, char *text, size_t len);
       -static struct action next_word(ledit_view *view, char *text, size_t len);
       -static struct action next_word_end(ledit_view *view, char *text, size_t len);
       -static struct action next_bigword(ledit_view *view, char *text, size_t len);
       -static struct action next_bigword_end(ledit_view *view, char *text, size_t len);
       -static struct action prev_word(ledit_view *view, char *text, size_t len);
       -static struct action prev_bigword(ledit_view *view, char *text, size_t len);
       -static struct action append_after_eol(ledit_view *view, char *text, size_t len);
       -static struct action append_after_cursor(ledit_view *view, char *text, size_t len);
       -static struct action append_line_above(ledit_view *view, char *text, size_t len);
       -static struct action append_line_below(ledit_view *view, char *text, size_t len);
       -static struct action find_next_char_forwards(ledit_view *view, char *text, size_t len);
       -static struct action find_next_char_backwards(ledit_view *view, char *text, size_t len);
       -static struct action find_char_forwards(ledit_view *view, char *text, size_t len);
       -static struct action find_char_backwards(ledit_view *view, char *text, size_t len);
       -static struct action change_to_eol(ledit_view *view, char *text, size_t len);
       -static struct action delete_to_eol(ledit_view *view, char *text, size_t len);
       -static struct action delete_chars_forwards(ledit_view *view, char *text, size_t len);
       -static struct action delete_chars_backwards(ledit_view *view, char *text, size_t len);
       -static struct action yank(ledit_view *view, char *text, size_t len);
       -static struct action yank_lines(ledit_view *view, char *text, size_t len);
       -static struct action replace(ledit_view *view, char *text, size_t len);
       -static struct action cursor_to_first_non_ws(ledit_view *view, char *text, size_t len);
       -static struct action join_lines(ledit_view *view, char *text, size_t len);
       -static struct action insert_at_beginning(ledit_view *view, char *text, size_t len);
       -static struct action toggle_hard_line_based(ledit_view *view, char *text, size_t len);
       -
       -/* FIXME: maybe sort these and use binary search
       -   -> but that would mess with the catch-all keys */
       -static struct key keys_en[] = {
       -        {NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &backspace},
       -        {NULL, 0, XK_Left, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left},
       -        {NULL, 0, XK_Right, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right},
       -        {NULL, 0, XK_Up, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up},
       -        {NULL, 0, XK_Down, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {NULL, XK_ANY_MOD, XK_Return, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &return_key},
       -        {NULL, 0, XK_Delete, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &delete_key},
       -        {NULL, 0, XK_Escape, NORMAL|VISUAL|INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &escape_key},
       -        {"i",  0, 0, NORMAL|VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_insert},
       -        {"h",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left},
       -        {"l",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right},
       -        {"j",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {"k",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up},
       -        {"h",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left},
       -        {"t",  ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &toggle_hard_line_based},
       -        {NULL,  0, XK_space, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right},
       -        {"j",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {"n",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {"p",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up},
       -        {"0",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_beginning},
       -        {"0",  0, 0, NORMAL|VISUAL, KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &push_0},
       -        {"1",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_1},
       -        {"2",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_2},
       -        {"3",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_3},
       -        {"4",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_4},
       -        {"5",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_5},
       -        {"6",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_6},
       -        {"7",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_7},
       -        {"8",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_8},
       -        {"9",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_9},
       -        {"x",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_forwards},
       -        {"X",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_backwards},
       -        {"d",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &delete},
       -        {"y",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &yank},
       -        {"Y",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &yank_lines},
       -        {"c",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &change},
       -        {"v",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_visual},
       -        {"o",  0, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &switch_selection_end},
       -        {"c",  ControlMask, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clipcopy},
       -        {"v",  ControlMask, 0, INSERT, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clippaste},
       -        {"g",  ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &show_line},
       -        {":",  0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &enter_commandedit},
       -        {"?",  0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_backward},
       -        {"/",  0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_forward},
       -        {"n",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_next},
       -        {"N",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_prev},
       -        {"u",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &undo},
       -        {"U",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &redo},
       -        {".",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &repeat_command}, /* FIXME: only allow after finished key sequence */
       -        {"z",  ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &undo},
       -        {"y",  ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &redo}, /* FIXME: this is confusing with ctrl+y in normal mode */
       -        {"b",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_up},
       -        {"f",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_down},
       -        {"e",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_down},
       -        {"y",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_up},
       -        {"d",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_down},
       -        {"u",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_up},
       -        {"$",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_eol},
       -        {"w",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word},
       -        {"e",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word_end},
       -        {"W",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword},
       -        {"E",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword_end},
       -        {"b",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_word},
       -        {"B",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_bigword},
       -        {"G",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_line},
       -        {"J",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &join_lines},
       -        {"I",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &insert_at_beginning},
       -        {"p",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal},
       -        {"P",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal_backwards},
       -        {"A",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_eol},
       -        {"a",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_cursor},
       -        {"O",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_above},
       -        {"o",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_below},
       -        {"m",  0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &mark_line},
       -        {"'",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &jump_to_mark},
       -        {"C",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &change_to_eol},
       -        {"D",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &delete_to_eol},
       -        {"r",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &replace},
       -        {"^",  0, 0, NORMAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_first_non_ws},
       -        {"t",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_forwards},
       -        {"T",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_backwards},
       -        {"f",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_forwards},
       -        {"F",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_backwards},
       -        {"", 0, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &insert_mode_insert_text}
       -};
       -
       -static struct key keys_de[] = {
       -        {NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &backspace},
       -        {NULL, 0, XK_Left, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left},
       -        {NULL, 0, XK_Right, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right},
       -        {NULL, 0, XK_Up, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up},
       -        {NULL, 0, XK_Down, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {NULL, XK_ANY_MOD, XK_Return, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &return_key},
       -        {NULL, 0, XK_Delete, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &delete_key},
       -        {NULL, 0, XK_Escape, NORMAL|VISUAL|INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &escape_key},
       -        {"i",  0, 0, NORMAL|VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_insert},
       -        {"h",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left},
       -        {"l",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right},
       -        {"j",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {"k",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up},
       -        {"h",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left},
       -        {"t",  ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &toggle_hard_line_based},
       -        {NULL,  0, XK_space, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right},
       -        {"j",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {"n",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {"p",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up},
       -        {"0",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_beginning},
       -        {"0",  0, 0, NORMAL|VISUAL, KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &push_0},
       -        {"1",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_1},
       -        {"2",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_2},
       -        {"3",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_3},
       -        {"4",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_4},
       -        {"5",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_5},
       -        {"6",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_6},
       -        {"7",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_7},
       -        {"8",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_8},
       -        {"9",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_9},
       -        {"x",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_forwards},
       -        {"X",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_backwards},
       -        {"d",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &delete},
       -        {"z",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &yank},
       -        {"Z",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &yank_lines},
       -        {"c",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &change},
       -        {"v",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_visual},
       -        {"o",  0, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &switch_selection_end},
       -        {"c",  ControlMask, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clipcopy},
       -        {"v",  ControlMask, 0, INSERT, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clippaste},
       -        {"g",  ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &show_line},
       -        {"Ö",  0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &enter_commandedit},
       -        {"_",  0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_backward},
       -        {"-",  0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_forward},
       -        {"n",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_next},
       -        {"N",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_prev},
       -        {"u",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &undo},
       -        {"U",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &redo},
       -        {".",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &repeat_command},
       -        {"y",  ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &undo},
       -        {"z",  ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &redo},
       -        {"b",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_up},
       -        {"f",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_down},
       -        {"e",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_down},
       -        {"z",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_up},
       -        {"d",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_down},
       -        {"u",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_up},
       -        {"$",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_eol},
       -        {"w",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word},
       -        {"e",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word_end},
       -        {"W",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword},
       -        {"E",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword_end},
       -        {"b",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_word},
       -        {"B",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_bigword},
       -        {"G",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_line},
       -        {"J",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &join_lines},
       -        {"I",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &insert_at_beginning},
       -        {"p",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal},
       -        {"P",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal_backwards},
       -        {"A",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_eol},
       -        {"a",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_cursor},
       -        {"O",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_above},
       -        {"o",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_below},
       -        {"m",  0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &mark_line},
       -        {"ä",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &jump_to_mark},
       -        {"C",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &change_to_eol},
       -        {"D",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &delete_to_eol},
       -        {"r",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &replace},
       -        {"&",  0, 0, NORMAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_first_non_ws},
       -        {"t",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_forwards},
       -        {"T",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_backwards},
       -        {"f",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_forwards},
       -        {"F",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_backwards},
       -        {"", 0, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &insert_mode_insert_text}
       -};
       -
       -static struct key keys_ur[] = {
       -        {NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &backspace},
       -        {NULL, 0, XK_Left, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left},
       -        {NULL, 0, XK_Right, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right},
       -        {NULL, 0, XK_Up, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up},
       -        {NULL, 0, XK_Down, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {NULL, XK_ANY_MOD, XK_Return, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &return_key},
       -        {NULL, 0, XK_Delete, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &delete_key},
       -        {NULL, 0, XK_Escape, NORMAL|VISUAL|INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &escape_key},
       -        {"ی",  0, 0, NORMAL|VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_insert},
       -        {"ح",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left},
       -        {"ل",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right},
       -        {"ج",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {"ک",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up},
       -        {"ح",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left},
       -        {"ت",  ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &toggle_hard_line_based},
       -        {NULL,  0, XK_space, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right},
       -        {"ج",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {"ن",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {"پ",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up},
       -        {"0",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_beginning},
       -        {"0",  0, 0, NORMAL|VISUAL, KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &push_0},
       -        {"1",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_1},
       -        {"2",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_2},
       -        {"3",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_3},
       -        {"4",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_4},
       -        {"5",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_5},
       -        {"6",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_6},
       -        {"7",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_7},
       -        {"8",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_8},
       -        {"9",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_9},
       -        {"ش",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_forwards},
       -        {"ژ",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_backwards},
       -        {"د",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &delete},
       -        {"ے",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &yank},
       -        {"َ",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &yank_lines},
       -        {"چ",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &change},
       -        {"ط",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_visual},
       -        {"ہ",  0, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &switch_selection_end},
       -        {"چ",  ControlMask, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clipcopy},
       -        {"ط",  ControlMask, 0, INSERT, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clippaste},
       -        {"گ",  ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &show_line},
       -        {":",  0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &enter_commandedit},
       -        {"؟",  0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_backward},
       -        {"/",  0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_forward},
       -        {"ن",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_next},
       -        {"ں",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_prev},
       -        {"ء",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &undo},
       -        {"ئ",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &redo},
       -        {"۔",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &repeat_command},
       -        {"ز",  ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &undo},
       -        {"َ",  ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &redo},
       -        {"ب",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_up},
       -        {"ف",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_down},
       -        {"ع",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_down},
       -        {"ے",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_up},
       -        {"د",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_down},
       -        {"ء",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_up},
       -        {"$",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_eol},
       -        {"و",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word},
       -        {"ع",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word_end},
       -        {"ؤ",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword},
       -        {"ٰ",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword_end},
       -        {"ب",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_word},
       -        {".",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_bigword},
       -        {"غ",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_line},
       -        {"ض",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &join_lines},
       -        {"ِ",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &insert_at_beginning},
       -        {"پ",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal},
       -        {"ُ",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal_backwards},
       -        {"آ",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_eol},
       -        {"ا",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_cursor},
       -        {"ۃ",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_above},
       -        {"ہ",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_below},
       -        {"م",  0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &mark_line},
       -        {"'",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &jump_to_mark},
       -        {"ث",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &change_to_eol},
       -        {"ڈ",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &delete_to_eol},
       -        {"ر",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &replace},
       -        {"^",  0, 0, NORMAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_first_non_ws},
       -        {"ت",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_forwards},
       -        {"ٹ",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_backwards},
       -        {"ف",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_forwards},
       -        {"ّ",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_backwards},
       -        {"", 0, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &insert_mode_insert_text}
       -};
       -
       -static struct key keys_hi[] = {
       -        {NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &backspace},
       -        {NULL, 0, XK_Left, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left},
       -        {NULL, 0, XK_Right, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right},
       -        {NULL, 0, XK_Up, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up},
       -        {NULL, 0, XK_Down, VISUAL|INSERT|NORMAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {NULL, XK_ANY_MOD, XK_Return, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &return_key},
       -        {NULL, 0, XK_Delete, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &delete_key},
       -        {NULL, 0, XK_Escape, NORMAL|VISUAL|INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &escape_key},
       -        {"ि",  0, 0, NORMAL|VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_insert},
       -        {"ह",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left},
       -        {"ल",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right},
       -        {"ज",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {"क",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up},
       -        {"ह",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_left},
       -        {"त",  ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &toggle_hard_line_based},
       -        {NULL,  0, XK_space, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_right},
       -        {"ज",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {"न",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_down},
       -        {"प",  ControlMask, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &cursor_up},
       -        {"0",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_beginning},
       -        {"0",  0, 0, NORMAL|VISUAL, KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &push_0},
       -        {"1",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_1},
       -        {"2",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_2},
       -        {"3",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_3},
       -        {"4",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_4},
       -        {"5",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_5},
       -        {"6",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_6},
       -        {"7",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_7},
       -        {"8",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_8},
       -        {"9",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_NUMBERALLOWED, KEY_ENSURE_CURSOR_SHOWN, &push_9},
       -        {"्",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_forwards},
       -        {"ॉ",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &delete_chars_backwards},
       -        {"द",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &delete},
       -        {"य",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &yank},
       -        {"ञ",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &yank_lines},
       -        {"च",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &change},
       -        {"ड",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &enter_visual},
       -        {"ो",  0, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &switch_selection_end},
       -        {"च",  ControlMask, 0, VISUAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clipcopy},
       -        {"ड",  ControlMask, 0, INSERT, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &clippaste},
       -        {"ग",  ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_NONE, &show_line},
       -        {":",  0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &enter_commandedit},
       -        {"?",  0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_backward},
       -        {"/",  0, 0, NORMAL, KEY_NONE, KEY_NONE, &enter_searchedit_forward},
       -        {"न",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_next},
       -        {"ण",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &key_search_prev},
       -        {"ु",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &undo},
       -        {"ू",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &redo},
       -        {".",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &repeat_command},
       -        {"श",  ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &undo},
       -        {"य",  ControlMask, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &redo},
       -        {"ब",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_up},
       -        {"ट",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &screen_down},
       -        {"े",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_down},
       -        {"य",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_with_cursor_up},
       -        {"द",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_down},
       -        {"ु",  ControlMask, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &scroll_lines_up},
       -        {"$",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_eol},
       -        {"व",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word},
       -        {"े",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_word_end},
       -        {"ॐ",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword},
       -        {"ै",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &next_bigword_end},
       -        {"ब",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_word},
       -        {"भ",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &prev_bigword},
       -        {"घ",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &move_to_line},
       -        {"झ",  0, 0, NORMAL, KEY_NONE|KEY_NUMBER, KEY_ENSURE_CURSOR_SHOWN, &join_lines},
       -        {"ी",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &insert_at_beginning},
       -        {"प",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal},
       -        {"फ",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &paste_normal_backwards},
       -        {"आ",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_eol},
       -        {"ा",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_after_cursor},
       -        {"ौ",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_above},
       -        {"ो",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &append_line_below},
       -        {"म",  0, 0, NORMAL|VISUAL, KEY_NONE, KEY_NONE, &mark_line},
       -        {"'",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &jump_to_mark},
       -        {"छ",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &change_to_eol},
       -        {"ध",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &delete_to_eol},
       -        {"र",  0, 0, NORMAL, KEY_NONE, KEY_ENSURE_CURSOR_SHOWN, &replace},
       -        {"^",  0, 0, NORMAL, KEY_NONE|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &cursor_to_first_non_ws},
       -        {"त",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_forwards},
       -        {"थ",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_next_char_backwards},
       -        {"ट",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_forwards},
       -        {"ठ",  0, 0, NORMAL|VISUAL, KEY_NONE|KEY_NUMBER|KEY_MOTIONALLOWED, KEY_ENSURE_CURSOR_SHOWN, &find_char_backwards},
       -        {"", 0, 0, INSERT, KEY_ANY, KEY_ENSURE_CURSOR_SHOWN, &insert_mode_insert_text}
       -};
       -
       -GEN_KEY_ARRAY(struct key, keys_en, keys_de, keys_ur, keys_hi);
   DIR diff --git a/keys_command.c b/keys_command.c
       t@@ -1,3 +1,4 @@
       +/* FIXME: remove CHECK_VIEW_LOCKED when it is confirmed that the new system works properly */
        /* FIXME: Parse commands properly and allow combinations of commands */
        #include <stdio.h>
        #include <ctype.h>
       t@@ -18,7 +19,6 @@
        #include "txtbuf.h"
        #include "undo.h"
        #include "cache.h"
       -#include "theme.h"
        #include "window.h"
        #include "buffer.h"
        #include "view.h"
       t@@ -27,9 +27,113 @@
        #include "util.h"
        
        #include "keys.h"
       -#include "keys_config.h"
        #include "keys_command.h"
       -#include "keys_command_config.h"
       +#include "configparser.h"
       +
       +/*************************************************************************
       + * Declarations for all functions that can be used in the configuration. *
       + *************************************************************************/
       +
       +static int substitute_yes(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int substitute_yes_all(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int substitute_no(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int substitute_no_all(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int edit_insert_text(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int edit_cursor_left(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int edit_cursor_right(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int edit_cursor_to_end(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int edit_backspace(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int edit_delete(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int edit_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int edit_prevcommand(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int edit_nextcommand(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int edit_prevsearch(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int edit_nextsearch(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int editsearch_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int editsearchb_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +static int edit_discard(ledit_view *view, char *key_text, size_t len, size_t lang_index);
       +
       +static int create_view(ledit_view *view, char *cmd, size_t l1, size_t l2);
       +static int close_view(ledit_view *view, char *cmd, size_t l1, size_t l2);
       +static int handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2);
       +static int handle_quit(ledit_view *view, char *cmd, size_t l1, size_t l2);
       +static int handle_write_quit(ledit_view *view, char *cmd, size_t l1, size_t l2);
       +static int handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2);
       +
       +/***********************************************
       + * String-function mapping for config parsing. *
       + ***********************************************/
       +
       +typedef enum {
       +        KEY_FLAG_NONE = 0,
       +        KEY_FLAG_JUMP_TO_CURSOR = 1,
       +        KEY_FLAG_LOCK_ALLOWED = 2
       +} command_key_cb_flags;
       +
       +typedef enum {
       +        CMD_FLAG_NONE = 0,
       +        CMD_FLAG_OPTIONAL_RANGE = 1,
       +        CMD_FLAG_LOCK_ALLOWED = 2
       +} command_cb_flags;
       +
       +typedef int (*command_key_cb_func)(ledit_view *, char *, size_t, size_t);
       +struct command_key_cb {
       +        char *text;
       +        command_key_cb_func func;
       +        command_key_cb_flags flags;
       +        command_mode allowed_modes;
       +};
       +
       +typedef int (*command_cb_func)(ledit_view *view, char *cmd, size_t l1, size_t l2);
       +struct command_cb {
       +        char *text;
       +        command_cb_func func;
       +        command_cb_flags flags;
       +};
       +
       +int
       +command_key_cb_modemask_is_valid(command_key_cb *cb, command_mode modes) {
       +        return (~cb->allowed_modes & modes) == 0;
       +}
       +
       +static command_key_cb command_key_cb_map[] = {
       +        {"edit-backspace", &edit_backspace, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-cursor-left", &edit_cursor_left, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-cursor-right", &edit_cursor_right, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-cursor-to-beginning", &edit_cursor_to_beginning, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-cursor-to-end", &edit_cursor_to_end, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-delete", &edit_delete, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-discard", &edit_discard, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-insert-text", &edit_insert_text, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-next-command", &edit_nextcommand, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT},
       +        {"edit-next-search", &edit_nextsearch, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-previous-command", &edit_prevcommand, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT},
       +        {"edit-previous-search", &edit_prevsearch, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-submit", &edit_submit, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT},
       +        {"edit-submit-backwards-search", &editsearchb_submit, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCHB},
       +        {"edit-submit-search", &editsearch_submit, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCH},
       +        {"substitute-no", &substitute_no, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, CMD_SUBSTITUTE},
       +        {"substitute-no-all", &substitute_no_all, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, CMD_SUBSTITUTE},
       +        {"substitute-yes", &substitute_yes, KEY_FLAG_JUMP_TO_CURSOR, CMD_SUBSTITUTE},
       +        {"substitute-yes-all", &substitute_yes_all, KEY_FLAG_JUMP_TO_CURSOR, CMD_SUBSTITUTE},
       +};
       +
       +static command_cb command_cb_map[] = {
       +        {"close-view", &close_view, CMD_FLAG_LOCK_ALLOWED},
       +        {"create-view", &create_view, CMD_FLAG_LOCK_ALLOWED},
       +        {"quit", &handle_quit, CMD_FLAG_LOCK_ALLOWED},
       +        {"substitute", &handle_substitute, CMD_FLAG_OPTIONAL_RANGE},
       +        {"write", &handle_write, CMD_FLAG_OPTIONAL_RANGE|CMD_FLAG_LOCK_ALLOWED},
       +        {"write-quit", &handle_write_quit, CMD_FLAG_OPTIONAL_RANGE|CMD_FLAG_LOCK_ALLOWED},
       +};
       +
       +GEN_CB_MAP_HELPERS(command_key_cb_map, command_key_cb, text)
       +GEN_CB_MAP_HELPERS(command_cb_map, command_cb, text)
       +
       +/***************************************************
       + * General global variables and utility functions. *
       + ***************************************************/
        
        static struct {
                char *search;
       t@@ -99,20 +203,17 @@ view_locked_error(ledit_view *view) {
        
        #define CHECK_VIEW_LOCKED(view) if (view->lock_text) return view_locked_error(view)
        
       -static int create_view(ledit_view *view, char *cmd, size_t l1, size_t l2);
       -static int close_view(ledit_view *view, char *cmd, size_t l1, size_t l2);
       -static int handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2);
       -static int handle_quit(ledit_view *view, char *cmd, size_t l1, size_t l2);
       -static int handle_write_quit(ledit_view *view, char *cmd, size_t l1, size_t l2);
       -static int handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2);
       +/********************************************************************
       + * Functions for handling commands typed in line editor (:w, etc.). *
       + ********************************************************************/
       +
        static int parse_range(
       -    ledit_view *view, char *cmd, size_t len, char **cmd_ret,
       +    ledit_view *view, char *cmd, size_t len, size_t *idx_ret,
            size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid,
            char **errstr_ret
        );
       -static int handle_cmd(ledit_view *view, char *cmd, size_t len);
       +static int handle_cmd(ledit_view *view, char *cmd, size_t len, size_t lang_index);
        
       -/* FIXME: remove command name before passing to handlers */
        static int
        handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) {
                (void)l1;
       t@@ -120,7 +221,6 @@ handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) {
                /* FIXME: allow writing only part of file */
                char *filename = view->buffer->filename;
                int stored = 1;
       -        cmd++; /* remove 'w' */
                int force = 0;
                if (*cmd == '!') {
                        force = 1;
       t@@ -132,7 +232,7 @@ handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) {
                        stored = 0;
                }
                /* FIXME: file locks */
       -        char *errstr;
       +        char *errstr = NULL;
                if (filename) {
                        struct stat sb;
                        /* There technically is a race between checking stat and actually
       t@@ -176,7 +276,6 @@ static int
        handle_quit(ledit_view *view, char *cmd, size_t l1, size_t l2) {
                (void)l1;
                (void)l2;
       -        cmd++;
                int force = 0;
                if (*cmd == '!')
                        force = 1;
       t@@ -194,7 +293,7 @@ create_view(ledit_view *view, char *cmd, size_t l1, size_t l2) {
                (void)cmd;
                (void)l1;
                (void)l2;
       -        buffer_add_view(view->buffer, view->theme, view->mode, view->cur_line, view->cur_index, view->display_offset);
       +        buffer_add_view(view->buffer, view->mode, view->cur_line, view->cur_index, view->display_offset);
                return 0;
        }
        
       t@@ -205,7 +304,6 @@ close_view(ledit_view *view, char *cmd, size_t l1, size_t l2) {
                (void)l2;
                /* FIXME: This will lead to problems if I add something that
                   requires access to the view after the command is handled. */
       -        cmd++;
                int force = 0;
                if (*cmd == '!')
                        force = 1;
       t@@ -220,7 +318,7 @@ close_view(ledit_view *view, char *cmd, size_t l1, size_t l2) {
        
        static int
        handle_write_quit(ledit_view *view, char *cmd, size_t l1, size_t l2) {
       -        handle_write(view, cmd + 1, l1, l2);
       +        handle_write(view, cmd, l1, l2);
                ledit_cleanup();
                exit(0);
                return 0;
       t@@ -328,7 +426,6 @@ substitute_all_remaining(ledit_view *view) {
        static int
        handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) {
                CHECK_VIEW_LOCKED(view);
       -        cmd++; /* remove 's' at beginning */
                size_t len = strlen(cmd);
                char *sep = NULL;
                if (len == 0) goto error;
       t@@ -399,34 +496,19 @@ enum cmd_type {
                CMD_OPTIONAL_RANGE
        };
        
       -static const struct {
       -        char *cmd;
       -        enum cmd_type type;
       -        int (*handler)(ledit_view *view, char *cmd, size_t l1, size_t l2);
       -} cmds[] = {
       -        {"wq", CMD_OPTIONAL_RANGE, &handle_write_quit},
       -        {"w", CMD_OPTIONAL_RANGE, &handle_write},
       -        {"q", CMD_NORMAL, &handle_quit},
       -        {"v", CMD_NORMAL, &create_view},
       -        {"c", CMD_NORMAL, &close_view},
       -        {"s", CMD_OPTIONAL_RANGE, &handle_substitute}
       -};
       -
        /*
        . current line
        $ last line
        % all lines
        */
        
       -/* FIXME: ACTUALLY USE LEN!!! */
        /* NOTE: Marks are only recognized here if they are one unicode character! */
        /* NOTE: Only the line range of the selection is used at the moment. */
        static int
        parse_range(
       -    ledit_view *view, char *cmd, size_t len, char **cmd_ret,
       +    ledit_view *view, char *cmd, size_t len, size_t *idx_ret,
            size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid,
            char **errstr_ret) {
       -        (void)len;
                *errstr_ret = "";
                enum {
                        START_LINENO = 1,
       t@@ -437,8 +519,10 @@ parse_range(
                size_t l1 = 0, l2 = 0;
                *l1_valid = 0;
                *l2_valid = 0;
       -        char *c = cmd;
       -        while (*c != '\0') {
       +        size_t cur = 0;
       +        char *c;
       +        while (cur < len) {
       +                c = &cmd[cur];
                        if (isdigit(*c)) {
                                if (s & IN_LINENO) {
                                        size_t *final = &l2;
       t@@ -466,22 +550,20 @@ parse_range(
                                        s = IN_LINENO;
                                }
                        } else if (*c == '\'' && (s & START_LINENO)) {
       -                        if (c[1] == '\0' || c[2] == '\0') {
       +                        if (len - cur <= 2) {
                                        *errstr_ret = "Invalid range";
                                        return 1;
                                }
       -                        char *aftermark = next_utf8(c + 2);
       -                        size_t marklen = aftermark - (c + 1);
       +                        size_t aftermark_idx = next_utf8_len(c + 2, len - cur - 2);
       +                        size_t marklen = aftermark_idx - (cur + 1);
                                size_t l, b;
       -                        if (!strncmp(c + 1, "<", strlen("<")) && view->sel_valid) {
       +                        if (*(c + 1) == '<' && view->sel_valid) {
                                        l = view->sel.line1 < view->sel.line2 ? view->sel.line1 : view->sel.line2;
       -                        } else if (!strncmp(c + 1, ">", strlen(">")) && view->sel_valid) {
       +                        } else if (*(c + 1) == '>' && view->sel_valid) {
                                        l = view->sel.line1 > view->sel.line2 ? view->sel.line1 : view->sel.line2;
       -                        } else {
       -                                if (buffer_get_mark(view->buffer, c + 1, marklen, &l, &b)) {
       -                                        *errstr_ret = "Invalid mark";
       -                                        return 1;
       -                                }
       +                        } else if (buffer_get_mark(view->buffer, c + 1, marklen, &l, &b)) {
       +                                *errstr_ret = "Invalid mark";
       +                                return 1;
                                }
                                if (!*l1_valid) {
                                        l1 = l + 1;
       t@@ -490,7 +572,7 @@ parse_range(
                                        l2 = l + 1;
                                        *l2_valid = 1;
                                }
       -                        c = aftermark;
       +                        cur = aftermark_idx;
                                s = 0;
                                continue;
                        } else if (*c == ',' && !(s & START_RANGE)) {
       t@@ -505,7 +587,7 @@ parse_range(
                                        l1 = 1;
                                        l2 = view->lines_num;
                                        *l1_valid = *l2_valid = 1;
       -                                c++;
       +                                cur++;
                                        s = 0;
                                        break;
                                } else {
       t@@ -543,7 +625,7 @@ parse_range(
                        } else {
                                break;
                        }
       -                c++;
       +                cur++;
                }
                if ((!*l1_valid || !*l2_valid) && !(s & START_RANGE)) {
                        *errstr_ret = "Invalid range";
       t@@ -553,7 +635,7 @@ parse_range(
                        *errstr_ret = "Invalid line number in range";
                        return 1;
                }
       -        *cmd_ret = c;
       +        *idx_ret = cur;
                /* ranges are given 1-indexed by user */
                *line1_ret = l1 - 1;
                *line2_ret = l2 - 1;
       t@@ -561,36 +643,58 @@ parse_range(
        }
        
        static int
       -handle_cmd(ledit_view *view, char *cmd, size_t len) {
       +handle_cmd(ledit_view *view, char *cmd, size_t len, size_t lang_index) {
                if (len < 1)
                        return 0;
                push_cmdhistory(cmd, len);
       -        char *c;
                size_t l1, l2;
                int l1_valid, l2_valid;
                char *errstr;
       -        if (parse_range(view, cmd, len, &c, &l1, &l2, &l1_valid, &l2_valid, &errstr)) {
       +        size_t start_idx;
       +        if (parse_range(view, cmd, len, &start_idx, &l1, &l2, &l1_valid, &l2_valid, &errstr)) {
                        window_show_message(view->window, errstr, -1);
                        return 0;
                }
       +        if (start_idx >= len) {
       +                window_show_message(view->window, "Invalid command", -1);
       +                return 0;
       +        }
       +        size_t rem_len = len - start_idx;
       +        char *cur_str = cmd + start_idx;
                int range_given = l1_valid && l2_valid;
                if (!range_given) {
                        l1 = l2 = view->cur_line;
                }
       -        for (size_t i = 0; i < LENGTH(cmds); i++) {
       -                if (!strncmp(cmds[i].cmd, c, strlen(cmds[i].cmd)) &&
       -                    (!range_given || cmds[i].type == CMD_OPTIONAL_RANGE)) {
       -                        return cmds[i].handler(view, c, l1, l2);
       +        command_array *cur_cmds = config_get_commands(lang_index);
       +        char *cmd_text;
       +        size_t text_len;
       +        for (size_t i = 0; i < cur_cmds->num_cmds; i++) {
       +                cmd_text = cur_cmds->cmds[i].text;
       +                text_len = strlen(cmd_text);
       +                if (rem_len < text_len)
       +                        continue;
       +                if (!strncmp(cmd_text, cur_str, text_len)) {
       +                        if (range_given && !(cur_cmds->cmds[i].cb->flags & CMD_OPTIONAL_RANGE)) {
       +                                window_show_message(view->window, "Command does not take range", -1);
       +                                return 0;
       +                        } else if (view->lock_text && !(cur_cmds->cmds[i].cb->flags & CMD_FLAG_LOCK_ALLOWED)) {
       +                                window_show_message(view->window, view->lock_text, -1);
       +                                return 0;
       +                        }
       +                        return cur_cmds->cmds[i].cb->func(view, cur_str + text_len, l1, l2);
                        }
                }
                window_show_message(view->window, "Invalid command", -1);
                return 0;
        }
        
       +/***********************************
       + * Functions called on keypresses. *
       + ***********************************/
       +
        static int
       -substitute_yes(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +substitute_yes(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                substitute_single(view);
                buffer_recalc_line(view->buffer, sub_state.line);
                if (!sub_state.global) {
       t@@ -606,18 +710,16 @@ substitute_yes(ledit_view *view, char *key_text, size_t len) {
        }
        
        static int
       -substitute_yes_all(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +substitute_yes_all(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                substitute_all_remaining(view);
                show_num_substituted(view);
                return 0;
        }
        
        static int
       -substitute_no(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +substitute_no(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                if (!sub_state.global) {
                        sub_state.line++;
                        sub_state.byte = 0;
       t@@ -631,16 +733,16 @@ substitute_no(ledit_view *view, char *key_text, size_t len) {
        }
        
        static int
       -substitute_no_all(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +substitute_no_all(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                buffer_unlock_all_views(view->buffer);
                show_num_substituted(view);
                return 0;
        }
        
        static int
       -edit_insert_text(ledit_view *view, char *key_text, size_t len) {
       +edit_insert_text(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)lang_index;
                /* FIXME: overflow */
                window_insert_bottom_bar_text(view->window, key_text, len);
                window_set_bottom_bar_cursor(
       t@@ -650,57 +752,50 @@ edit_insert_text(ledit_view *view, char *key_text, size_t len) {
        }
        
        static int
       -edit_cursor_to_end(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +edit_cursor_to_end(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                window_bottom_bar_cursor_to_end(view->window);
                return 1;
        }
        
        static int
       -edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                window_bottom_bar_cursor_to_beginning(view->window);
                return 1;
        }
        
        static int
       -edit_cursor_left(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +edit_cursor_left(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                window_move_bottom_bar_cursor(view->window, -1);
                return 1;
        }
        
        static int
       -edit_cursor_right(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +edit_cursor_right(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                window_move_bottom_bar_cursor(view->window, 1);
                return 1;
        }
        
        static int
       -edit_backspace(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +edit_backspace(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                window_delete_bottom_bar_char(view->window, -1);
                return 1;
        }
        
        static int
       -edit_delete(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +edit_delete(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                window_delete_bottom_bar_char(view->window, 1);
                return 1;
        }
        
        static int
       -edit_submit(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +edit_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len;
                window_set_bottom_bar_text_shown(view->window, 0);
                char *text = window_get_bottom_bar_text(view->window);
                int min_pos = window_get_bottom_bar_min_pos(view->window);
       t@@ -714,15 +809,14 @@ edit_submit(ledit_view *view, char *key_text, size_t len) {
                }
                /* FIXME: this is hacky */
                char *cmd = ledit_strndup(text, textlen);
       -        int ret = handle_cmd(view, cmd, (size_t)textlen);
       +        int ret = handle_cmd(view, cmd, (size_t)textlen, lang_index);
                free(cmd);
                return ret;
        }
        
        static int
       -edit_prevcommand(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +edit_prevcommand(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                if (cmdhistory.cur > 0) {
                        cmdhistory.cur--;
                        window_set_bottom_bar_realtext(view->window, cmdhistory.cmds[cmdhistory.cur], -1);
       t@@ -732,9 +826,8 @@ edit_prevcommand(ledit_view *view, char *key_text, size_t len) {
        }
        
        static int
       -edit_nextcommand(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +edit_nextcommand(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                if (cmdhistory.len > 0 && cmdhistory.cur < cmdhistory.len - 1) {
                        cmdhistory.cur++;
                        window_set_bottom_bar_realtext(view->window, cmdhistory.cmds[cmdhistory.cur], -1);
       t@@ -747,9 +840,8 @@ edit_nextcommand(ledit_view *view, char *key_text, size_t len) {
        }
        
        static int
       -edit_prevsearch(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +edit_prevsearch(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                if (searchhistory.cur > 0) {
                        searchhistory.cur--;
                        window_set_bottom_bar_realtext(view->window, searchhistory.cmds[searchhistory.cur], -1);
       t@@ -759,9 +851,8 @@ edit_prevsearch(ledit_view *view, char *key_text, size_t len) {
        }
        
        static int
       -edit_nextsearch(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +edit_nextsearch(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                if (searchhistory.len > 0 && searchhistory.cur < searchhistory.len - 1) {
                        searchhistory.cur++;
                        window_set_bottom_bar_realtext(view->window, searchhistory.cmds[searchhistory.cur], -1);
       t@@ -795,9 +886,8 @@ search_prev(ledit_view *view) {
        }
        
        static int
       -editsearch_submit(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +editsearch_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                window_set_bottom_bar_text_shown(view->window, 0);
                char *text = window_get_bottom_bar_text(view->window);
                int min_pos = window_get_bottom_bar_min_pos(view->window);
       t@@ -818,9 +908,8 @@ editsearch_submit(ledit_view *view, char *key_text, size_t len) {
        }
        
        static int
       -editsearchb_submit(ledit_view *view, char *key_text, size_t len) {
       -        (void)key_text;
       -        (void)len;
       +editsearchb_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)key_text; (void)len; (void)lang_index;
                window_set_bottom_bar_text_shown(view->window, 0);
                char *text = window_get_bottom_bar_text(view->window);
                int min_pos = window_get_bottom_bar_min_pos(view->window);
       t@@ -841,9 +930,8 @@ editsearchb_submit(ledit_view *view, char *key_text, size_t len) {
        }
        
        static int
       -edit_discard(ledit_view *view, char *key_text, size_t len) {
       -        (void)view;
       -        (void)key_text;
       +edit_discard(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
       +        (void)view; (void)key_text; (void)lang_index;
                (void)len;
                window_set_bottom_bar_text_shown(view->window, 0);
                return 0;
       t@@ -854,28 +942,46 @@ command_key_handler(ledit_view *view, XEvent *event, int lang_index) {
                char buf[64];
                KeySym sym;
                int n;
       -        struct key *cur_keys = keys[lang_index].keys;
       -        int num_keys = keys[lang_index].num_keys;
       +        command_key_array *cur_keys = config_get_command_keys(lang_index);
       +        size_t num_keys = cur_keys->num_keys;
                unsigned int key_state = event->xkey.state;
                preprocess_key(view->window, event, &sym, buf, sizeof(buf), &n);
       -        int grabkey = 1;
       -        for (int i = 0; i < num_keys; i++) {
       -                if (cur_keys[i].text) {
       +        int grabkey = 1, found = 0;
       +        command_key_cb_flags flags = KEY_FLAG_NONE;
       +        for (size_t i = 0; i < num_keys; i++) {
       +                if (cur_keys->keys[i].text) {
                                if (n > 0 &&
       -                            (cur_keys[i].type == view->cur_command_type) &&
       -                            ((!strncmp(cur_keys[i].text, buf, n) &&
       -                              match_key(cur_keys[i].mods, key_state & ~ShiftMask)) ||
       -                             cur_keys[i].text[0] == '\0')) {
       -                                grabkey = cur_keys[i].func(view, buf, (size_t)n);
       +                            (cur_keys->keys[i].modes & view->cur_command_type) &&
       +                            ((!strncmp(cur_keys->keys[i].text, buf, n) &&
       +                              match_key(cur_keys->keys[i].mods, key_state & ~ShiftMask)) ||
       +                             cur_keys->keys[i].text[0] == '\0')) {
       +                                flags = cur_keys->keys[i].cb->flags;
       +                                if (!(flags & KEY_FLAG_LOCK_ALLOWED) && view->lock_text) {
       +                                        (void)view_locked_error(view);
       +                                        grabkey = 0;
       +                                        break;
       +                                }
       +                                grabkey = cur_keys->keys[i].cb->func(view, buf, (size_t)n, lang_index);
       +                                found = 1;
       +                                break;
       +                        }
       +                } else if ((cur_keys->keys[i].modes & view->cur_command_type) &&
       +                           (cur_keys->keys[i].keysym == sym) &&
       +                           (match_key(cur_keys->keys[i].mods, key_state))) {
       +                        flags = cur_keys->keys[i].cb->flags;
       +                        if (!(flags & KEY_FLAG_LOCK_ALLOWED) && view->lock_text) {
       +                                (void)view_locked_error(view);
       +                                grabkey = 0;
                                        break;
                                }
       -                } else if ((cur_keys[i].type == view->cur_command_type) &&
       -                           (cur_keys[i].keysym == sym) &&
       -                           (match_key(cur_keys[i].mods, key_state))) {
       -                        grabkey = cur_keys[i].func(view, buf, (size_t)n);
       +                        grabkey = cur_keys->keys[i].cb->func(view, buf, (size_t)n, lang_index);
       +                        found = 1;
                                break;
                        }
                }
       +        if (found && (flags & KEY_FLAG_JUMP_TO_CURSOR))
       +                view_ensure_cursor_shown(view);
       +        /* FIXME: proper error on invalid key */
                if (grabkey)
                        return (struct action){ACTION_GRABKEY, &command_key_handler};
                else
   DIR diff --git a/keys_command.h b/keys_command.h
       t@@ -3,6 +3,14 @@
        
        #include <X11/Xlib.h>
        #include "view.h"
       +#include "uglycrap.h"
       +
       +typedef struct command_key_cb command_key_cb;
       +typedef struct command_cb command_cb;
       +
       +int command_key_cb_modemask_is_valid(command_key_cb *cb, command_mode modes);
       +command_key_cb *command_key_cb_map_get_entry(char *text, size_t len);
       +command_cb *command_cb_map_get_entry(char *text, size_t len);
        
        /* these are only here so they can also be used by keys_basic */
        void search_next(ledit_view *view);
   DIR diff --git a/keys_command_config.h b/keys_command_config.h
       t@@ -1,196 +0,0 @@
       -/*
       - * These are the keys used by special commands that require a special key
       - * handler. This includes keys used to edit the line entry at the bottom
       - * and keys used for confirmation (e.g. when substituting).
       - */
       -
       -static int substitute_yes(ledit_view *view, char *key_text, size_t len);
       -static int substitute_yes_all(ledit_view *view, char *key_text, size_t len);
       -static int substitute_no(ledit_view *view, char *key_text, size_t len);
       -static int substitute_no_all(ledit_view *view, char *key_text, size_t len);
       -static int edit_insert_text(ledit_view *view, char *key_text, size_t len);
       -static int edit_cursor_left(ledit_view *view, char *key_text, size_t len);
       -static int edit_cursor_right(ledit_view *view, char *key_text, size_t len);
       -static int edit_cursor_to_end(ledit_view *view, char *key_text, size_t len);
       -static int edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len);
       -static int edit_backspace(ledit_view *view, char *key_text, size_t len);
       -static int edit_delete(ledit_view *view, char *key_text, size_t len);
       -static int edit_submit(ledit_view *view, char *key_text, size_t len);
       -static int edit_prevcommand(ledit_view *view, char *key_text, size_t len);
       -static int edit_nextcommand(ledit_view *view, char *key_text, size_t len);
       -static int edit_prevsearch(ledit_view *view, char *key_text, size_t len);
       -static int edit_nextsearch(ledit_view *view, char *key_text, size_t len);
       -static int editsearch_submit(ledit_view *view, char *key_text, size_t len);
       -static int editsearchb_submit(ledit_view *view, char *key_text, size_t len);
       -static int edit_discard(ledit_view *view, char *key_text, size_t len);
       -
       -struct key {
       -        char *text;                                /* for keys that correspond with text */
       -        unsigned int mods;                         /* modifier mask */
       -        KeySym keysym;                             /* for other keys, e.g. arrow keys */
       -        enum ledit_command_type type;              /* substitute, etc. */
       -        int (*func)(ledit_view *, char *, size_t); /* callback function */
       -};
       -
       -/* "" means catch-all, i.e. all keys with text are given to that callback */
       -static struct key keys_en[] = {
       -        {"y", 0, 0, CMD_SUBSTITUTE, &substitute_yes},
       -        {"Y", 0, 0, CMD_SUBSTITUTE, &substitute_yes_all},
       -        {"n", 0, 0, CMD_SUBSTITUTE, &substitute_no},
       -        {"N", 0, 0, CMD_SUBSTITUTE, &substitute_no_all},
       -        {NULL, XK_ANY_MOD, XK_Return, CMD_EDIT, &edit_submit},
       -        {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCH, &editsearch_submit},
       -        {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCHB, &editsearchb_submit},
       -        {NULL, 0, XK_Left, CMD_EDIT, &edit_cursor_left},
       -        {NULL, 0, XK_Left, CMD_EDITSEARCH, &edit_cursor_left},
       -        {NULL, 0, XK_Left, CMD_EDITSEARCHB, &edit_cursor_left},
       -        {NULL, 0, XK_Right, CMD_EDIT, &edit_cursor_right},
       -        {NULL, 0, XK_Right, CMD_EDITSEARCH, &edit_cursor_right},
       -        {NULL, 0, XK_Right, CMD_EDITSEARCHB, &edit_cursor_right},
       -        {NULL, 0, XK_Up, CMD_EDIT, &edit_prevcommand},
       -        {NULL, 0, XK_Up, CMD_EDITSEARCH, &edit_prevsearch},
       -        {NULL, 0, XK_Up, CMD_EDITSEARCHB, &edit_prevsearch},
       -        {NULL, 0, XK_Down, CMD_EDIT, &edit_nextcommand},
       -        {NULL, 0, XK_Down, CMD_EDITSEARCH, &edit_nextsearch},
       -        {NULL, 0, XK_Down, CMD_EDITSEARCHB, &edit_nextsearch},
       -        {NULL, 0, XK_BackSpace, CMD_EDIT, &edit_backspace},
       -        {NULL, 0, XK_BackSpace, CMD_EDITSEARCH, &edit_backspace},
       -        {NULL, 0, XK_BackSpace, CMD_EDITSEARCHB, &edit_backspace},
       -        {NULL, 0, XK_Delete, CMD_EDIT, &edit_delete},
       -        {NULL, 0, XK_Delete, CMD_EDITSEARCH, &edit_delete},
       -        {NULL, 0, XK_Delete, CMD_EDITSEARCHB, &edit_delete},
       -        {NULL, 0, XK_End, CMD_EDIT, &edit_cursor_to_end},
       -        {NULL, 0, XK_End, CMD_EDITSEARCH, &edit_cursor_to_end},
       -        {NULL, 0, XK_End, CMD_EDITSEARCHB, &edit_cursor_to_end},
       -        {NULL, 0, XK_Home, CMD_EDIT, &edit_cursor_to_beginning},
       -        {NULL, 0, XK_Home, CMD_EDITSEARCH, &edit_cursor_to_beginning},
       -        {NULL, 0, XK_Home, CMD_EDITSEARCHB, &edit_cursor_to_beginning},
       -        {NULL, 0, XK_Escape, CMD_EDIT, &edit_discard},
       -        {NULL, 0, XK_Escape, CMD_EDITSEARCH, &edit_discard},
       -        {NULL, 0, XK_Escape, CMD_EDITSEARCHB, &edit_discard},
       -        {"", 0, 0, CMD_EDIT, &edit_insert_text},
       -        {"", 0, 0, CMD_EDITSEARCH, &edit_insert_text},
       -        {"", 0, 0, CMD_EDITSEARCHB, &edit_insert_text}
       -};
       -
       -static struct key keys_de[] = {
       -        {"z", 0, 0, CMD_SUBSTITUTE, &substitute_yes},
       -        {"Z", 0, 0, CMD_SUBSTITUTE, &substitute_yes_all},
       -        {"n", 0, 0, CMD_SUBSTITUTE, &substitute_no},
       -        {"N", 0, 0, CMD_SUBSTITUTE, &substitute_no_all},
       -        {NULL, XK_ANY_MOD, XK_Return, CMD_EDIT, &edit_submit},
       -        {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCH, &editsearch_submit},
       -        {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCHB, &editsearchb_submit},
       -        {NULL, 0, XK_Left, CMD_EDIT, &edit_cursor_left},
       -        {NULL, 0, XK_Left, CMD_EDITSEARCH, &edit_cursor_left},
       -        {NULL, 0, XK_Left, CMD_EDITSEARCHB, &edit_cursor_left},
       -        {NULL, 0, XK_Right, CMD_EDIT, &edit_cursor_right},
       -        {NULL, 0, XK_Right, CMD_EDITSEARCH, &edit_cursor_right},
       -        {NULL, 0, XK_Right, CMD_EDITSEARCHB, &edit_cursor_right},
       -        {NULL, 0, XK_Up, CMD_EDIT, &edit_prevcommand},
       -        {NULL, 0, XK_Up, CMD_EDITSEARCH, &edit_prevsearch},
       -        {NULL, 0, XK_Up, CMD_EDITSEARCHB, &edit_prevsearch},
       -        {NULL, 0, XK_Down, CMD_EDIT, &edit_nextcommand},
       -        {NULL, 0, XK_Down, CMD_EDITSEARCH, &edit_nextsearch},
       -        {NULL, 0, XK_Down, CMD_EDITSEARCHB, &edit_nextsearch},
       -        {NULL, 0, XK_BackSpace, CMD_EDIT, &edit_backspace},
       -        {NULL, 0, XK_BackSpace, CMD_EDITSEARCH, &edit_backspace},
       -        {NULL, 0, XK_BackSpace, CMD_EDITSEARCHB, &edit_backspace},
       -        {NULL, 0, XK_Delete, CMD_EDIT, &edit_delete},
       -        {NULL, 0, XK_Delete, CMD_EDITSEARCH, &edit_delete},
       -        {NULL, 0, XK_Delete, CMD_EDITSEARCHB, &edit_delete},
       -        {NULL, 0, XK_End, CMD_EDIT, &edit_cursor_to_end},
       -        {NULL, 0, XK_End, CMD_EDITSEARCH, &edit_cursor_to_end},
       -        {NULL, 0, XK_End, CMD_EDITSEARCHB, &edit_cursor_to_end},
       -        {NULL, 0, XK_Home, CMD_EDIT, &edit_cursor_to_beginning},
       -        {NULL, 0, XK_Home, CMD_EDITSEARCH, &edit_cursor_to_beginning},
       -        {NULL, 0, XK_Home, CMD_EDITSEARCHB, &edit_cursor_to_beginning},
       -        {NULL, 0, XK_Escape, CMD_EDIT, &edit_discard},
       -        {NULL, 0, XK_Escape, CMD_EDITSEARCH, &edit_discard},
       -        {NULL, 0, XK_Escape, CMD_EDITSEARCHB, &edit_discard},
       -        {"", 0, 0, CMD_EDIT, &edit_insert_text},
       -        {"", 0, 0, CMD_EDITSEARCH, &edit_insert_text},
       -        {"", 0, 0, CMD_EDITSEARCHB, &edit_insert_text}
       -};
       -
       -static struct key keys_ur[] = {
       -        {"ے", 0, 0, CMD_SUBSTITUTE, &substitute_yes},
       -        {"َ", 0, 0, CMD_SUBSTITUTE, &substitute_yes_all},
       -        {"ن", 0, 0, CMD_SUBSTITUTE, &substitute_no},
       -        {"ں", 0, 0, CMD_SUBSTITUTE, &substitute_no_all},
       -        {NULL, XK_ANY_MOD, XK_Return, CMD_EDIT, &edit_submit},
       -        {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCH, &editsearch_submit},
       -        {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCHB, &editsearchb_submit},
       -        {NULL, 0, XK_Left, CMD_EDIT, &edit_cursor_left},
       -        {NULL, 0, XK_Left, CMD_EDITSEARCH, &edit_cursor_left},
       -        {NULL, 0, XK_Left, CMD_EDITSEARCHB, &edit_cursor_left},
       -        {NULL, 0, XK_Right, CMD_EDIT, &edit_cursor_right},
       -        {NULL, 0, XK_Right, CMD_EDITSEARCH, &edit_cursor_right},
       -        {NULL, 0, XK_Right, CMD_EDITSEARCHB, &edit_cursor_right},
       -        {NULL, 0, XK_Up, CMD_EDIT, &edit_prevcommand},
       -        {NULL, 0, XK_Up, CMD_EDITSEARCH, &edit_prevsearch},
       -        {NULL, 0, XK_Up, CMD_EDITSEARCHB, &edit_prevsearch},
       -        {NULL, 0, XK_Down, CMD_EDIT, &edit_nextcommand},
       -        {NULL, 0, XK_Down, CMD_EDITSEARCH, &edit_nextsearch},
       -        {NULL, 0, XK_Down, CMD_EDITSEARCHB, &edit_nextsearch},
       -        {NULL, 0, XK_BackSpace, CMD_EDIT, &edit_backspace},
       -        {NULL, 0, XK_BackSpace, CMD_EDITSEARCH, &edit_backspace},
       -        {NULL, 0, XK_BackSpace, CMD_EDITSEARCHB, &edit_backspace},
       -        {NULL, 0, XK_Delete, CMD_EDIT, &edit_delete},
       -        {NULL, 0, XK_Delete, CMD_EDITSEARCH, &edit_delete},
       -        {NULL, 0, XK_Delete, CMD_EDITSEARCHB, &edit_delete},
       -        {NULL, 0, XK_End, CMD_EDIT, &edit_cursor_to_end},
       -        {NULL, 0, XK_End, CMD_EDITSEARCH, &edit_cursor_to_end},
       -        {NULL, 0, XK_End, CMD_EDITSEARCHB, &edit_cursor_to_end},
       -        {NULL, 0, XK_Home, CMD_EDIT, &edit_cursor_to_beginning},
       -        {NULL, 0, XK_Home, CMD_EDITSEARCH, &edit_cursor_to_beginning},
       -        {NULL, 0, XK_Home, CMD_EDITSEARCHB, &edit_cursor_to_beginning},
       -        {NULL, 0, XK_Escape, CMD_EDIT, &edit_discard},
       -        {NULL, 0, XK_Escape, CMD_EDITSEARCH, &edit_discard},
       -        {NULL, 0, XK_Escape, CMD_EDITSEARCHB, &edit_discard},
       -        {"", 0, 0, CMD_EDIT, &edit_insert_text},
       -        {"", 0, 0, CMD_EDITSEARCH, &edit_insert_text},
       -        {"", 0, 0, CMD_EDITSEARCHB, &edit_insert_text}
       -};
       -
       -static struct key keys_hi[] = {
       -        {"य", 0, 0, CMD_SUBSTITUTE, &substitute_yes},
       -        {"ञ", 0, 0, CMD_SUBSTITUTE, &substitute_yes_all},
       -        {"न", 0, 0, CMD_SUBSTITUTE, &substitute_no},
       -        {"ण", 0, 0, CMD_SUBSTITUTE, &substitute_no_all},
       -        {NULL, XK_ANY_MOD, XK_Return, CMD_EDIT, &edit_submit},
       -        {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCH, &editsearch_submit},
       -        {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCHB, &editsearchb_submit},
       -        {NULL, 0, XK_Left, CMD_EDIT, &edit_cursor_left},
       -        {NULL, 0, XK_Left, CMD_EDITSEARCH, &edit_cursor_left},
       -        {NULL, 0, XK_Left, CMD_EDITSEARCHB, &edit_cursor_left},
       -        {NULL, 0, XK_Right, CMD_EDIT, &edit_cursor_right},
       -        {NULL, 0, XK_Right, CMD_EDITSEARCH, &edit_cursor_right},
       -        {NULL, 0, XK_Right, CMD_EDITSEARCHB, &edit_cursor_right},
       -        {NULL, 0, XK_Up, CMD_EDIT, &edit_prevcommand},
       -        {NULL, 0, XK_Up, CMD_EDITSEARCH, &edit_prevsearch},
       -        {NULL, 0, XK_Up, CMD_EDITSEARCHB, &edit_prevsearch},
       -        {NULL, 0, XK_Down, CMD_EDIT, &edit_nextcommand},
       -        {NULL, 0, XK_Down, CMD_EDITSEARCH, &edit_nextsearch},
       -        {NULL, 0, XK_Down, CMD_EDITSEARCHB, &edit_nextsearch},
       -        {NULL, 0, XK_BackSpace, CMD_EDIT, &edit_backspace},
       -        {NULL, 0, XK_BackSpace, CMD_EDITSEARCH, &edit_backspace},
       -        {NULL, 0, XK_BackSpace, CMD_EDITSEARCHB, &edit_backspace},
       -        {NULL, 0, XK_Delete, CMD_EDIT, &edit_delete},
       -        {NULL, 0, XK_Delete, CMD_EDITSEARCH, &edit_delete},
       -        {NULL, 0, XK_Delete, CMD_EDITSEARCHB, &edit_delete},
       -        {NULL, 0, XK_End, CMD_EDIT, &edit_cursor_to_end},
       -        {NULL, 0, XK_End, CMD_EDITSEARCH, &edit_cursor_to_end},
       -        {NULL, 0, XK_End, CMD_EDITSEARCHB, &edit_cursor_to_end},
       -        {NULL, 0, XK_Home, CMD_EDIT, &edit_cursor_to_beginning},
       -        {NULL, 0, XK_Home, CMD_EDITSEARCH, &edit_cursor_to_beginning},
       -        {NULL, 0, XK_Home, CMD_EDITSEARCHB, &edit_cursor_to_beginning},
       -        {NULL, 0, XK_Escape, CMD_EDIT, &edit_discard},
       -        {NULL, 0, XK_Escape, CMD_EDITSEARCH, &edit_discard},
       -        {NULL, 0, XK_Escape, CMD_EDITSEARCHB, &edit_discard},
       -        {"", 0, 0, CMD_EDIT, &edit_insert_text},
       -        {"", 0, 0, CMD_EDITSEARCH, &edit_insert_text},
       -        {"", 0, 0, CMD_EDITSEARCHB, &edit_insert_text}
       -};
       -
       -GEN_KEY_ARRAY(struct key, keys_en, keys_de, keys_hi, keys_ur);
   DIR diff --git a/keys_config.h b/keys_config.h
       t@@ -1,31 +1,182 @@
        #ifndef _KEYS_CONFIG_H_
        #define _KEYS_CONFIG_H_
        
       +#include "X11/Xlib.h"
       +#include "X11/keysym.h"
       +
       +#include "common.h"
        #include "keys.h"
        
       -/*
       - * These are the language strings compared with the language strings that
       - * xkb gives in order to change the key mapping on layout change events.
       - */
       -#define KEY_LANGS             \
       -static char *key_langs[] = {  \
       -        "English (US)",       \
       -        "German",             \
       -        "Urdu (Pakistan)",    \
       -        "Hindi (Bolnagri)"    \
       -}
       -
       -#define GEN_KEY_ARRAY(key_struct, en, de, ur, hi) \
       -static struct {                                   \
       -        key_struct *keys;                         \
       -        int num_keys;                             \
       -} keys[] = {                                      \
       -        {en, LENGTH(en)},                         \
       -        {de, LENGTH(de)},                         \
       -        {ur, LENGTH(ur)},                         \
       -        {hi, LENGTH(hi)}                          \
       -}
       -
       -#define LANG_KEYS(index) &keys[index]
       -
       -#endif
       +/*********************************
       + * Key and command configuration *
       + *********************************/
       +
       +char *language_default = "English (US)";
       +
       +/* FIXME: binary search keys */
       +struct {
       +        char *func_name;
       +        char *text;
       +        unsigned int mods;
       +        KeySym keysym;
       +        ledit_mode modes;
       +} basic_keys_default[] = {
       +        {"backspace", NULL, 0, XK_BackSpace, INSERT},
       +        {"cursor-left", NULL, 0, XK_Left, VISUAL|INSERT|NORMAL},
       +        {"cursor-right", NULL, 0, XK_Right, VISUAL|INSERT|NORMAL},
       +        {"cursor-up", NULL, 0, XK_Up, VISUAL|INSERT|NORMAL},
       +        {"cursor-down", NULL, 0, XK_Down, VISUAL|INSERT|NORMAL},
       +        {"return-key", NULL, XK_ANY_MOD, XK_Return, INSERT},
       +        {"delete-key", NULL, 0, XK_Delete, INSERT},
       +        {"escape-key", NULL, 0, XK_Escape, NORMAL|VISUAL|INSERT},
       +        {"enter-insert", "i", 0, 0, NORMAL|VISUAL},
       +        {"cursor-left", "h", 0, 0, NORMAL|VISUAL},
       +        {"cursor-left", "h", ControlMask, 0, NORMAL|VISUAL},
       +        {"cursor-right", "l", 0, 0, NORMAL|VISUAL},
       +        {"cursor-up", "k", 0, 0, NORMAL|VISUAL},
       +        {"cursor-down", "j", 0, 0, NORMAL|VISUAL},
       +        {"toggle-hard-line-based", "t", ControlMask, 0, NORMAL|VISUAL}, /* FIXME: also in insert */
       +        {"cursor-right", NULL, 0, XK_space, NORMAL|VISUAL},
       +        {"cursor-down", "j", ControlMask, 0, NORMAL|VISUAL},
       +        {"cursor-down", "n", ControlMask, 0, NORMAL|VISUAL},
       +        {"cursor-up", "p", ControlMask, 0, NORMAL|VISUAL},
       +        {"key-0", "0", 0, 0, NORMAL|VISUAL},
       +        {"push-1", "1", 0, 0, NORMAL|VISUAL},
       +        {"push-2", "2", 0, 0, NORMAL|VISUAL},
       +        {"push-3", "3", 0, 0, NORMAL|VISUAL},
       +        {"push-4", "4", 0, 0, NORMAL|VISUAL},
       +        {"push-5", "5", 0, 0, NORMAL|VISUAL},
       +        {"push-6", "6", 0, 0, NORMAL|VISUAL},
       +        {"push-7", "7", 0, 0, NORMAL|VISUAL},
       +        {"push-8", "8", 0, 0, NORMAL|VISUAL},
       +        {"push-9", "9", 0, 0, NORMAL|VISUAL},
       +        {"delete-forwards", "x", 0, 0, NORMAL},
       +        {"delete-backwards", "X", 0, 0, NORMAL},
       +        {"delete", "d", 0, 0, NORMAL|VISUAL},
       +        {"yank", "y", 0, 0, NORMAL|VISUAL},
       +        {"yank-lines", "Y", 0, 0, NORMAL},
       +        {"change", "c", 0, 0, NORMAL|VISUAL},
       +        {"enter-visual", "v", 0, 0, NORMAL},
       +        {"switch-selection-end", "o", 0, 0, VISUAL},
       +        {"clipboard-copy", "c", ControlMask, 0, VISUAL},
       +        {"clipboard-paste", "v", ControlMask, 0, INSERT},
       +        {"show-line", "g", ControlMask, 0, NORMAL|VISUAL},
       +        {"enter-commandedit", ":", 0, 0, NORMAL|VISUAL},
       +        {"enter-searchedit-backwards", "?", 0, 0, NORMAL},
       +        {"enter-searchedit-forwards", "/", 0, 0, NORMAL},
       +        {"search-next", "n", 0, 0, NORMAL},
       +        {"search-previous", "N", 0, 0, NORMAL},
       +        {"undo", "u", 0, 0, NORMAL},
       +        {"redo", "U", 0, 0, NORMAL},
       +        {"repeat-command", ".", 0, 0, NORMAL}, /* FIXME: only allow after finished key sequence */
       +        {"undo", "z", ControlMask, 0, INSERT},
       +        {"redo", "y", ControlMask, 0, INSERT}, /* FIXME: this is confusing with ctrl-y in normal mode */
       +        {"screen-up", "b", ControlMask, 0, NORMAL},
       +        {"screen-down", "f", ControlMask, 0, NORMAL},
       +        {"scroll-with-cursor-down", "e", ControlMask, 0, NORMAL},
       +        {"scroll-with-cursor-up", "y", ControlMask, 0, NORMAL},
       +        {"scroll-lines-down", "d", ControlMask, 0, NORMAL},
       +        {"scroll-lines-up", "u", ControlMask, 0, NORMAL},
       +        {"move-to-eol", "$", 0, 0, NORMAL|VISUAL},
       +        {"next-word", "w", 0, 0, NORMAL|VISUAL},
       +        {"next-word-end", "e", 0, 0, NORMAL|VISUAL},
       +        {"next-bigword", "W", 0, 0, NORMAL|VISUAL},
       +        {"next-bigword-end", "E", 0, 0, NORMAL|VISUAL},
       +        {"previous-word", "b", 0, 0, NORMAL|VISUAL},
       +        {"previous-bigword", "B", 0, 0, NORMAL|VISUAL},
       +        {"move-to-line", "G", 0, 0, NORMAL|VISUAL},
       +        {"join-lines", "J", 0, 0, NORMAL},
       +        {"insert-at-beginning", "I", 0, 0, NORMAL},
       +        {"paste-normal", "p", 0, 0, NORMAL},
       +        {"paste-normal-backwards", "P", 0, 0, NORMAL},
       +        {"append-after-eol", "A", 0, 0, NORMAL},
       +        {"append-after-cursor", "a", 0, 0, NORMAL},
       +        {"append-line-above", "O", 0, 0, NORMAL},
       +        {"append-line-below", "o", 0, 0, NORMAL},
       +        {"mark-line", "m", 0, 0, NORMAL|VISUAL},
       +        {"jump-to-mark", "'", 0, 0, NORMAL|VISUAL},
       +        {"change-to-eol", "C", 0, 0, NORMAL},
       +        {"delete-to-eol", "D", 0, 0, NORMAL},
       +        {"replace", "r", 0, 0, NORMAL},
       +        {"cursor-to-first-non-whitespace", "^", 0, 0, NORMAL},
       +        {"find-next-char-forwards", "t", 0, 0, NORMAL|VISUAL},
       +        {"find-next-char-backwards", "T", 0, 0, NORMAL|VISUAL},
       +        {"find-char-forwards", "f", 0, 0, NORMAL|VISUAL},
       +        {"find-char-backwards", "F", 0, 0, NORMAL|VISUAL},
       +        {"insert-text", "", 0, 0, INSERT}
       +};
       +
       +struct {
       +        char *func_name;
       +        char *text;
       +        unsigned int mods;
       +        KeySym keysym;
       +        command_mode modes;
       +} command_keys_default[] = {
       +        {"substitute-yes", "y", 0, 0, CMD_SUBSTITUTE},
       +        {"substitute-yes-all", "Y", 0, 0, CMD_SUBSTITUTE},
       +        {"substitute-no", "n", 0, 0, CMD_SUBSTITUTE},
       +        {"substitute-no-all", "N", 0, 0, CMD_SUBSTITUTE},
       +        {"edit-submit", NULL, XK_ANY_MOD, XK_Return, CMD_EDIT},
       +        {"edit-submit-search", NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCH},
       +        {"edit-submit-backwards-search", NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCHB},
       +        {"edit-cursor-left", NULL, 0, XK_Left, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-cursor-right", NULL, 0, XK_Right, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-cursor-right", NULL, 0, XK_Right, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-previous-command", NULL, 0, XK_Up, CMD_EDIT},
       +        {"edit-next-command", NULL, 0, XK_Down, CMD_EDIT},
       +        {"edit-previous-search", NULL, 0, XK_Up, CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-next-search", NULL, 0, XK_Down, CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-backspace", NULL, 0, XK_BackSpace, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-delete", NULL, 0, XK_Delete, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-cursor-to-end", NULL, 0, XK_End, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-cursor-to-beginning", NULL, 0, XK_Home, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-discard", NULL, 0, XK_Escape, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
       +        {"edit-insert-text", "", 0, 0, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB}
       +};
       +
       +struct {
       +        char *func_name;
       +        char *text;
       +} commands_default[] = {
       +        {"write-quit", "wg"},
       +        {"write", "w"},
       +        {"quit", "q"},
       +        {"create-view", "v"},
       +        {"close-view", "c"},
       +        {"substitute", "s"}
       +};
       +
       +/*****************************************
       + * Key and command mapping configuration *
       + *****************************************/
       +
       +struct mapping {
       +        char *from;
       +        char *to;
       +};
       +
       +struct language_mapping {
       +        char *lang;
       +        struct mapping *keys;
       +        struct mapping *cmds;
       +        size_t keys_len;
       +        size_t cmds_len;
       +};
       +
       +struct mapping key_mapping_de[] = {
       +        {"z", "y"},
       +        {"y", "z"},
       +        {"Z", "Y"},
       +        {"Y", "Z"},
       +        {"Ö", ":"},
       +        {"_", "?"},
       +        {"-", "/"},
       +        {"ä", "'"}
       +};
       +
       +struct language_mapping mappings_default[] = {
       +        {"German", key_mapping_de, NULL, LENGTH(key_mapping_de), 0}
       +};
       +
       +#endif /* _KEYS_CONFIG_H_ */
   DIR diff --git a/ledit.1 b/ledit.1
       t@@ -1,6 +1,6 @@
        .\" WARNING: Some parts of this are stolen shamelessly from OpenBSD's
        .\" vi(1) manpage!
       -.Dd December 26, 2021
       +.Dd May 26, 2022
        .Dt LEDIT 1
        .Os
        .Sh NAME
       t@@ -8,6 +8,7 @@
        .Nd weird text editor
        .Sh SYNOPSIS
        .Nm
       +.Op Fl c Ar config
        .Op Ar file
        .Sh DESCRIPTION
        .Nm
       t@@ -45,6 +46,17 @@ that is not the main goal.
        .Pp
        .Nm
        is not a good text editor.
       +.Sh OPTIONS
       +.Bl -tag -width Ds
       +.It Fl c Ar config
       +Load the configuration file given by
       +.Ar config .
       +If this option is not specified,
       +.Nm
       +will attempt to read the configuration file
       +.Pa .leditrc
       +in the user's home directory before using the defaults.
       +.El
        .Sh BASIC CONCEPTS
        Some terminology should probably be explained in order to understand the
        rest of this manual.
       t@@ -693,12 +705,6 @@ Also note that the commands which take filenames currently use the entire rest o
        the line as the filename instead of doing any string parsing.
        This may be changed in the future.
        .Pp
       -The commands do not fit in very well with the rest of the program since they
       -are hard-coded in English and cannot be remapped in other languages.
       -This is because it isn't entirely clear how a remapping would even work
       -because the commands are shown on screen, which might look weird, especially
       -if they are remapped to non-printable characters.
       -.Pp
        .Bl -tag -width Ds -compact
        .It Xo
        .Cm :w
       t@@ -819,71 +825,8 @@ Note that the text is pasted at the current cursor position, not the
        position of the mouse cursor.
        The author prefers this way.
        .Sh CONFIGURATION
       -.Nm
       -currently has to be configured entirely by changing header files and
       -recompiling.
       -This will probably be changed in the future, but there hasn't been
       -time for it yet.
       -.Pp
       -There are five configuration headers:
       -.Bl -tag -width Ds
       -.It Pa config.h
       -This contains several timing parameters that most users will probably
       -not need to change.
       -.It Pa keys_config.h
       -This contains the list of languages for which keys are available.
       -The language strings in
       -.Va key_langs
       -are matched exactly with the strings returned by XKB, which seem to
       -sometimes be different on different systems, so they will probably
       -need to be configured properly. Also note that there are many
       -variants of some keyboard layouts, all with small differences, so
       -the mappings will often have to be adjusted slightly.
       -.It Pa keys_basic_config.h
       -This contains the keys used during regular text editing.
       -The first entry in each key is the text that is associated with the text.
       -If this is
       -.Dv NULL ,
       -the third entry, which may contain a symbolic key name, is used for the
       -matching instead.
       -If the key text is an empty string, it is a catch-all, and the actual
       -text is given to the handling function.
       -.Pp
       -Note that the list of keys is currently traversed from top to bottom,
       -so catch-all keys should all be in the end in order to not inferfere
       -with other mappings.
       -.Pp
       -The second entry is a mask of modifier keys that need to be
       -pressed during the key press.
       -If this is set to
       -.Dv XK_ANY_MOD ,
       -the modifier keys are ignored.
       -.Pp
       -The other entries should not be touched unless the actual handling
       -function implemented in
       -.Pa keys_basic.c
       -has been changed.
       -.Pp
       -Note that the key handling is currently a bit weird, so there might
       -be unexpected results sometimes.
       -Please report these.
       -.It Pa keys_command_config.h
       -This is similar to
       -.Pa keys_basic_config.h ,
       -but contains the keys used during line editing mode or while performing
       -commands such as substitution with confirmation.
       -.It Pa theme_config.h
       -This contains the configuration of the theme.
       -The configuration options are described in the file itself.
       -.El
       -.Pp
       -This short explanation of the configuration is not very good currently.
       -That will hopefully be changed in the future.
       -.Pp
       -Note that there are a few actions that are currently hard-coded and cannot
       -be configured.
       -This includes all mouse actions and using escape to cancel a multi-key
       -command.
       +See
       +.Xr leditrc 5 .
        .Sh MISCELLANEOUS
        .Nm
        includes a fair number of sanity checks (asserts) to make sure some illegal
       t@@ -930,7 +873,8 @@ Pango developers decided to hide spaces at the end of a line.
        .Sh SEE ALSO
        .Xr ed 1 ,
        .Xr vi 1 ,
       -.Xr vim 1
       +.Xr vim 1 ,
       +.Xr leditrc 5
        .Sh AUTHORS
        .An lumidify Aq Mt nobody@lumidify.org
        .Sh BUGS
       t@@ -938,8 +882,4 @@ Too many to count.
        See
        .Sx TINY SUBSET OF BUGS .
        .Sh TINY SUBSET OF BUGS
       -The keyboard mapping is currently only changed when a keyboard change event
       -occurs.
       -This means that it always is the default mapping when
       -.Nm
       -starts, regardless of the current keyboard layout.
       +Well, I guess I'm too lazy to collect bugs right now.
   DIR diff --git a/ledit.c b/ledit.c
       t@@ -10,6 +10,7 @@
        /* FIXME: horizontal scrolling (also need cache to avoid too large pixmaps) */
        /* TODO: allow extending selection with shift+mouse like in e.g. gtk */
        
       +#include <pwd.h>
        #include <time.h>
        #include <errno.h>
        #include <stdio.h>
       t@@ -24,8 +25,8 @@
        #include <X11/extensions/Xdbe.h>
        #include <X11/extensions/XKBrules.h>
        
       +#include "util.h"
        #include "view.h"
       -#include "theme.h"
        #include "buffer.h"
        #include "common.h"
        #include "window.h"
       t@@ -37,6 +38,7 @@
        #include "keys.h"
        #include "keys_basic.h"
        #include "keys_command.h"
       +#include "configparser.h"
        
        static void mainloop(void);
        static void setup(int argc, char *argv[]);
       t@@ -45,10 +47,9 @@ static void redraw(void);
        static void change_keyboard(char *lang);
        static void key_press(ledit_view *view, XEvent *event);
        
       -ledit_theme *theme = NULL;
        ledit_buffer *buffer = NULL;
        ledit_common common;
       -int cur_lang = 0;
       +size_t cur_lang = 0;
        
        static void
        mainloop(void) {
       t@@ -76,7 +77,7 @@ mainloop(void) {
                );
                XSync(common.dpy, False);
                int running = 1;
       -        int change_kbd = 0;
       +        int change_kbd = 1;
        
                redraw();
                /* store last draw time so framerate can be limited */
       t@@ -193,13 +194,33 @@ mainloop(void) {
                }
        }
        
       +extern char *optarg;
       +extern int optind;
       +
        static void
        setup(int argc, char *argv[]) {
                setlocale(LC_CTYPE, "");
                XSetLocaleModifiers("");
        
       +        char c;
       +        char *opt_filename = NULL;
       +        while ((c = getopt(argc, argv, "c:")) != -1) {
       +                switch (c) {
       +                case 'c':
       +                        opt_filename = optarg;
       +                        break;
       +                default:
       +                        fprintf(stderr, "USAGE: ledit [-c config] [file]\n");
       +                        exit(1);
       +                        break;
       +                }
       +        }
       +        argc -= optind;
       +        argv += optind;
       +
                common.dpy = XOpenDisplay(NULL);
                common.screen = DefaultScreen(common.dpy);
       +        /* FIXME: fallback when no db support */
                /* based on http://wili.cc/blog/xdbe.html */
                int major, minor;
                if (XdbeQueryExtension(common.dpy, &major, &minor)) {
       t@@ -233,6 +254,8 @@ setup(int argc, char *argv[]) {
                                exit(1);
                        }
                        common.vis = xvisinfo_match->visual;
       +                XFree(xvisinfo_match);
       +                XdbeFreeVisualInfo(info);
                } else {
                        fprintf(stderr, "No Xdbe support.\n");
                        ledit_cleanup();
       t@@ -242,15 +265,83 @@ setup(int argc, char *argv[]) {
                common.depth = DefaultDepth(common.dpy, common.screen);
                common.cm = DefaultColormap(common.dpy, common.screen);
        
       -        theme = theme_create(&common);
       +        #ifdef LEDIT_DEBUG
       +        struct timespec now, elapsed, last;
       +        clock_gettime(CLOCK_MONOTONIC, &last);
       +        #endif
       +
       +        char *stat_errstr = NULL, *load_errstr = NULL, *load_default_errstr = NULL;
       +        char *cfgfile = NULL;
       +        if (!opt_filename) {
       +                uid_t uid = getuid();
       +                struct passwd *pw = getpwuid(uid);
       +                if (!pw)
       +                        fprintf(stderr, "Unable to determine home directory\n");
       +                else
       +                        cfgfile = ledit_strcat(pw->pw_dir, "/.leditrc");
       +                struct stat cfgst;
       +                if (stat(cfgfile, &cfgst)) {
       +                        free(cfgfile);
       +                        cfgfile = NULL;
       +                }
       +        } else {
       +                struct stat cfgst;
       +                if (stat(opt_filename, &cfgst)) {
       +                        stat_errstr = print_fmt("Unable to load configuration file '%s'", opt_filename);
       +                        fprintf(stderr, "%s\n", stat_errstr);
       +                } else {
       +                        cfgfile = ledit_strdup(opt_filename);
       +                }
       +        }
       +        if (config_loadfile(&common, cfgfile, &load_errstr)) {
       +                fprintf(stderr, "%s\n", load_errstr);
       +                int failure = 1;
       +                if (cfgfile) {
       +                        /* retry with default config */
       +                        failure = config_loadfile(&common, NULL, &load_default_errstr);
       +                }
       +                if (failure) {
       +                        fprintf(stderr, "Unable to load configuration: %s\n", load_errstr);
       +                        if (load_default_errstr)
       +                                fprintf(stderr, "Also unable to load default configuration: %s\n", load_default_errstr);
       +                        free(stat_errstr);
       +                        free(load_errstr);
       +                        free(load_default_errstr);
       +                        ledit_cleanup();
       +                        exit(1);
       +                }
       +        }
       +        free(load_default_errstr);
       +        free(cfgfile);
       +
       +        #ifdef LEDIT_DEBUG
       +        clock_gettime(CLOCK_MONOTONIC, &now);
       +        ledit_timespecsub(&now, &last, &elapsed);
       +        ledit_debug_fmt("Time to load config (total): %lld seconds, %ld nanoseconds\n", (long long)elapsed.tv_sec, elapsed.tv_nsec);
       +        #endif
       +
                buffer = buffer_create(&common);
       -        buffer_add_view(buffer, theme, NORMAL, 0, 0, 0);
       +        buffer_add_view(buffer, NORMAL, 0, 0, 0);
                /* FIXME: don't access view directly here */
                ledit_view *view = buffer->views[0];
       +        /* FIXME: this message may be wiped immediately */
       +        /* -> maybe allow showing multiple messages? */
       +        /* currently, the more important message is just prioritized */
       +        int show_error = 0;
       +        if (stat_errstr || load_errstr) {
       +                show_error = 1;
       +                if (stat_errstr)
       +                        window_show_message(view->window, stat_errstr, -1);
       +                else if (load_errstr)
       +                        window_show_message(view->window, load_errstr, -1);
       +                free(stat_errstr);
       +                free(load_errstr);
       +        }
                view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
       +        /* FIXME: maybe also log all errors instead of just showing them on screen? */
                /* FIXME: Support multiple buffers/files */
                /* FIXME: check if file may be binary */
       -        if (argc > 1) {
       +        if (argc >= 1) {
                        /* FIXME: move this to different file */
                        char *load_err;
                        struct stat sb;
       t@@ -258,7 +349,7 @@ setup(int argc, char *argv[]) {
                        int readonly = 0;
                        int error = 0;
                        /* FIXME: maybe copy vi and open file in /tmp by default? */
       -                if (stat(argv[1], &sb)) {
       +                if (stat(argv[0], &sb)) {
                                if (errno == ENOENT) {
                                        /* note that there may still be a failure
                                           when trying to write if a directory in
       t@@ -267,32 +358,35 @@ setup(int argc, char *argv[]) {
                                } else {
                                        window_show_message_fmt(
                                            view->window, "Error opening file '%s': %s",
       -                                    argv[1], strerror(errno)
       +                                    argv[0], strerror(errno)
                                        );
                                        error = 1;
                                }
                        }
       -                if (access(argv[1], W_OK)) {
       +                if (access(argv[0], W_OK)) {
                                readonly = 1;
                        }
                        if (!newfile) {
       -                        if (buffer_load_file(buffer, argv[1], 0, &load_err)) {
       +                        if (buffer_load_file(buffer, argv[0], 0, &load_err)) {
                                        window_show_message_fmt(
                                            view->window, "Error opening file '%s': %s",
       -                                    argv[1], load_err
       +                                    argv[0], load_err
                                        );
                                        error = 1;
                                }
                                buffer->file_mtime = sb.st_mtim;
                        }
                        if (!error) {
       -                        buffer->filename = ledit_strdup(argv[1]);
       -                        if (newfile) {
       -                                window_show_message_fmt(view->window, "%s: new file", argv[1]);
       -                        } else if (readonly) {
       -                                window_show_message_fmt(view->window, "%s: readonly", argv[1]);
       -                        } else {
       -                                window_show_message(view->window, argv[1], -1);
       +                        buffer->filename = ledit_strdup(argv[0]);
       +                        /* FIXME: show this *in addition* to error */
       +                        if (!show_error) {
       +                                if (newfile) {
       +                                        window_show_message_fmt(view->window, "%s: new file", argv[0]);
       +                                } else if (readonly) {
       +                                        window_show_message_fmt(view->window, "%s: readonly", argv[0]);
       +                                } else {
       +                                        window_show_message(view->window, argv[0], -1);
       +                                }
                                }
                        }
                }
       t@@ -302,6 +396,8 @@ setup(int argc, char *argv[]) {
        
        void
        ledit_emergencydump(void) {
       +        /* FIXME: pre-allocate memory for template to avoid memory errors?
       +           -> probably overkill since something else will fail anyways */
                if (!buffer)
                        return;
                /* FIXME: maybe write assertion message to file? */
       t@@ -355,8 +451,7 @@ ledit_cleanup(void) {
                command_key_cleanup();
                if (buffer)
                        buffer_destroy(buffer);
       -        if (theme)
       -                theme_destroy(&common, theme);
       +        config_cleanup(&common);
                XCloseDisplay(common.dpy);
        }
        
       t@@ -369,8 +464,8 @@ redraw(void) {
        
        static void
        change_keyboard(char *lang) {
       -        cur_lang = get_language_index(lang);
       -        if (cur_lang < 0) {
       +        ledit_debug_fmt("New keyboard layout: %s\n", lang);
       +        if (config_get_language_index(lang, &cur_lang)) {
                        for (size_t i = 0; i < buffer->views_num; i++) {
                                window_show_message_fmt(
                                    buffer->views[i]->window,
   DIR diff --git a/leditrc.5 b/leditrc.5
       t@@ -0,0 +1,347 @@
       +.Dd May 26, 2022
       +.Dt LEDITRC 5
       +.Os
       +.Sh NAME
       +.Nm leditrc
       +.Nd configuration file for
       +.Xr ledit 1
       +.Sh DESCRIPTION
       +.Nm
       +is the configuration file for the text editor
       +.Xr ledit 1 ,
       +which can be used to configure the theme and key bindings used.
       +.Pp
       +The parser recognizes four different types of structures:
       +strings, lists, statements, and assignments.
       +.Pp
       +A string is simply any sequence of characters surrounded by double quotes.
       +Double quotes must be backslash-escaped.
       +If a string does not contain any whitespace or the special
       +characters
       +.Sq \&" ,
       +.Sq { ,
       +.Sq } ,
       +or
       +.Sq = ,
       +the double quotes are not required.
       +.Pp
       +A statement is a sequence of strings, separated by whitespace and
       +all on the same line.
       +.Pp
       +An assignment is of the form
       +.Aq identifier
       +=
       +.Aq structure ,
       +where
       +.Aq identifier
       +is a string and
       +.Aq structure
       +is a string or a list.
       +.Pp
       +A list is a sequence of assignments and/or statements that is
       +enclosed by curly braces.
       +The assignments/statements must be separated by newlines.
       +.Pp
       +The configuration file consists of several top-level assignments
       +which are described in the following sections.
       +.Sh THEME
       +The theme may be configured by assigning
       +.Ar theme
       +to a list of assignments, each of which sets one of the following
       +possible properties.
       +Colors are given in the form #RRGGBB, where the
       +.Sq #
       +is optional (mainly because
       +.Sq #
       +also starts comments in the configuration file format).
       +.Bl -tag -width Ds
       +.It Ar text-font
       +Font used for all text.
       +Default: Monospace
       +.It Ar text-size
       +Text size (in points or whatever pango uses).
       +Default: 12
       +.It Ar text-fg
       +Text color in main editing area.
       +Default: #000000
       +.It Ar text-bg
       +Background color in main editing area.
       +Default: #FFFFFF
       +.It Ar cursor-fg
       +Color of text under cursor.
       +Default: #FFFFFF
       +.It Ar cursor-bg
       +Color of text cursor.
       +Default: #000000
       +.It Ar selection-fg
       +Color of selected text.
       +Default: #FFFFFF
       +.It Ar selection-bg
       +Color of selection.
       +Default: #000000
       +.It Ar bar-fg
       +Color of text in status bar/line editor.
       +Default: #000000
       +.It Ar bar-bg
       +Background color of status bar/line editor.
       +Default: #CCCCCC
       +.It Ar bar-cursor
       +Color of text cursor in line editor.
       +Default: #000000
       +.It Ar scrollbar-width
       +Width of scrollbar in pixels.
       +Default: 10
       +.It Ar scrollbar-step
       +Number of pixels scrolled with each scroll event.
       +Default: 20
       +.It Ar scrollbar-bg
       +Background color of scrollbar.
       +Default: #CCCCCC
       +.It Ar scrollbar-fg
       +Color of scrollbar handle.
       +Default: #000000
       +.El
       +.Sh BINDINGS
       +The key bindings may be configured by assigning
       +.Ar bindings
       +to a list of the following assignments.
       +.Bl -tag -width Ds
       +.It Ar language
       +.Pp
       +This is the language string for the key layout, as given by XKB.
       +.It Ar basic-keys
       +.Pp
       +This is a list of statements of the form
       +.Pp
       +.Sy bind
       +.Aq func_name
       +.Op Sy keysym Aq keysym
       +.Op Sy text Aq text
       +.Op Sy catchall
       +.Op Sy modes Aq modes
       +.Op Sy mods Aq mods
       +.Pp
       +.Sy keysym
       +is the symbolic description for a key, such as
       +.Ar space .
       +The full list is not documented yet (FIXME!).
       +.Sy text
       +is the text corresponding to a key.
       +.Sy catchall
       +is a catchall for any key which can for instance be used to insert text.
       +Note that a key binding containing
       +.Sy catchall
       +should always be at the end of the list so it does not prevent
       +any other key bindings from being used.
       +.Pp
       +Exactly one of
       +.Sy text ,
       +.Sy keysym ,
       +and
       +.Sy catchall
       +must be specified.
       +.Pp
       +.Sy mods
       +specifies modifier keys.
       +The current options are
       +.Ar shift ,
       +.Ar lock ,
       +.Ar control ,
       +.Ar mod1 ,
       +.Ar mod2 ,
       +.Ar mod3 ,
       +.Ar mod4 ,
       +.Ar mod5 ,
       +and
       +.Ar any .
       +.Pp
       +.Sy modes
       +specifies the allowed modes and can be a combination of
       +.Ar normal ,
       +.Ar visual ,
       +and
       +.Ar insert .
       +.Pp
       +Multiple mods or modes can be given by joining them with
       +.Sq | .
       +.Pp
       +.Aq func_name
       +may be one of the following functions.
       +The possible modes are listed beside the function names.
       +.Bl -tag -width Ds
       +.It Ar append-after-cursor Op normal
       +.It Ar append-after-eol Op normal
       +.It Ar append-line-above Op normal
       +.It Ar append-line-below Op normal
       +.It Ar backspace Op insert
       +.It Ar change Op normal, visual
       +.It Ar change-to-eol Op normal
       +.It Ar clipboard-copy Op visual
       +.It Ar clipboard-paste Op insert
       +.It Ar cursor-down Op normal, visual, insert
       +.It Ar cursor-left Op normal, visual, insert
       +.It Ar cursor-right Op normal, visual, insert
       +.It Ar cursor-to-beginning Op normal, visual
       +.It Ar cursor-to-first-non-whitespace Op normal
       +.It Ar cursor-up Op normal, visual, insert
       +.It Ar delete Op normal, visual
       +.It Ar delete-backwards Op normal
       +.It Ar delete-forwards Op normal
       +.It Ar delete-key Op insert
       +.It Ar delete-to-eol Op normal
       +.It Ar enter-commandedit Op normal, visual
       +.It Ar enter-insert Op normal, visual
       +.It Ar enter-searchedit-backwards Op normal
       +.It Ar enter-searchedit-forwards Op normal
       +.It Ar enter-visual Op normal
       +.It Ar escape-key Op normal, visual, insert
       +.It Ar find-char-backwards Op normal, visual
       +.It Ar find-char-forwards Op normal, visual
       +.It Ar find-next-char-backwards Op normal, visual
       +.It Ar find-next-char-forwards Op normal, visual
       +.It Ar insert-at-beginning Op normal
       +.It Ar insert-text Op insert
       +.It Ar join-lines Op normal
       +.It Ar jump-to-mark Op normal, visual
       +.It Ar key-0 Op normal, visual
       +.It Ar mark-line Op normal, visual
       +.It Ar move-to-eol Op normal, visual
       +.It Ar move-to-line Op normal, visual
       +.It Ar next-bigword Op normal, visual
       +.It Ar next-bigword-end Op normal, visual
       +.It Ar next-word Op normal, visual
       +.It Ar next-word-end Op normal, visual
       +.It Ar paste-normal Op normal
       +.It Ar paste-normal-backwards normal
       +.It Ar previous-bigword Op normal, visual
       +.It Ar previous-word Op normal, visual
       +.It Ar push-0 Op normal, visual
       +.It Ar push-1 Op normal, visual
       +.It Ar push-2 Op normal, visual
       +.It Ar push-3 Op normal, visual
       +.It Ar push-4 Op normal, visual
       +.It Ar push-5 Op normal, visual
       +.It Ar push-6 Op normal, visual
       +.It Ar push-7 Op normal, visual
       +.It Ar push-8 Op normal, visual
       +.It Ar push-9 Op normal, visual
       +.It Ar redo Op normal, insert
       +.It Ar repeat-command Op normal
       +.It Ar replace Op normal
       +.It Ar return-key Op insert
       +.It Ar screen-down Op normal
       +.It Ar screen-up Op normal
       +.It Ar scroll-lines-down Op normal
       +.It Ar scroll-lines-up Op normal
       +.It Ar scroll-with-cursor-down Op normal
       +.It Ar scroll-with-cursor-up Op normal
       +.It Ar search-next Op normal
       +.It Ar search-previous Op normal
       +.It Ar show-line Op normal, visual
       +.It Ar switch-selection-end Op visual
       +.It Ar toggle-hard-line-based Op normal, visual
       +.It Ar undo Op normal, insert
       +.It Ar yank Op normal, visual
       +.It Ar yank-lines Op normal
       +.El
       +.Pp
       +Note that some of these functions should work in other modes
       +as well, but I still need to fix that (FIXME!).
       +.It Ar command-keys
       +.Pp
       +This is the same as
       +.Ar basic-keys ,
       +except that
       +.Sy modes
       +must be a combination of
       +.Ar substitute ,
       +.Ar edit ,
       +.Ar edit-search ,
       +and
       +.Ar edit-search-backwards .
       +.Pp
       +The possible functions are given in the following list, with
       +the possible modes listed beside each function.
       +.Bl -tag -width Ds
       +.It Ar edit-backspace Op edit, edit-search, edit-search-backwards
       +.It Ar edit-cursor-left Op edit, edit-search, edit-search-backwards
       +.It Ar edit-cursor-right Op edit, edit-search, edit-search-backwards
       +.It Ar edit-cursor-to-beginning Op edit, edit-search, edit-search-backwards
       +.It Ar edit-cursor-to-end Op edit, edit-search, edit-search-backwards
       +.It Ar edit-delete Op edit, edit-search, edit-search-backwards
       +.It Ar edit-discard Op edit, edit-search, edit-search-backwards
       +.It Ar edit-insert-text Op edit, edit-search, edit-search-backwards
       +.It Ar edit-next-command Op edit
       +.It Ar edit-next-search Op edit-search, edit-search-backwards
       +.It Ar edit-previous-command Op edit
       +.It Ar edit-previous-search Op edit-search, edit-search-backwards
       +.It Ar edit-submit Op edit
       +.It Ar edit-submit-backwards-search Op edit-search-backwards
       +.It Ar edit-submit-search Op edit-search
       +.It Ar substitute-no Op substitute
       +.It Ar substitute-no-all Op substitute
       +.It Ar substitute-yes Op substitute
       +.It Ar substitute-yes-all Op substitute
       +.El
       +.It Ar commands
       +.Pp
       +This is a list of statements of the form
       +.Pp
       +.Sy bind
       +.Aq func_name
       +.Aq text
       +.Pp
       +The possible functions are given in the following list.
       +.Bl -tag -width Ds
       +.It Ar close-view
       +.It Ar create-view
       +.It Ar quit
       +.It Ar substitute
       +.It Ar write
       +.It Ar write-quit
       +.El
       +.El
       +.Pp
       +If the
       +.Ar bindings
       +configuration or any part of it is left out, the
       +default is used.
       +.Sh LANGUAGE MAPPINGS
       +A language mapping defines a mapping for the text associated with each
       +key or command so the bindings still work with other keyboard layouts.
       +Language mappings may be defined by assigning
       +.Ar language-mapping
       +to a list of the following assignments, once for each language.
       +Note that any definition of
       +.Ar language-mapping
       +must come after
       +.Ar bindings .
       +.Bl -tag -width Ds
       +.It Ar language
       +.Pp
       +This is the language string for the key layout, as in
       +.Ar bindings .
       +.It Ar key-mapping
       +.Pp
       +This is a list of statements of the form
       +.Pp
       +.Sy map
       +.Aq foreign
       +.Aq native
       +.Pp
       +where
       +.Aq foreign
       +is the key text in the new language mapping and
       +.Aq native
       +is the key text given in
       +.Ar bindings .
       +.It Ar command-mapping
       +.Pp
       +This is the same as
       +.Ar key-mapping ,
       +but for the commands.
       +.El
       +.Sh SEE ALSO
       +.Xr ledit 1
       +.Sh AUTHORS
       +.An lumidify Aq Mt nobody@lumidify.org
   DIR diff --git a/leditrc.example b/leditrc.example
       t@@ -0,0 +1,300 @@
       +theme = {
       +        text-font = Monospace
       +        text-size = 12
       +        text-fg = 000000
       +        text-bg = FFFFFF
       +        cursor-fg = FFFFFF
       +        cursor-bg = 000000
       +        selection-fg = ffffff
       +        selection-bg = 000000
       +        bar-fg = 000000
       +        bar-bg = CCCCCC
       +        bar-cursor = 000000
       +        scrollbar-width = 10
       +        scrollbar-step = 20
       +        scrollbar-bg = CCCCCC
       +        scrollbar-fg = 000000
       +}
       +
       +bindings = {
       +        language = "English (US)"
       +        basic-keys = {
       +                bind backspace keysym backspace modes insert
       +                bind cursor-left keysym left modes visual|insert|normal
       +                bind cursor-right keysym right modes visual|insert|normal
       +                bind cursor-up keysym up modes visual|insert|normal
       +                bind cursor-down keysym down modes visual|insert|normal
       +                bind return-key keysym return modes insert mods any
       +                bind delete-key keysym delete modes insert mods any
       +                bind escape-key keysym escape modes normal|visual|insert mods any
       +                bind enter-insert text "i" modes normal|visual
       +                bind cursor-left text "h" modes normal|visual
       +                bind cursor-right text "l" modes normal|visual
       +                bind cursor-down text "j" modes normal|visual
       +                bind cursor-up text "k" modes normal|visual
       +                bind cursor-left text "h" modes normal|visual mods control
       +                bind toggle-hard-line-based text "t" modes normal|visual mods control
       +                bind cursor-right keysym space modes normal|visual
       +                bind cursor-down text "j" modes normal|visual mods control
       +                bind cursor-down text "n" modes normal|visual mods control
       +                bind cursor-up text "p" modes normal|visual mods control
       +                bind key-0 text "0" modes normal|visual
       +                bind push-1 text "1" modes normal|visual
       +                bind push-2 text "2" modes normal|visual
       +                bind push-3 text "3" modes normal|visual
       +                bind push-4 text "4" modes normal|visual
       +                bind push-5 text "5" modes normal|visual
       +                bind push-6 text "6" modes normal|visual
       +                bind push-7 text "7" modes normal|visual
       +                bind push-8 text "8" modes normal|visual
       +                bind push-9 text "9" modes normal|visual
       +                bind delete-forwards text "x" modes normal
       +                bind delete-backwards text "X" modes normal
       +                bind delete text "d" modes normal|visual
       +                bind yank text "y" modes normal|visual
       +                bind yank-lines text "Y" modes normal
       +                bind change text "c" modes normal|visual
       +                bind enter-visual text "v" modes normal
       +                bind switch-selection-end text "o" modes visual
       +                bind clipboard-copy text "c" modes visual mods control
       +                bind clipboard-paste text "v" modes insert mods control
       +                bind show-line text "g" modes normal|visual mods control
       +                bind enter-commandedit text ":" modes normal|visual
       +                bind enter-searchedit-backwards text "?" modes normal
       +                bind enter-searchedit-forwards text "/" modes normal
       +                bind search-next text "n" modes normal
       +                bind search-previous text "N" modes normal
       +                bind undo text "u" modes normal
       +                bind redo text "U" modes normal
       +                bind repeat-command text "." modes normal
       +                bind undo text "z" modes insert mods control
       +                bind redo text "y" modes insert mods control
       +                bind screen-up text "b" modes normal mods control
       +                bind screen-down text "f" modes normal mods control
       +                bind scroll-with-cursor-down text "e" modes normal mods control
       +                bind scroll-with-cursor-up text "y" modes normal mods control
       +                bind scroll-lines-down text "d" modes normal mods control
       +                bind scroll-lines-up text "u" modes normal mods control
       +                bind move-to-eol text "$" modes normal|visual
       +                bind next-word text "w" modes normal|visual
       +                bind next-word-end text "e" modes normal|visual
       +                bind next-bigword text "W" modes normal|visual
       +                bind next-bigword-end text "E" modes normal|visual
       +                bind previous-word text "b" modes normal|visual
       +                bind previous-bigword text "B" modes normal|visual
       +                bind move-to-line text "G" modes normal|visual
       +                bind join-lines text "J" modes normal
       +                bind insert-at-beginning text "I" modes normal
       +                bind paste-normal text "p" modes normal
       +                bind paste-normal-backwards text "P" modes normal
       +                bind append-after-eol text "A" modes normal
       +                bind append-after-cursor text "a" modes normal
       +                bind append-line-above text "O" modes normal
       +                bind append-line-below text "o" modes normal
       +                bind mark-line text "m" modes normal|visual
       +                bind jump-to-mark text "'" modes normal|visual
       +                bind change-to-eol text "C" modes normal
       +                bind delete-to-eol text "D" modes normal
       +                bind replace text "r" modes normal
       +                bind cursor-to-first-non-whitespace text "^" modes normal
       +                bind find-next-char-forwards text "t" modes normal|visual
       +                bind find-next-char-backwards text "T" modes normal|visual
       +                bind find-char-forwards text "f" modes normal|visual
       +                bind find-char-backwards text "F" modes normal|visual
       +                bind insert-text catchall modes insert
       +        }
       +        command-keys = {
       +                bind substitute-yes text "y" modes substitute
       +                bind substitute-yes-all text "Y" modes substitute
       +                bind substitute-no text "n" modes substitute
       +                bind substitute-no-all text "N" modes substitute
       +                bind edit-submit keysym return mods any modes edit
       +                bind edit-submit-search keysym return mods any modes edit-search
       +                bind edit-submit-backwards-search keysym return mods any modes edit-search-backwards
       +                bind edit-cursor-left keysym left modes edit|edit-search|edit-search-backwards
       +                bind edit-cursor-right keysym right modes edit|edit-search|edit-search-backwards
       +                bind edit-previous-command keysym up modes edit
       +                bind edit-next-command keysym down modes edit
       +                bind edit-previous-search keysym up modes edit-search|edit-search-backwards
       +                bind edit-next-search keysym down modes edit-search|edit-search-backwards
       +                bind edit-backspace keysym backspace modes edit|edit-search|edit-search-backwards
       +                bind edit-delete keysym delete modes edit|edit-search|edit-search-backwards
       +                bind edit-cursor-to-end keysym end modes edit|edit-search|edit-search-backwards
       +                bind edit-cursor-to-beginning keysym home modes edit|edit-search|edit-search-backwards
       +                bind edit-discard keysym escape modes edit|edit-search|edit-search-backwards
       +                bind edit-insert-text catchall modes edit|edit-search|edit-search-backwards
       +        }
       +        commands = {
       +                bind write-quit "wq"
       +                bind write "w"
       +                bind quit "q"
       +                bind create-view "v"
       +                bind close-view "c"
       +                bind substitute "s"
       +        }
       +}
       +
       +language-mapping = {
       +        language = "German"
       +        key-mapping = {
       +                map "z" "y"
       +                map "y" "z"
       +                map "Z" "Y"
       +                map "Ö" ":"
       +                map "_" "?"
       +                map "-" "/"
       +                map "ä" "'"
       +        }
       +        command-mapping = {
       +                map "wq" "wq"
       +                map "w" "w"
       +                map "q" "q"
       +                map "v" "v"
       +                map "c" "c"
       +                map "s" "s"
       +        }
       +}
       +
       +language-mapping = {
       +        language = "Hindi (Bolnagri)"
       +        key-mapping = {
       +                map "0" "0"
       +                map "1" "1"
       +                map "2" "2"
       +                map "3" "3"
       +                map "4" "4"
       +                map "5" "5"
       +                map "6" "6"
       +                map "7" "7"
       +                map "8" "8"
       +                map "9" "9"
       +                map "ा" "a"
       +                map "आ" "A"
       +                map "ब" "b"
       +                map "भ" "B"
       +                map "च" "c"
       +                map "छ" "C"
       +                map "द" "d"
       +                map "ध" "D"
       +                map "े" "e"
       +                map "ै" "E"
       +                map "ट" "f"
       +                map "ठ" "F"
       +                map "ग" "g"
       +                map "घ" "G"
       +                map "ह" "h"
       +                map "ि" "i"
       +                map "ी" "I"
       +                map "ज" "j"
       +                map "झ" "J"
       +                map "क" "k"
       +                map "ल" "l"
       +                map "म" "m"
       +                map "न" "n"
       +                map "ण" "N"
       +                map "ो" "o"
       +                map "ौ" "O"
       +                map "प" "p"
       +                map "फ" "P"
       +                map "र" "r"
       +                map "त" "t"
       +                map "थ" "T"
       +                map "ु" "u"
       +                map "ू" "U"
       +                map "ड" "v"
       +                map "व" "w"
       +                map "ॐ" "W"
       +                map "्" "x"
       +                map "ॉ" "X"
       +                map "य" "y"
       +                map "ञ" "Y"
       +                map "श" "z"
       +                map ":" ":"
       +                map "?" "?"
       +                map "/" "/"
       +                map "." "."
       +                map "$" "$"
       +                map "'" "'"
       +                map "^" "^"
       +        }
       +        command-mapping = {
       +                map "वग" "wq"
       +                map "व" "w"
       +                map "‌" "q"
       +                map "ड" "v"
       +                map "च" "c"
       +                map "स" "s"
       +        }
       +}
       +
       +language-mapping = {
       +        language = "Urdu (Pakistan)"
       +        key-mapping = {
       +                map "0" "0"
       +                map "1" "1"
       +                map "2" "2"
       +                map "3" "3"
       +                map "4" "4"
       +                map "5" "5"
       +                map "6" "6"
       +                map "7" "7"
       +                map "8" "8"
       +                map "9" "9"
       +                map "ا" "a"
       +                map "آ" "A"
       +                map "ب" "b"
       +                map "." "B"
       +                map "چ" "c"
       +                map "ث" "C"
       +                map "د" "d"
       +                map "ڈ" "D"
       +                map "ع" "e"
       +                map "ٰ" "E"
       +                map "ف" "f"
       +                map "ّ" "F"
       +                map "گ" "g"
       +                map "غ" "G"
       +                map "ح" "h"
       +                map "ی" "i"
       +                map "ِ" "I"
       +                map "ج" "j"
       +                map "ض" "J"
       +                map "ک" "k"
       +                map "ل" "l"
       +                map "م" "m"
       +                map "ن" "n"
       +                map "ں" "N"
       +                map "ہ" "o"
       +                map "ۃ" "O"
       +                map "پ" "p"
       +                map "ُ" "P"
       +                map "ر" "r"
       +                map "ت" "t"
       +                map "ٹ" "T"
       +                map "ء" "u"
       +                map "ئ" "U"
       +                map "ط" "v"
       +                map "و" "w"
       +                map "ؤ" "W"
       +                map "ش" "x"
       +                map "ژ" "X"
       +                map "ے" "y"
       +                map "َ" "Y"
       +                map "ز" "z"
       +                map ":" ":"
       +                map "؟" "?"
       +                map "/" "/"
       +                map "۔" "."
       +                map "$" "$"
       +                map "'" "'"
       +                map "^" "^"
       +        }
       +        command-mapping = {
       +                map "وگ" "wq"
       +                map "و" "w"
       +                map "ق" "q"
       +                map "ط" "v"
       +                map "چ" "c"
       +                map "س" "s"
       +        }
       +}
   DIR diff --git a/memory.c b/memory.c
       t@@ -10,6 +10,7 @@
        static void
        fatal_err(const char *msg) {
                fprintf(stderr, "%s", msg);
       +        /* FIXME: maybe don't cleanup here - it will probably fail anyways */
                ledit_cleanup();
                exit(1);
        }
       t@@ -82,6 +83,23 @@ ledit_strcat(const char *str1, const char *str2) {
                return ret;
        }
        
       +char *
       +print_fmt(char *fmt, ...) {
       +        va_list args;
       +        va_start(args, fmt);
       +        int len = vsnprintf(NULL, 0, fmt, args);
       +        /* FIXME: what should be done on error? */
       +        if (len < 0)
       +                fatal_err("Error in vsnprintf called from print_fmt");
       +        /* FIXME: overflow */
       +        char *str = ledit_malloc(len + 1);
       +        va_end(args);
       +        va_start(args, fmt);
       +        vsnprintf(str, len + 1, fmt, args);
       +        va_end(args);
       +        return str;
       +}
       +
        /*
         * This (reallocarray) is from OpenBSD (adapted to exit on error):
         * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
       t@@ -96,11 +114,11 @@ ledit_strcat(const char *str1, const char *str2) {
        void *
        ledit_reallocarray(void *optr, size_t nmemb, size_t size)
        {
       -        if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
       -            nmemb > 0 && SIZE_MAX / nmemb < size) {
       +        if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
       +            nmemb > 0 && SIZE_MAX / nmemb < size) {
                        err_overflow();
       -        }
       -        return ledit_realloc(optr, size * nmemb);
       +        }
       +        return ledit_realloc(optr, size * nmemb);
        }
        
        void
   DIR diff --git a/memory.h b/memory.h
       t@@ -3,6 +3,7 @@
        
        #include <stddef.h>
        #include <stdint.h>
       +#include <stdarg.h>
        
        /*
         * These functions all wrap the regular functions but exit on error.
       t@@ -19,6 +20,11 @@ void *ledit_realloc(void *ptr, size_t size);
         */
        char *ledit_strcat(const char *str1, const char *str2);
        
       +/* This acts like snprintf but automatically allocates
       +   a string of the appropriate size.
       +   Like the other functions here, it exits on error. */
       +char *print_fmt(char *fmt, ...);
       +
        /*
         * This is like OpenBSD's reallocarray but exits on error.
         */
   DIR diff --git a/theme.c b/theme.c
       t@@ -1,57 +0,0 @@
       -#include <stddef.h>
       -
       -#include <X11/Xlib.h>
       -#include <X11/Xft/Xft.h>
       -
       -#include "memory.h"
       -#include "common.h"
       -#include "theme.h"
       -#include "theme_config.h"
       -
       -ledit_theme *
       -theme_create(ledit_common *common) {
       -        ledit_theme *theme = ledit_malloc(sizeof(ledit_theme));
       -        theme->scrollbar_width = SCROLLBAR_WIDTH;
       -        theme->scrollbar_step = SCROLLBAR_STEP;
       -        theme->text_font = TEXT_FONT;
       -        theme->text_size = TEXT_SIZE;
       -        theme->text_fg_hex = TEXT_FG;
       -        theme->text_bg_hex = TEXT_BG;
       -        theme->cursor_fg_hex = CURSOR_FG;
       -        theme->cursor_bg_hex = CURSOR_BG;
       -        theme->selection_fg_hex = SELECTION_FG;
       -        theme->selection_bg_hex = SELECTION_BG;
       -        theme->bar_fg_hex = BAR_FG;
       -        theme->bar_bg_hex = BAR_BG;
       -        theme->bar_cursor_hex = BAR_CURSOR;
       -        theme->scrollbar_fg_hex = SCROLLBAR_FG;
       -        theme->scrollbar_bg_hex = SCROLLBAR_BG;
       -        XftColorAllocName(common->dpy, common->vis, common->cm, TEXT_FG, &theme->text_fg);
       -        XftColorAllocName(common->dpy, common->vis, common->cm, TEXT_BG, &theme->text_bg);
       -        XftColorAllocName(common->dpy, common->vis, common->cm, CURSOR_FG, &theme->cursor_fg);
       -        XftColorAllocName(common->dpy, common->vis, common->cm, CURSOR_BG, &theme->cursor_bg);
       -        XftColorAllocName(common->dpy, common->vis, common->cm, SELECTION_FG, &theme->selection_fg);
       -        XftColorAllocName(common->dpy, common->vis, common->cm, SELECTION_BG, &theme->selection_bg);
       -        XftColorAllocName(common->dpy, common->vis, common->cm, BAR_FG, &theme->bar_fg);
       -        XftColorAllocName(common->dpy, common->vis, common->cm, BAR_BG, &theme->bar_bg);
       -        XftColorAllocName(common->dpy, common->vis, common->cm, BAR_CURSOR, &theme->bar_cursor);
       -        XftColorAllocName(common->dpy, common->vis, common->cm, SCROLLBAR_FG, &theme->scrollbar_fg);
       -        XftColorAllocName(common->dpy, common->vis, common->cm, SCROLLBAR_BG, &theme->scrollbar_bg);
       -        return theme;
       -}
       -
       -void
       -theme_destroy(ledit_common *common, ledit_theme *theme) {
       -        XftColorFree(common->dpy, common->vis, common->cm, &theme->text_fg);
       -        XftColorFree(common->dpy, common->vis, common->cm, &theme->text_bg);
       -        XftColorFree(common->dpy, common->vis, common->cm, &theme->cursor_fg);
       -        XftColorFree(common->dpy, common->vis, common->cm, &theme->cursor_bg);
       -        XftColorFree(common->dpy, common->vis, common->cm, &theme->selection_fg);
       -        XftColorFree(common->dpy, common->vis, common->cm, &theme->selection_bg);
       -        XftColorFree(common->dpy, common->vis, common->cm, &theme->bar_fg);
       -        XftColorFree(common->dpy, common->vis, common->cm, &theme->bar_bg);
       -        XftColorFree(common->dpy, common->vis, common->cm, &theme->bar_cursor);
       -        XftColorFree(common->dpy, common->vis, common->cm, &theme->scrollbar_fg);
       -        XftColorFree(common->dpy, common->vis, common->cm, &theme->scrollbar_bg);
       -        free(theme);
       -}
   DIR diff --git a/theme.h b/theme.h
       t@@ -1,39 +0,0 @@
       -#ifndef _THEME_H_
       -#define _THEME_H_
       -
       -#include <X11/Xft/Xft.h>
       -#include "common.h"
       -
       -typedef struct {
       -        int scrollbar_width;
       -        int scrollbar_step;
       -        int text_size;
       -        XftColor text_fg;
       -        XftColor text_bg;
       -        XftColor cursor_fg;
       -        XftColor cursor_bg;
       -        XftColor selection_fg;
       -        XftColor selection_bg;
       -        XftColor bar_fg;
       -        XftColor bar_bg;
       -        XftColor bar_cursor;
       -        XftColor scrollbar_fg;
       -        XftColor scrollbar_bg;
       -        const char *text_font;
       -        const char *text_fg_hex;
       -        const char *text_bg_hex;
       -        const char *cursor_fg_hex;
       -        const char *cursor_bg_hex;
       -        const char *selection_fg_hex;
       -        const char *selection_bg_hex;
       -        const char *bar_fg_hex;
       -        const char *bar_bg_hex;
       -        const char *bar_cursor_hex;
       -        const char *scrollbar_fg_hex;
       -        const char *scrollbar_bg_hex;
       -} ledit_theme;
       -
       -ledit_theme *theme_create(ledit_common *common);
       -void theme_destroy(ledit_common *common, ledit_theme *theme);
       -
       -#endif
   DIR diff --git a/theme_config.h b/theme_config.h
       t@@ -1,7 +1,19 @@
       +#ifndef _THEME_CONFIG_H_
       +#define _THEME_CONFIG_H_
       +
       +/***********************
       + * Theme configuration *
       + ***********************/
       +
       +/* Note: Integer values have to be given as strings because
       +   that simplifies some things in the config parser. */
       +
       +/* FIXME: Check what happens when 0 is given for integer values */
       +
        /* font used for all text */
        static const char *TEXT_FONT = "Monospace";
        /* size used for text in points or whatever pango uses */
       -static const int TEXT_SIZE = 12;
       +static const char *TEXT_SIZE = "12";
        /* text color of main text area */
        static const char *TEXT_FG = "#000000";
        /* background color of main text area */
       t@@ -24,10 +36,12 @@ static const char *BAR_CURSOR = "#000000";
        
        /* FIXME: give in units other than pixels */
        /* scrollbar width in pixels */
       -static const int SCROLLBAR_WIDTH = 10;
       +static const char *SCROLLBAR_WIDTH = "10";
        /* number of pixels scrolled with every scroll event */
       -static const int SCROLLBAR_STEP = 20;
       +static const char *SCROLLBAR_STEP = "20";
        /* background color of scrollbar */
        static const char *SCROLLBAR_BG = "#CCCCCC";
        /* color of scrollbar handle */
        static const char *SCROLLBAR_FG = "#000000";
       +
       +#endif /* _THEME_CONFIG_H_ */
   DIR diff --git a/txtbuf.c b/txtbuf.c
       t@@ -1,5 +1,7 @@
       +#include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
       +#include <stdarg.h>
        
        #include "util.h"
        #include "memory.h"
       t@@ -14,16 +16,49 @@ txtbuf_new(void) {
                return buf;
        }
        
       +txtbuf *
       +txtbuf_new_from_char(char *str) {
       +        txtbuf *buf = ledit_malloc(sizeof(txtbuf));
       +        buf->text = ledit_strdup(str);
       +        buf->len = strlen(str);
       +        buf->cap = buf->len + 1;
       +        return buf;
       +}
       +
       +txtbuf *
       +txtbuf_new_from_char_len(char *str, size_t len) {
       +        txtbuf *buf = ledit_malloc(sizeof(txtbuf));
       +        buf->text = ledit_strndup(str, len);
       +        buf->len = len;
       +        buf->cap = len + 1;
       +        return buf;
       +}
       +
       +void
       +txtbuf_fmt(txtbuf *buf, char *fmt, ...) {
       +        va_list args;
       +        va_start(args, fmt);
       +        int len = vsnprintf(buf->text, buf->cap, fmt, args);
       +        /* FIXME: len can never be negative, right? */
       +        /* FIXME: maybe also shrink here */
       +        if ((size_t)len >= buf->cap) {
       +                va_end(args);
       +                va_start(args, fmt);
       +                txtbuf_resize(buf, len);
       +                vsnprintf(buf->text, buf->cap, fmt, args);
       +        }
       +        buf->len = len;
       +        va_end(args);
       +}
       +
        void
        txtbuf_resize(txtbuf *buf, size_t sz) {
                /* always leave room for extra \0 */
       -        /* FIXME: '\0' isn't actually used anywhere */
                size_t cap = ideal_array_size(buf->cap, add_sz(sz, 1));
                if (cap != buf->cap) {
                        buf->text = ledit_realloc(buf->text, cap);
                        buf->cap = cap;
                }
       -        ledit_assert(buf->cap >= add_sz(sz, 1));
        }
        
        void
       t@@ -38,6 +73,7 @@ void
        txtbuf_copy(txtbuf *dst, txtbuf *src) {
                txtbuf_resize(dst, src->len);
                memcpy(dst->text, src->text, src->len);
       +        dst->text[src->len] = '\0';
                dst->len = src->len;
        }
        
       t@@ -47,3 +83,22 @@ txtbuf_dup(txtbuf *src) {
                txtbuf_copy(dst, src);
                return dst;
        }
       +
       +int
       +txtbuf_cmp(txtbuf *buf1, txtbuf *buf2) {
       +        /* FIXME: I guess strcmp would be possible as well since it's nul-terminated now */
       +        /* FIXME: Test this because I was tired while writing it */
       +        int cmp = strncmp(buf1->text, buf2->text, LEDIT_MIN(buf1->len, buf2->len));
       +        if (cmp == 0) {
       +                if (buf1->len < buf2->len)
       +                        return -1;
       +                else if (buf1->len > buf2->len)
       +                        return 1;
       +        }
       +        return cmp;
       +}
       +
       +int
       +txtbuf_eql(txtbuf *buf1, txtbuf *buf2) {
       +        return txtbuf_cmp(buf1, buf2) == 0;
       +}
   DIR diff --git a/txtbuf.h b/txtbuf.h
       t@@ -5,6 +5,7 @@
        
        /*
         * txtbuf is really just a string data type that is badly named.
       + * The stored text is always nul-terminated.
         */
        
        typedef struct {
       t@@ -18,6 +19,35 @@ typedef struct {
        txtbuf *txtbuf_new(void);
        
        /*
       + * Create a new txtbuf, initializing it with the nul-terminated
       + * string 'str'. The input string is copied.
       + */
       +txtbuf *txtbuf_new_from_char(char *str);
       +
       +/*
       + * Create a new txtbuf, initializing it with the string 'str'
       + * of length 'len'. The input string is copied.
       + */
       +txtbuf *txtbuf_new_from_char_len(char *str, size_t len);
       +
       +/*
       + * Replace the stored text in 'buf' with the text generated by
       + * 'snprintf' when called with the given format string and args.
       + */
       +void txtbuf_fmt(txtbuf *buf, char *fmt, ...);
       +
       +/*
       + * Compare the text of two txtbuf's like 'strcmp'.
       + */
       +int txtbuf_cmp(txtbuf *buf1, txtbuf *buf2);
       +
       +/*
       + * Convenience function for calling 'txtbuf_cmp' and checking if the
       + * return value is 0, i.e. the strings are equal.
       + */
       +int txtbuf_eql(txtbuf *buf1, txtbuf *buf2);
       +
       +/*
         * Make sure the txtbuf has space for at least the given size,
         * plus '\0' at the end.
         */
   DIR diff --git a/uglycrap.h b/uglycrap.h
       t@@ -0,0 +1,15 @@
       +#ifndef _UGLYCRAP_H_
       +#define _UGLYCRAP_H_
       +
       +/* FIXME: Figure out where to put it - it would make sens to put it in
       +   keys_command.h, but it is needed by view.h to make the command mode
       +   per-view, but I don't want view.* to depend on keys_command.h */
       +
       +typedef enum command_mode {
       +        CMD_EDIT = 1,        /* edit command */
       +        CMD_EDITSEARCH = 2,  /* edit search term */
       +        CMD_EDITSEARCHB = 4, /* edit search term for backwards search */
       +        CMD_SUBSTITUTE = 8   /* confirm substitution */
       +} command_mode;
       +
       +#endif
   DIR diff --git a/undo.h b/undo.h
       t@@ -73,6 +73,7 @@ void undo_change_mode_group(undo_stack *undo);
        /*
         * Push an insert action onto the undo stack.
         * See documentation at top for details on the other arguments.
       + * 'text' is copied, so the original should be freed.
         */
        void undo_push_insert(
            undo_stack *undo, txtbuf *text,
       t@@ -83,6 +84,7 @@ void undo_push_insert(
        /*
         * Push an delete action onto the undo stack.
         * See documentation at top for details on the other arguments.
       + * 'text' is copied, so the original should be freed.
         */
        void undo_push_delete(
            undo_stack *undo, txtbuf *text,
   DIR diff --git a/util.c b/util.c
       t@@ -1,3 +1,4 @@
       +#include <string.h>
        #include <stddef.h>
        #include "memory.h"
        
       t@@ -9,6 +10,15 @@ next_utf8(char *str) {
        }
        
        size_t
       +next_utf8_len(char *str, size_t len) {
       +        size_t cur = 0;
       +        while (cur < len && (str[cur] & 0xC0) == 0x80)
       +                cur++;
       +        return cur;
       +}
       +
       +/* FIXME: change these to macros somehow */
       +size_t
        add_sz(size_t a, size_t b) {
                if (a > SIZE_MAX - b)
                        err_overflow();
       t@@ -38,3 +48,13 @@ sort_range(size_t *l1, size_t *b1, size_t *l2, size_t *b2) {
                        swap_sz(b1, b2);
                }
        }
       +
       +int
       +str_array_equal(char *terminated, char *array, size_t len) {
       +        if (!strncmp(terminated, array, len)) {
       +                /* 'terminated' and 'array' are equal for the first 'len'
       +                   characters, so this index in 'terminated' must exist */
       +                return terminated[len] == '\0';
       +        }
       +        return 0;
       +}
   DIR diff --git a/util.h b/util.h
       t@@ -9,6 +9,13 @@
        char *next_utf8(char *str);
        
        /*
       + * Same as above, but also works with non-nul-terminated strings.
       + * Instead of a pointer, the index of the next utf8 char is
       + * returned.
       + */
       +size_t next_utf8_len(char *str, size_t len);
       +
       +/*
         * Add size_t values and abort if overflow would occur.
         * FIXME: Maybe someone with actual experience could tell me
         * if this overflow checking actually works.
       t@@ -19,4 +26,25 @@ size_t add_sz3(size_t a, size_t b, size_t c);
        void swap_sz(size_t *a, size_t *b);
        void sort_range(size_t *l1, size_t *b1, size_t *l2, size_t *b2);
        
       +/*
       + * Compare the nul-terminated string 'terminated' with the char
       + * array 'array' with length 'len'.
       + * Returns non-zero if they are equal, 0 otherwise.
       + */
       +/* Note: this doesn't work if array contains '\0'. */
       +int str_array_equal(char *terminated, char *array, size_t len);
       +
       +#define LEDIT_MIN(x, y) ((x) < (y) ? (x) : (y))
       +#define LEDIT_MAX(x, y) ((x) > (y) ? (x) : (y))
       +
       +/* Apparently, ISO C99 requires at least one argument for
       +   variadic macros, so there are two versions of the macro here. */
       +#ifdef LEDIT_DEBUG
       +        #define ledit_debug(fmt) do {fprintf(stderr, "%s:%d: " fmt, __FILE__, __LINE__);} while (0)
       +        #define ledit_debug_fmt(fmt, ...) do {fprintf(stderr, "%s:%d: " fmt, __FILE__, __LINE__, __VA_ARGS__);} while (0)
       +#else
       +        #define ledit_debug(fmt) do {} while (0)
       +        #define ledit_debug_fmt(fmt, ...) do {} while (0)
       +#endif
       +
        #endif
   DIR diff --git a/view.c b/view.c
       t@@ -18,10 +18,10 @@
        #include "txtbuf.h"
        #include "undo.h"
        #include "cache.h"
       -#include "theme.h"
        #include "window.h"
        #include "buffer.h"
        #include "assert.h"
       +#include "configparser.h"
        
        /* Basic attributes set for all text. */
        static PangoAttrList *basic_attrs = NULL;
       t@@ -96,7 +96,7 @@ view_set_mode(ledit_view *view, ledit_mode mode) {
        }
        
        ledit_view *
       -view_create(ledit_buffer *buffer, ledit_theme *theme, ledit_mode mode, size_t line, size_t pos) {
       +view_create(ledit_buffer *buffer, ledit_mode mode, size_t line, size_t pos) {
                if (basic_attrs == NULL) {
                        basic_attrs = pango_attr_list_new();
                        #if PANGO_VERSION_CHECK(1, 44, 0)
       t@@ -108,8 +108,7 @@ view_create(ledit_buffer *buffer, ledit_theme *theme, ledit_mode mode, size_t li
                ledit_view *view = ledit_malloc(sizeof(ledit_view));
                view->mode = mode;
                view->buffer = buffer;
       -        view->window = window_create(buffer->common, theme, mode);
       -        view->theme = theme;
       +        view->window = window_create(buffer->common, mode);
                view->cache = cache_create(buffer->common->dpy);
                view->lock_text = NULL;
                view->cur_action = (struct action){ACTION_NONE, NULL};
       t@@ -328,12 +327,13 @@ get_pango_attributes(size_t start_byte, size_t end_byte, XRenderColor fg, XRende
        /* this takes layout directly to possibly avoid infinite recursion */
        static void
        set_line_layout_attrs(ledit_view *view, size_t line, PangoLayout *layout) {
       +        ledit_theme *theme = config_get_theme();
                ledit_line *ll = buffer_get_line(view->buffer, line);
                ledit_view_line *vl = view_get_line(view, line);
                PangoAttrList *list = NULL;
                if (view->sel_valid) {
       -                XRenderColor fg = view->theme->selection_fg.color;
       -                XRenderColor bg = view->theme->selection_bg.color;
       +                XRenderColor fg = theme->selection_fg.color;
       +                XRenderColor bg = theme->selection_bg.color;
                        ledit_range sel = view->sel;
                        sort_range(&sel.line1, &sel.byte1, &sel.line2, &sel.byte2);
                        if (sel.line1 < line && sel.line2 > line) {
       t@@ -349,8 +349,8 @@ set_line_layout_attrs(ledit_view *view, size_t line, PangoLayout *layout) {
                                list = get_pango_attributes(0, sel.byte2, fg, bg);
                        }
                } else if (vl->cursor_index_valid) {
       -                XRenderColor fg = view->theme->cursor_fg.color;
       -                XRenderColor bg = view->theme->cursor_bg.color;
       +                XRenderColor fg = theme->cursor_fg.color;
       +                XRenderColor bg = theme->cursor_bg.color;
                        /* FIXME: does just adding one really do the right thing? */
                        list = get_pango_attributes(vl->cursor_index, vl->cursor_index + 1, fg, bg);
                }
       t@@ -392,6 +392,7 @@ line_visible_callback(void *data, size_t line) {
        /* FIXME: standardize variable names (line/line_index, etc.) */
        void
        render_line(ledit_view *view, size_t line_index) {
       +        ledit_theme *theme = config_get_theme();
                /* FIXME: check for <= 0 on size */
                ledit_view_line *ll = view_get_line(view, line_index);
                ledit_assert(!ll->h_dirty); /* FIXME */
       t@@ -430,8 +431,8 @@ render_line(ledit_view *view, size_t line_index) {
                        pix->h = new_h;
                        XftDrawChange(pix->draw, pix->pixmap);
                }
       -        XftDrawRect(pix->draw, &view->theme->text_bg, 0, 0, ll->w, ll->h);
       -        pango_xft_render_layout(pix->draw, &view->theme->text_fg, layout, 0, 0);
       +        XftDrawRect(pix->draw, &theme->text_bg, 0, 0, ll->w, ll->h);
       +        pango_xft_render_layout(pix->draw, &theme->text_fg, layout, 0, 0);
                ll->dirty = 0;
        }
        
       t@@ -1839,6 +1840,7 @@ view_button_handler(void *data, XEvent *event) {
        
        static void
        view_redraw_text(ledit_view *view) {
       +        ledit_theme *theme = config_get_theme();
                int h = 0;
                int cur_line_y = 0;
                int cursor_displayed = 0;
       t@@ -1884,7 +1886,7 @@ view_redraw_text(ledit_view *view) {
                        h += vline->h;
                }
        
       -        XSetForeground(view->buffer->common->dpy, view->window->gc, view->theme->cursor_bg.pixel);
       +        XSetForeground(view->buffer->common->dpy, view->window->gc, theme->cursor_bg.pixel);
                PangoRectangle strong, weak;
                ledit_line *cur_line = buffer_get_line(view->buffer, view->cur_line);
                PangoLayout *layout = get_pango_layout(view, view->cur_line);
   DIR diff --git a/view.h b/view.h
       t@@ -10,8 +10,8 @@
        #include "common.h"
        #include "txtbuf.h"
        #include "window.h"
       -#include "theme.h"
        #include "cache.h"
       +#include "uglycrap.h"
        
        typedef struct ledit_view ledit_view;
        
       t@@ -49,24 +49,14 @@ typedef struct {
                char h_dirty;              /* whether height needs to be recalculated */
        } ledit_view_line;
        
       -/* FIXME: It's kind of ugly to put this here instead of keys_command.h,
       -   but it has to be per-view, so I don't know any other option. */
       -enum ledit_command_type {
       -        CMD_EDIT,        /* edit command */
       -        CMD_EDITSEARCH,  /* edit search term */
       -        CMD_EDITSEARCHB, /* edit search term for backwards search */
       -        CMD_SUBSTITUTE   /* confirm substitution */
       -};
       -
        struct ledit_view {
                ledit_buffer *buffer;     /* parent buffer */
                ledit_window *window;     /* window showing this view */
       -        ledit_theme *theme;       /* current theme in use */
                ledit_cache *cache;       /* cache for pixmaps and pango layouts */
                ledit_view_line *lines;   /* array of lines, stored as gap buffer */
                char *lock_text;          /* text to show if view is locked, i.e. no edits allowed */
                /* current command type - used by key handler in keys_command.c */
       -        enum ledit_command_type cur_command_type;
       +        command_mode cur_command_type;
                struct action cur_action; /* current action to execute on key press */
                size_t lines_cap;         /* size of lines array */
                size_t lines_gap;         /* position of gap for line gap buffer */
       t@@ -98,14 +88,11 @@ enum delete_mode {
        void view_set_mode(ledit_view *view, ledit_mode mode);
        
        /*
       - * Create a view with associated buffer 'buffer' and theme 'theme'.
       + * Create a view with associated buffer 'buffer'
         * The initial mode, line, and byte position are given, respectively,
         * by 'mode', 'line', and 'pos'.
         */
       -ledit_view *view_create(
       -    ledit_buffer *buffer, ledit_theme *theme,
       -    ledit_mode mode, size_t line, size_t pos
       -);
       +ledit_view *view_create(ledit_buffer *buffer, ledit_mode mode, size_t line, size_t pos);
        
        /*
         * Lock a view.
   DIR diff --git a/window.c b/window.c
       t@@ -14,7 +14,6 @@
        #include <X11/extensions/Xdbe.h>
        
        #include "util.h"
       -#include "theme.h"
        #include "memory.h"
        #include "common.h"
        #include "txtbuf.h"
       t@@ -23,6 +22,7 @@
        #include "config.h"
        #include "assert.h"
        #include "draw_util.h"
       +#include "configparser.h"
        
        /* FIXME: Everything to do with the bottom bar is extremely hacky */
        struct bottom_bar {
       t@@ -94,10 +94,11 @@ window_get_primary_clipboard_buffer(void) {
        /* FIXME: guard against negative width/height */
        static void
        recalc_text_size(ledit_window *window) {
       +        ledit_theme *theme = config_get_theme();
                int bar_h = window->bb->mode_h;
                if (window->bottom_text_shown || window->message_shown)
                        bar_h = window->bb->line_h;
       -        window->text_w = window->w - window->theme->scrollbar_width;
       +        window->text_w = window->w - theme->scrollbar_width;
                window->text_h = window->h - bar_h;
                if (window->text_w < 0)
                        window->text_w = 0;
       t@@ -120,12 +121,13 @@ resize_line_text(ledit_window *window, int min_size) {
        
        static void
        redraw_line_text(ledit_window *window) {
       +        ledit_theme *theme = config_get_theme();
                /* FIXME: set_text doesn't really belong here */
                pango_layout_set_text(window->bb->line, window->bb->line_text, window->bb->line_len);
                pango_layout_get_pixel_size(window->bb->line, &window->bb->line_w, &window->bb->line_h);
                draw_grow(window, window->bb->line_draw, window->bb->line_w, window->bb->line_h);
       -        XftDrawRect(window->bb->line_draw->xftdraw, &window->theme->bar_bg, 0, 0, window->bb->line_w, window->bb->line_h);
       -        pango_xft_render_layout(window->bb->line_draw->xftdraw, &window->theme->bar_fg, window->bb->line, 0, 0);
       +        XftDrawRect(window->bb->line_draw->xftdraw, &theme->bar_bg, 0, 0, window->bb->line_w, window->bb->line_h);
       +        pango_xft_render_layout(window->bb->line_draw->xftdraw, &theme->bar_fg, window->bb->line, 0, 0);
                recalc_text_size(window);
                window->redraw = 1;
        }
       t@@ -332,6 +334,7 @@ window_hide_message(ledit_window *window) {
        
        void
        window_set_mode(ledit_window *window, ledit_mode mode) {
       +        ledit_theme *theme = config_get_theme();
                window->mode = mode;
                char *text;
                switch (mode) {
       t@@ -353,8 +356,8 @@ window_set_mode(ledit_window *window, ledit_mode mode) {
                free(final_text);
                pango_layout_get_pixel_size(window->bb->mode, &window->bb->mode_w, &window->bb->mode_h);
                draw_grow(window, window->bb->mode_draw, window->bb->mode_w, window->bb->mode_h);
       -        XftDrawRect(window->bb->mode_draw->xftdraw, &window->theme->bar_bg, 0, 0, window->bb->mode_w, window->bb->mode_h);
       -        pango_xft_render_layout(window->bb->mode_draw->xftdraw, &window->theme->bar_fg, window->bb->mode, 0, 0);
       +        XftDrawRect(window->bb->mode_draw->xftdraw, &theme->bar_bg, 0, 0, window->bb->mode_w, window->bb->mode_h);
       +        pango_xft_render_layout(window->bb->mode_draw->xftdraw, &theme->bar_fg, window->bb->mode, 0, 0);
                recalc_text_size(window);
                window->redraw = 1;
        }
       t@@ -510,10 +513,13 @@ xximspot(ledit_window *window, int x, int y) {
        }
        
        ledit_window *
       -window_create(ledit_common *common, ledit_theme *theme, ledit_mode mode) {
       +window_create(ledit_common *common, ledit_mode mode) {
                XGCValues gcv;
        
       +        ledit_theme *theme = config_get_theme();
       +
                ledit_window *window = ledit_malloc(sizeof(ledit_window));
       +        window->first_resize = 1;
        
                window->mode = mode;
                window->scroll_dragging = 0;
       t@@ -569,7 +575,6 @@ window_create(ledit_common *common, ledit_theme *theme, ledit_mode mode) {
                XSetWMProtocols(common->dpy, window->xwin, &window->wm_delete_msg, 1);
        
                window->common = common;
       -        window->theme = theme;
        
                window->bb = ledit_malloc(sizeof(bottom_bar));
                window->bb->mode = pango_layout_new(window->context);
       t@@ -652,6 +657,7 @@ window_destroy(ledit_window *window) {
                /*g_object_unref(window->context);*/
                g_object_unref(window->fontmap);
        
       +        XFreeGC(window->common->dpy, window->gc);
                if (window->spotlist)
                        XFree(window->spotlist);
                XDestroyWindow(window->common->dpy, window->xwin);
       t@@ -671,7 +677,8 @@ window_cleanup(void) {
        
        void
        window_clear(ledit_window *window) {
       -        XSetForeground(window->common->dpy, window->gc, window->theme->text_bg.pixel);
       +        ledit_theme *theme = config_get_theme();
       +        XSetForeground(window->common->dpy, window->gc, theme->text_bg.pixel);
                XFillRectangle(
                    window->common->dpy, window->drawable, window->gc, 0, 0, window->w, window->h
                );
       t@@ -679,7 +686,7 @@ window_clear(ledit_window *window) {
        
        void
        window_redraw(ledit_window *window) {
       -        ledit_theme *t = window->theme;
       +        ledit_theme *t = config_get_theme();
                if (window->scroll_max > window->text_h) {
                        XSetForeground(window->common->dpy, window->gc, t->scrollbar_bg.pixel);
                        XFillRectangle(
       t@@ -791,7 +798,7 @@ window_handle_filtered_events(ledit_window *window) {
                if (window->last_resize_valid) {
                        clock_gettime(CLOCK_MONOTONIC, &now);
                        ledit_timespecsub(&now, &window->last_resize, &elapsed);
       -                if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= RESIZE_TICK) {
       +                if (window->first_resize || elapsed.tv_sec > 0 || elapsed.tv_nsec >= RESIZE_TICK) {
                                window_resize(
                                    window,
                                    window->last_resize_event.xconfigure.width,
       t@@ -800,6 +807,7 @@ window_handle_filtered_events(ledit_window *window) {
                                window->last_resize = now;
                                window->last_resize_valid = 0;
                                window->redraw = 1;
       +                        window->first_resize = 0;
                        }
                }
        }
       t@@ -1036,6 +1044,7 @@ window_register_motion(ledit_window *window, XEvent *event) {
        /* FIXME: improve set_scroll_pos; make it a bit clearer */
        void
        window_button_press(ledit_window *window, XEvent *event, int scroll_num) {
       +        ledit_theme *theme = config_get_theme();
                int x = event->xbutton.x;
                int y = event->xbutton.y;
                double scroll_h, scroll_y;
       t@@ -1062,7 +1071,7 @@ window_button_press(ledit_window *window, XEvent *event, int scroll_num) {
                                break;
                        case Button4:
                        case Button5:
       -                        window->scroll_offset += scroll_num * window->theme->scrollbar_step;
       +                        window->scroll_offset += scroll_num * theme->scrollbar_step;
                                if (window->scroll_offset < 0)
                                        window->scroll_offset = 0;
                                if (window->scroll_offset + window->text_h > window->scroll_max) {
   DIR diff --git a/window.h b/window.h
       t@@ -16,7 +16,6 @@
        #include <X11/extensions/Xdbe.h>
        #include <pango/pangoxft.h>
        
       -#include "theme.h"
        #include "common.h"
        #include "txtbuf.h"
        
       t@@ -65,6 +64,12 @@ typedef struct {
                int last_scroll_valid;
                int last_motion_valid;
                int last_resize_valid;
       +        /* This is a hack to make the first resizing of the window go quickly instead
       +           of being delayed due to the event filtering - this is noticeable in tiling
       +           window managers that resize the window immediately after it is created.
       +           The whole event filtering system needs to be rethought anyways, but this
       +           at least sort of works for the time being. (FIXME) */
       +        int first_resize;
                int scroll_num;
                int scroll_delta;
        
       t@@ -75,7 +80,6 @@ typedef struct {
                XVaNestedList spotlist;
        
                ledit_common *common;
       -        ledit_theme *theme;
        
                /* various callbacks */
                void (*paste_callback)(void *, char *, size_t);
       t@@ -94,7 +98,7 @@ typedef struct {
        /*
         * Create a window with initial mode 'mode'.
         */
       -ledit_window *window_create(ledit_common *common, ledit_theme *theme, ledit_mode mode);
       +ledit_window *window_create(ledit_common *common, ledit_mode mode);
        
        /*
         * Destroy a window.