URI: 
       tAdd initial work that was done without version control - 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 78088f20cdb93ead1b980f3a4092ce0a1227aafa
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Thu,  1 Apr 2021 19:31:23 +0200
       
       Add initial work that was done without version control
       
       Diffstat:
         A .gitignore                          |       4 ++++
         A Makefile                            |      24 ++++++++++++++++++++++++
         A ledit.c                             |     859 +++++++++++++++++++++++++++++++
       
       3 files changed, 887 insertions(+), 0 deletions(-)
       ---
   DIR diff --git a/.gitignore b/.gitignore
       t@@ -0,0 +1,4 @@
       +tmp
       +ledit
       +*.core
       +*.o
   DIR diff --git a/Makefile b/Makefile
       t@@ -0,0 +1,24 @@
       +.POSIX:
       +
       +NAME = ledit
       +VERSION = -999-prealpha0
       +
       +PREFIX = /usr/local
       +MANPREFIX = ${PREFIX}/man
       +
       +BIN = ${NAME}
       +SRC = ${BIN:=.c}
       +MAN1 = ${BIN:=.1}
       +
       +CFLAGS = -g -D_POSIX_C_SOURCE=200809L `pkg-config --cflags x11 xkbfile pangoxft xext`
       +LDFLAGS += `pkg-config --libs x11 xkbfile pangoxft xext` -lm
       +
       +all: ${BIN}
       +
       +.c:
       +        ${CC} ${CFLAGS} ${LDFLAGS} -o $@ $<
       +
       +clean:
       +        rm -f ${BIN}
       +
       +.PHONY: all clean
   DIR diff --git a/ledit.c b/ledit.c
       t@@ -0,0 +1,859 @@
       +#include <math.h>
       +#include <stdio.h>
       +#include <errno.h>
       +#include <string.h>
       +#include <stdlib.h>
       +#include <limits.h>
       +#include <unistd.h>
       +#include <locale.h>
       +#include <X11/Xlib.h>
       +#include <X11/Xutil.h>
       +#include <X11/keysym.h>
       +#include <X11/XF86keysym.h>
       +#include <X11/cursorfont.h>
       +
       +#include <pango/pangoxft.h>
       +
       +#include <X11/XKBlib.h>
       +#include <X11/extensions/XKBrules.h>
       +#include <X11/extensions/Xdbe.h>
       +
       +static enum mode {
       +        NORMAL = 1,
       +        INSERT = 2,
       +        VISUAL = 4
       +} cur_mode = INSERT;
       +
       +struct key {
       +        char *text;         /* for keys that correspond with text */
       +        KeySym keysym;      /* for other keys, e.g. arrow keys */
       +        enum mode modes;    /* modes in which this keybinding is functional */
       +        void (*func)(void); /* callback function */
       +};
       +
       +static struct {
       +        Display *dpy;
       +        GC gc;
       +        Window win;
       +        XdbeBackBuffer back_buf;
       +        Visual *vis;
       +        PangoFontMap *fontmap;
       +        PangoContext *context;
       +        PangoFontDescription *font;
       +        Colormap cm;
       +        int screen;
       +        int depth;
       +        XIM xim;
       +        XIC xic;
       +        int w;
       +        int h;
       +        XftColor fg;
       +        XftColor bg;
       +
       +        Atom wm_delete_msg;
       +} state;
       +
       +static void mainloop(void);
       +static void setup(int argc, char *argv[]);
       +static void cleanup(void);
       +static void redraw(void);
       +static void drag_motion(XEvent event);
       +static void resize_window(int w, int h);
       +static void button_release(void);
       +static void button_press(XEvent event);
       +static void key_press(XEvent event);
       +
       +int
       +main(int argc, char *argv[]) {
       +        setup(argc, argv);
       +        mainloop();
       +        cleanup();
       +
       +        return 0;
       +}
       +
       +static struct line {
       +        PangoLayout *layout;
       +        XftDraw *draw;
       +        char *text;
       +        size_t cap;
       +        size_t len;
       +        Pixmap pix;
       +        int w;
       +        int h;
       +        int pix_w;
       +        int pix_h;
       +        char dirty;
       +} *lines = NULL;
       +static size_t lines_num = 0;
       +static size_t lines_cap = 0;
       +
       +static int cur_line = 0;
       +static int cur_subline = 0;
       +static int cur_index = 0;
       +static int trailing = 0;
       +static int total_height = 0;
       +static int cur_display_offset = 0;
       +
       +static void
       +init_line(struct line *l) {
       +        /* FIXME: check that layout created properly */
       +        l->layout = pango_layout_new(state.context);
       +        pango_layout_set_width(l->layout, (state.w - 10) * PANGO_SCALE);
       +        pango_layout_set_font_description(l->layout, state.font);
       +        pango_layout_set_wrap(l->layout, PANGO_WRAP_WORD_CHAR);
       +        l->text = NULL;
       +        l->cap = l->len = 0;
       +        l->pix = None;
       +        /* FIXME: does this set line height reasonably when no text yet? */
       +        pango_layout_get_pixel_size(l->layout, &l->w, &l->h);
       +        l->dirty = 1;
       +}
       +
       +static void recalc_height_absolute(void);
       +
       +static void
       +insert_text(struct line *l, int index, char *text, int len) {
       +        if (len == -1)
       +                len = strlen(text);
       +        if (l->len + len > l->cap) {
       +                l->cap *= 2;
       +                if (l->cap == 0)
       +                        l->cap = 2;
       +                l->text = realloc(l->text, l->cap);
       +                if (!l->text) exit(1);
       +        }
       +        memmove(l->text + index + len, l->text + index, l->len - index);
       +        memcpy(l->text + index, text, len);
       +        l->len += len;
       +        pango_layout_set_text(l->layout, l->text, l->len);
       +        recalc_height_absolute();
       +        l->dirty = 1;
       +}
       +
       +static void insert_line_entry(int index);
       +
       +static void
       +render_line(struct line *l) {
       +        /* FIXME: check for <= 0 on size */
       +        if (l->pix == None) {
       +                l->pix = XCreatePixmap(state.dpy, state.back_buf, l->w + 10, l->h + 10, state.depth);
       +                l->pix_w = l->w + 10;
       +                l->pix_h = l->h + 10;
       +                l->draw = XftDrawCreate(state.dpy, l->pix, state.vis, state.cm);
       +        } else if (l->pix_w < l->w || l->pix_h < l->h) {
       +                int new_w = l->w > l->pix_w ? l->w + 10 : l->pix_w + 10;
       +                int new_h = l->h > l->pix_h ? l->h + 10 : l->pix_h + 10;
       +                XFreePixmap(state.dpy, l->pix);
       +                l->pix = XCreatePixmap(state.dpy, state.back_buf, new_w, new_h, state.depth);
       +                l->pix_w = new_w;
       +                l->pix_h = new_h;
       +                XftDrawChange(l->draw, l->pix);
       +        }
       +        XftDrawRect(l->draw, &state.bg, 0, 0, l->w, l->h);
       +        pango_xft_render_layout(l->draw, &state.fg, l->layout, 0, 0);
       +        l->dirty = 0;
       +}
       +
       +static void
       +append_line(int text_index, int line_index) {
       +        if (lines_num >= lines_cap) {
       +                lines_cap *= 2;
       +                if (lines_cap == 0)
       +                        lines_cap = 2;
       +                lines = realloc(lines, lines_cap * sizeof(struct line));
       +                if (!lines) exit(1);
       +        }
       +        memmove(lines + line_index + 2, lines + line_index + 1, (lines_num - (line_index + 1)) * sizeof(struct line));
       +        struct line *new_l = &lines[line_index + 1];
       +        init_line(new_l);
       +        lines_num++;
       +        if (text_index != -1) {
       +                struct line *l = &lines[line_index];
       +                int len = l->len - text_index;
       +                new_l->pix = None;
       +                new_l->len = len;
       +                new_l->cap = len + 10;
       +                new_l->text = malloc(new_l->cap);
       +                if (!new_l->text) exit(1);
       +                memcpy(new_l->text, l->text + text_index, len);
       +                l->len = text_index;
       +                pango_layout_set_text(new_l->layout, new_l->text, new_l->len);
       +                pango_layout_set_text(l->layout, l->text, l->len);
       +                /* FIXME: set height here */
       +        }
       +}
       +
       +static void change_keyboard(char *lang);
       +
       +PangoAttrList *basic_attrs;
       +
       +static void
       +mainloop(void) {
       +        XEvent event;
       +        int xkb_event_type;
       +        int major, minor;
       +        if (!XkbQueryExtension(state.dpy, 0, &xkb_event_type, NULL, &major, &minor)) {
       +                fprintf(stderr, "XKB not supported.");
       +                exit(1);
       +        }
       +        printf("XKB (%d.%d) supported.\n", major, minor);
       +        /* This should select the events when the keyboard mapping changes.
       +           When e.g. 'setxkbmap us' is executed, two events are sent, but I haven't figured out how to
       +           change that. When the xkb layout switching is used (e.g. 'setxkbmap -option grp:shifts_toggle'),
       +           this issue does not occur because only a state event is sent. */
       +        XkbSelectEvents(state.dpy, XkbUseCoreKbd, XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask);
       +        XkbSelectEventDetails(state.dpy, XkbUseCoreKbd, XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask);
       +        XSync(state.dpy, False);
       +        int running = 1;
       +        int change_kbd = 0;
       +
       +
       +        /*draw = XftDrawCreate(state.dpy, state.back_buf, state.vis, state.cm);*/
       +        state.fontmap = pango_xft_get_font_map(state.dpy, state.screen);
       +        state.context = pango_font_map_create_context(state.fontmap);
       +
       +        state.font = pango_font_description_from_string("Monospace");
       +        pango_font_description_set_size(state.font, 16 * PANGO_SCALE);
       +
       +        basic_attrs = pango_attr_list_new();
       +        PangoAttribute *no_hyphens = pango_attr_insert_hyphens_new(FALSE);
       +        pango_attr_list_insert(basic_attrs, no_hyphens);
       +
       +        append_line(-1, -1);
       +
       +        XftColorAllocName(state.dpy, state.vis, state.cm, "#000000", &state.fg);
       +        XftColorAllocName(state.dpy, state.vis, state.cm, "#FFFFFF", &state.bg);
       +        int need_redraw = 0;
       +        redraw();
       +
       +        while (running) {
       +                do {
       +                        XNextEvent(state.dpy, &event);
       +                        if (event.type == xkb_event_type) {
       +                                change_kbd = 1;
       +                                continue;
       +                        }
       +                        if (XFilterEvent(&event, None))
       +                                continue;
       +                        switch (event.type) {
       +                        case Expose:
       +                                redraw();
       +                                need_redraw = 1;
       +                                break;
       +                        case ConfigureNotify:
       +                                resize_window(event.xconfigure.width, event.xconfigure.height);
       +                                if (cur_display_offset > 0 && cur_display_offset + state.h >= total_height) {
       +                                        cur_display_offset = total_height - state.h;
       +                                        if (cur_display_offset < 0)
       +                                                cur_display_offset = 0;
       +                                }
       +                                redraw();
       +                                need_redraw = 1;
       +                                break;
       +                        case ButtonPress:
       +                                switch (event.xbutton.button) {
       +                                        case Button1:
       +                                                button_press(event);
       +                                                break;
       +                                        case Button4:
       +                                                cur_display_offset -= 10;
       +                                                if (cur_display_offset < 0)
       +                                                        cur_display_offset = 0;
       +                                                break;
       +                                        case Button5:
       +                                                if (cur_display_offset + state.h < total_height) {
       +                                                        cur_display_offset += 10;
       +                                                        if (cur_display_offset + state.h > total_height)
       +                                                                cur_display_offset = total_height - state.h;
       +                                                }
       +                                                break;
       +                                }
       +                                need_redraw = 1;
       +                                break;
       +                        case ButtonRelease:
       +                                if (event.xbutton.button == Button1)
       +                                        button_release();
       +                                break;
       +                        case MotionNotify:
       +                                drag_motion(event);
       +                                break;
       +                        case KeyPress:
       +                                need_redraw = 1;
       +                                key_press(event);
       +                                break;
       +                        case ClientMessage:
       +                                if ((Atom)event.xclient.data.l[0] == state.wm_delete_msg)
       +                                        running = 0;
       +                        default:
       +                                break;
       +                        }
       +                } while (XPending(state.dpy));
       +
       +                if (change_kbd) {
       +                        change_kbd = 0;
       +                        XkbStateRec s;
       +                        XkbGetState(state.dpy, XkbUseCoreKbd, &s);
       +                        XkbDescPtr desc = XkbGetKeyboard(state.dpy, XkbAllComponentsMask, XkbUseCoreKbd);
       +                        char *group = XGetAtomName(state.dpy, desc->names->groups[s.group]);
       +                        change_keyboard(group);
       +                        /*char *symbols = XGetAtomName(state.dpy, desc->names->symbols);*/
       +                        XFree(group);
       +                        /*XFree(symbols);*/
       +                        XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
       +                }
       +                if (need_redraw) {
       +                        XSetForeground(state.dpy, state.gc, state.bg.pixel);
       +                        XFillRectangle(state.dpy, state.back_buf, state.gc, 0, 0, state.w, state.h);
       +                        int h = 0;
       +                        /*int cur_line_height = 0;*/
       +                        int tmp_w, tmp_h;
       +                        int cur_line_y = 0;
       +                        int cursor_displayed = 0;
       +                        for (int i = 0; i < lines_num; i++) {
       +                                if (lines[i].dirty) {
       +                                        if (i == cur_line && cur_mode == NORMAL) {
       +                                                PangoAttribute *attr0 = pango_attr_background_new(0, 0, 0);
       +                                                PangoAttribute *attr1 = pango_attr_foreground_new(65535, 65535, 65535);
       +                                                attr0->start_index = cur_index;
       +                                                attr0->end_index = cur_index + 1;
       +                                                attr1->start_index = cur_index;
       +                                                attr1->end_index = cur_index + 1;
       +                                                PangoAttribute *attr2 = pango_attr_insert_hyphens_new(FALSE);
       +                                                PangoAttrList *list = pango_attr_list_new();
       +                                                pango_attr_list_insert(list, attr0);
       +                                                pango_attr_list_insert(list, attr1);
       +                                                pango_attr_list_insert(list, attr2);
       +                                                pango_layout_set_attributes(lines[cur_line].layout, list);
       +                                        } else {
       +                                                pango_layout_set_attributes(lines[i].layout, basic_attrs);
       +                                        }
       +                                        render_line(&lines[i]);
       +                                }
       +                                if (h + lines[i].h > cur_display_offset) {
       +                                        int final_y = 0;
       +                                        int dest_y = h - cur_display_offset;
       +                                        int final_h = lines[i].h;
       +                                        if (h < cur_display_offset) {
       +                                                dest_y = 0;
       +                                                final_y = cur_display_offset - h;
       +                                                final_h -= cur_display_offset - h;
       +                                        }
       +                                        if (dest_y + final_h > state.h) {
       +                                                final_h -= final_y + final_h - cur_display_offset - state.h;
       +                                        }
       +                                        XCopyArea(state.dpy, lines[i].pix, state.back_buf, state.gc, 0, final_y, lines[i].w, final_h, 0, dest_y);
       +                                        if (i == cur_line) {
       +                                                cur_line_y = h - cur_display_offset;
       +                                                cursor_displayed = 1;
       +                                        }
       +                                }
       +                                if (h + lines[i].h >= cur_display_offset + state.h)
       +                                        break;
       +                                h += lines[i].h;
       +                        }
       +                        need_redraw = 0;
       +
       +                        XSetForeground(state.dpy, state.gc, BlackPixel(state.dpy, state.screen));
       +                        PangoRectangle strong, weak;
       +                        pango_layout_get_cursor_pos(lines[cur_line].layout, cur_index, &strong, &weak);
       +                        int cursor_y = strong.y / PANGO_SCALE + cur_line_y;
       +                        if (cursor_displayed && cursor_y >= 0) {
       +                                if (cur_mode == NORMAL && cur_index == lines[cur_line].len) {
       +                                        XFillRectangle(
       +                                            state.dpy, state.back_buf, state.gc,
       +                                            strong.x / PANGO_SCALE, cursor_y,
       +                                            10, strong.height / PANGO_SCALE
       +                                        );
       +                                } else if (cur_mode == INSERT) {
       +                                        XDrawLine(
       +                                            state.dpy, state.back_buf, state.gc,
       +                                            strong.x / PANGO_SCALE, cursor_y,
       +                                            strong.x / PANGO_SCALE, (strong.y + strong.height) / PANGO_SCALE + cur_line_y
       +                                        );
       +                                }
       +                        }
       +                        if (total_height > state.h) {
       +                                double scroll_h = ((double)state.h / total_height) * state.h;
       +                                double scroll_y = ((double)cur_display_offset / (total_height - state.h)) * (state.h - scroll_h);
       +                                XFillRectangle(state.dpy, state.back_buf, state.gc, state.w - 10, (int)round(scroll_y), 10, (int)round(scroll_h));
       +                        }
       +
       +                        XdbeSwapInfo swap_info;
       +                        swap_info.swap_window = state.win;
       +                        swap_info.swap_action = XdbeBackground;
       +
       +                        if (!XdbeSwapBuffers(state.dpy, &swap_info, 1))
       +                                exit(1);
       +                        XFlush(state.dpy);
       +                }
       +        }
       +        pango_attr_list_unref(basic_attrs);
       +}
       +
       +static void
       +setup(int argc, char *argv[]) {
       +        setlocale(LC_CTYPE, "");
       +        XSetLocaleModifiers("");
       +        XSetWindowAttributes attrs;
       +        XGCValues gcv;
       +
       +        state.w = 500;
       +        state.h = 500;
       +        state.dpy = XOpenDisplay(NULL);
       +        state.screen = DefaultScreen(state.dpy);
       +
       +        /* based on http://wili.cc/blog/xdbe.html */
       +        int major, minor;
       +        if (XdbeQueryExtension(state.dpy, &major, &minor)) {
       +                printf("Xdbe (%d.%d) supported, using double buffering.\n", major, minor);
       +                int num_screens = 1;
       +                Drawable screens[] = { DefaultRootWindow(state.dpy) };
       +                XdbeScreenVisualInfo *info = XdbeGetVisualInfo(state.dpy, screens, &num_screens);
       +                if (!info || num_screens < 1 || info->count < 1) {
       +                        fprintf(stderr, "No visuals support Xdbe.\n");
       +                        exit(1);
       +                }
       +                XVisualInfo xvisinfo_templ;
       +                xvisinfo_templ.visualid = info->visinfo[0].visual; // We know there's at least one
       +                xvisinfo_templ.screen = 0;
       +                xvisinfo_templ.depth = info->visinfo[0].depth;
       +                int matches;
       +                XVisualInfo *xvisinfo_match =
       +                        XGetVisualInfo(state.dpy, VisualIDMask|VisualScreenMask|VisualDepthMask, &xvisinfo_templ, &matches);
       +                if (!xvisinfo_match || matches < 1) {
       +                        fprintf(stderr, "Couldn't match a Visual with double buffering\n");
       +                        exit(1);
       +                }
       +                state.vis = xvisinfo_match->visual;
       +        } else {
       +                fprintf(stderr, "No Xdbe support.\n");
       +                exit(1);
       +        }
       +
       +        state.depth = DefaultDepth(state.dpy, state.screen);
       +        state.cm = DefaultColormap(state.dpy, state.screen);
       +
       +        memset(&attrs, 0, sizeof(attrs));
       +        attrs.background_pixmap = None;
       +        attrs.colormap = state.cm;
       +        state.win = XCreateWindow(state.dpy, DefaultRootWindow(state.dpy), 0, 0,
       +            state.w, state.h, 0, state.depth,
       +            InputOutput, state.vis, CWBackPixmap | CWColormap, &attrs);
       +
       +        state.back_buf = XdbeAllocateBackBufferName(state.dpy, state.win, XdbeBackground);
       +
       +        memset(&gcv, 0, sizeof(gcv));
       +        gcv.line_width = 1;
       +        state.gc = XCreateGC(state.dpy, state.back_buf, GCLineWidth, &gcv);
       +
       +        XSelectInput(state.dpy, state.win, StructureNotifyMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask);
       +
       +        state.wm_delete_msg = XInternAtom(state.dpy, "WM_DELETE_WINDOW", False);
       +        XSetWMProtocols(state.dpy, state.win, &state.wm_delete_msg, 1);
       +
       +        /* blatantly stolen from st (simple terminal) */
       +        if ((state.xim = XOpenIM(state.dpy, NULL, NULL, NULL)) == NULL) {
       +                XSetLocaleModifiers("@im=local");
       +                if ((state.xim =  XOpenIM(state.dpy, NULL, NULL, NULL)) == NULL) {
       +                        XSetLocaleModifiers("@im=");
       +                        if ((state.xim = XOpenIM(state.dpy, NULL, NULL, NULL)) == NULL) {
       +                                fprintf(stderr, "XOpenIM failed. Could not open input device.\n");
       +                                exit(1);
       +                        }
       +                }
       +        }
       +        state.xic = XCreateIC(state.xim, XNInputStyle, XIMPreeditNothing
       +                                           | XIMStatusNothing, XNClientWindow, state.win,
       +                                           XNFocusWindow, state.win, NULL);
       +        if (state.xic == NULL) {
       +                fprintf(stderr, "XCreateIC failed. Could not obtain input method.\n");
       +                exit(1);
       +        }
       +        XSetICFocus(state.xic);
       +
       +        XMapWindow(state.dpy, state.win);
       +        redraw();
       +}
       +
       +static void
       +cleanup(void) {
       +        XDestroyWindow(state.dpy, state.win);
       +        XCloseDisplay(state.dpy);
       +}
       +
       +static void
       +redraw(void) {
       +        XSetForeground(state.dpy, state.gc, BlackPixel(state.dpy, state.screen));
       +        XFillRectangle(state.dpy, state.back_buf, state.gc, 0, 0, state.w, state.h);
       +}
       +
       +static void
       +button_press(XEvent event) {
       +}
       +
       +static void
       +button_release(void) {
       +}
       +
       +static void
       +recalc_height(void) {
       +        /*
       +        int w, h;
       +        pango_layout_get_pixel_size(lines[cur_line].layout, &w, &h);
       +        total_height += (h - cur_line_height);
       +        */
       +        if (total_height < 0)
       +                total_height = 0; /* should never actually happen */
       +        /*cur_line_height = h;*/
       +}
       +
       +static void
       +set_cur_line_height(void) {
       +        int w, h;
       +        pango_layout_get_pixel_size(lines[cur_line].layout, &w, &h);
       +        lines[cur_line].h = h;
       +        /*cur_line_height = h;*/
       +}
       +
       +static void
       +recalc_height_absolute(void) {
       +        int w, h;
       +        total_height = 0;
       +        for (int i = 0; i < lines_num; i++) {
       +                pango_layout_get_pixel_size(lines[i].layout, &w, &h);
       +                total_height += h;
       +                lines[i].w = w;
       +                lines[i].h = h;
       +        }
       +        set_cur_line_height();
       +}
       +
       +static void
       +resize_window(int w, int h) {
       +        state.w = w;
       +        state.h = h;
       +        total_height = 0;
       +        int tmp_w, tmp_h;
       +        for (int i = 0; i < lines_num; i++) {
       +                /* 10 pixels for scrollbar */
       +                pango_layout_set_width(lines[i].layout, (w - 10) * PANGO_SCALE);
       +                pango_layout_get_pixel_size(lines[i].layout, &tmp_w, &tmp_h);
       +                total_height += tmp_h;
       +                lines[i].h = tmp_h;
       +                lines[i].dirty = 1;
       +        }
       +        //set_cur_line_height();
       +}
       +
       +static void
       +drag_motion(XEvent event) {
       +}
       +
       +static void
       +delete_line_entry(int index) {
       +        if (index < lines_num - 1)
       +                memmove(lines + index, lines + index + 1, (lines_num - index - 1) * sizeof(struct line));
       +        lines_num--;
       +}
       +
       +static void
       +backspace(void) {
       +        if (cur_index == 0) {
       +                if (cur_line != 0) {
       +                        struct line *l1 = &lines[cur_line - 1];
       +                        struct line *l2 = &lines[cur_line];
       +                        int old_len = l1->len;
       +                        insert_text(l1, l1->len, l2->text, l2->len);
       +                        delete_line_entry(cur_line);
       +                        cur_line--;
       +                        cur_index = old_len;
       +                        //total_height -= cur_line_height();
       +                        //set_cur_line_height();
       +                }
       +        } else {
       +                int i = cur_index - 1;
       +                struct line *l = &lines[cur_line];
       +                /* find valid utf8 char - this probably needs to be improved */
       +                while (i > 0 && ((l->text[i] & 0xC0) == 0x80))
       +                        i--;
       +                memmove(l->text + i, l->text + cur_index, l->len - cur_index);
       +                l->len -= cur_index - i;
       +                cur_index = i;
       +                pango_layout_set_text(l->layout, l->text, l->len);
       +        }
       +        lines[cur_line].dirty = 1;
       +        recalc_height_absolute();
       +}
       +
       +static void
       +delete_key(void) {
       +        if (cur_index == lines[cur_line].len) {
       +                if (cur_line != lines_num - 1) {
       +                        struct line *l1 = &lines[cur_line];
       +                        struct line *l2 = &lines[cur_line + 1];
       +                        int old_len = l1->len;
       +                        insert_text(l1, l1->len, l2->text, l2->len);
       +                        delete_line_entry(cur_line + 1);
       +                        cur_index = old_len;
       +                        /*total_height -= cur_line_height();
       +                        set_cur_line_height();*/
       +                }
       +        } else {
       +                int i = cur_index + 1;
       +                struct line *l = &lines[cur_line];
       +                while (i < lines[cur_line].len && ((lines[cur_line].text[i] & 0xC0) == 0x80))
       +                        i++;
       +                memmove(l->text + cur_index, l->text + i, l->len - i);
       +                l->len -= i - cur_index;
       +                pango_layout_set_text(l->layout, l->text, l->len);
       +        }
       +        lines[cur_line].dirty = 1;
       +        recalc_height_absolute();
       +}
       +
       +static void
       +move_cursor(int dir) {
       +        int last_index = cur_index;
       +        pango_layout_move_cursor_visually(lines[cur_line].layout, TRUE, cur_index, trailing, dir, &cur_index, &trailing);
       +        /* we don't currently support a difference between the cursor being at
       +           the end of a soft line and the beginning of the next line */
       +        while (trailing > 0) {
       +                trailing--;
       +                cur_index++;
       +                while (cur_index < lines[cur_line].len && ((lines[cur_line].text[cur_index] & 0xC0) == 0x80))
       +                        cur_index++;
       +        }
       +        if (cur_index < 0)
       +                cur_index = 0;
       +        /* when in normal mode, the cursor cannot be at the very end
       +           of the line because it's always covering a character */
       +        if (cur_index >= lines[cur_line].len) {
       +                if (cur_mode == NORMAL)
       +                        cur_index = last_index;
       +                else
       +                        cur_index = lines[cur_line].len;
       +        }
       +        lines[cur_line].dirty = 1;
       +}
       +
       +static void
       +cursor_left(void) {
       +        move_cursor(-1);
       +}
       +
       +static void
       +cursor_right(void) {
       +        move_cursor(1);
       +}
       +
       +static void
       +return_key(void) {
       +        append_line(cur_index, cur_line);
       +        lines[cur_line].dirty = 1;
       +        cur_line++;
       +        lines[cur_line].dirty = 1;
       +        cur_index = 0;
       +        recalc_height_absolute();
       +}
       +
       +static void
       +escape_key(void) {
       +        cur_mode = NORMAL;
       +        PangoDirection dir = PANGO_DIRECTION_RTL;
       +        int tmp_index = cur_index;
       +        if (cur_index >= lines[cur_line].len)
       +                tmp_index--;
       +        if (tmp_index >= 0)
       +                dir = pango_layout_get_direction(lines[cur_line].layout, tmp_index);
       +        if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_WEAK_RTL) {
       +                cursor_right();
       +        } else {
       +                cursor_left();
       +        }
       +        lines[cur_line].dirty = 1;
       +        /*
       +        if (cur_index > 0)
       +                cursor_left();
       +        */
       +}
       +
       +static void
       +i_key(void) {
       +        cur_mode = INSERT;
       +        /*
       +        for (int i = 0; i < lines_num; i++) {
       +                pango_layout_set_attributes(lines[i].layout, NULL);
       +        }
       +        */
       +        lines[cur_line].dirty = 1;
       +}
       +
       +static void
       +line_down(void) {
       +        int lineno, x, trailing;
       +        pango_layout_index_to_line_x(lines[cur_line].layout, cur_index, 0, &lineno, &x);
       +        int maxlines = pango_layout_get_line_count(lines[cur_line].layout);
       +        PangoLayoutLine *line = pango_layout_get_line_readonly(lines[cur_line].layout, lineno);
       +        if (lineno == maxlines - 1) {
       +                lines[cur_line].dirty = 1;
       +                /* move to the next hard line */
       +                if (cur_line < lines_num - 1) {
       +                        cur_line++;
       +                        PangoLayoutLine *nextline = pango_layout_get_line_readonly(lines[cur_line].layout, 0);
       +                        if (pango_layout_line_x_to_index(nextline, x, &cur_index, &trailing) == FALSE) {
       +                                /* set it to *after* the last index of the line */
       +                                cur_index = nextline->start_index + nextline->length;
       +                        }
       +                }
       +        } else {
       +                /* move to the next soft line */
       +                PangoLayoutLine *nextline = pango_layout_get_line_readonly(lines[cur_line].layout, lineno + 1);
       +                if (pango_layout_line_x_to_index(nextline, x, &cur_index, &trailing) == FALSE) {
       +                        /* set it to *after* the last index of the line */
       +                        cur_index = nextline->start_index + nextline->length;
       +                }
       +        }
       +        if (cur_index > 0 && cur_mode == NORMAL && cur_index >= lines[cur_line].len)
       +                cursor_left();
       +        lines[cur_line].dirty = 1;
       +}
       +
       +static void
       +line_up(void) {
       +        int lineno, x, trailing;
       +        pango_layout_index_to_line_x(lines[cur_line].layout, cur_index, 0, &lineno, &x);
       +        PangoLayoutLine *line = pango_layout_get_line_readonly(lines[cur_line].layout, lineno);
       +        if (lineno == 0) {
       +                lines[cur_line].dirty = 1;
       +                /* move to the previous hard line */
       +                if (cur_line > 0) {
       +                        cur_line--;
       +                        int maxlines = pango_layout_get_line_count(lines[cur_line].layout);
       +                        PangoLayoutLine *prevline = pango_layout_get_line_readonly(lines[cur_line].layout, maxlines - 1);
       +                        if (pango_layout_line_x_to_index(prevline, x, &cur_index, &trailing) == FALSE) {
       +                                /* set it to *after* the last index of the line */
       +                                cur_index = prevline->start_index + prevline->length;
       +                        }
       +                }
       +        } else {
       +                /* move to the previous soft line */
       +                PangoLayoutLine *prevline = pango_layout_get_line_readonly(lines[cur_line].layout, lineno - 1);
       +                if (pango_layout_line_x_to_index(prevline, x, &cur_index, &trailing) == FALSE) {
       +                        /* set it to *after* the last index of the line */
       +                        cur_index = prevline->start_index + prevline->length;
       +                }
       +        }
       +        if (cur_index > 0 && cur_mode == NORMAL && cur_index >= lines[cur_line].len)
       +                cursor_left();
       +        lines[cur_line].dirty = 1;
       +}
       +
       +static void
       +zero_key(void) {
       +        cur_index = 0;
       +        lines[cur_line].dirty = 1;
       +}
       +
       +static struct key keys_en[] = {
       +        {NULL, XK_BackSpace, INSERT, &backspace},
       +        {NULL, XK_Left, INSERT|NORMAL, &cursor_left},
       +        {NULL, XK_Right, INSERT|NORMAL, &cursor_right},
       +        {NULL, XK_Up, INSERT|NORMAL, &line_up},
       +        {NULL, XK_Down, INSERT|NORMAL, &line_down},
       +        {NULL, XK_Return, INSERT, &return_key},
       +        {NULL, XK_Delete, INSERT, &delete_key},
       +        {NULL, XK_Escape, INSERT, &escape_key},
       +        {"i",  0, NORMAL, &i_key},
       +        {"h",  0, NORMAL, &cursor_left},
       +        {"l",  0, NORMAL, &cursor_right},
       +        {"j",  0, NORMAL, &line_down},
       +        {"k",  0, NORMAL, &line_up},
       +        {"0",  0, NORMAL, &zero_key}
       +};
       +
       +static struct key keys_ur[] = {
       +        {NULL, XK_BackSpace, INSERT, &backspace},
       +        {NULL, XK_Left, INSERT|NORMAL, &cursor_left},
       +        {NULL, XK_Right, INSERT|NORMAL, &cursor_right},
       +        {NULL, XK_Up, INSERT|NORMAL, &line_up},
       +        {NULL, XK_Down, INSERT|NORMAL, &line_down},
       +        {NULL, XK_Return, INSERT, &return_key},
       +        {NULL, XK_Delete, INSERT, &delete_key},
       +        {NULL, XK_Escape, INSERT, &escape_key},
       +        {"ی",  0, NORMAL, &i_key},
       +        {"ح",  0, NORMAL, &cursor_left},
       +        {"ل",  0, NORMAL, &cursor_right},
       +        {"ج",  0, NORMAL, &line_down},
       +        {"ک",  0, NORMAL, &line_up},
       +        {"0",  0, NORMAL, &zero_key}
       +};
       +
       +static struct key keys_hi[] = {
       +        {NULL, XK_BackSpace, INSERT, &backspace},
       +        {NULL, XK_Left, INSERT|NORMAL, &cursor_left},
       +        {NULL, XK_Right, INSERT|NORMAL, &cursor_right},
       +        {NULL, XK_Up, INSERT|NORMAL, &line_up},
       +        {NULL, XK_Down, INSERT|NORMAL, &line_down},
       +        {NULL, XK_Return, INSERT, &return_key},
       +        {NULL, XK_Delete, INSERT, &delete_key},
       +        {NULL, XK_Escape, INSERT, &escape_key},
       +        {"ि",  0, NORMAL, &i_key},
       +        {"ह",  0, NORMAL, &cursor_left},
       +        {"ल",  0, NORMAL, &cursor_right},
       +        {"ज",  0, NORMAL, &line_down},
       +        {"क",  0, NORMAL, &line_up},
       +        {"0",  0, NORMAL, &zero_key}
       +};
       +
       +#define LENGTH(X) (sizeof X / sizeof X[0])
       +
       +struct lang_keys {
       +        char *lang;
       +        struct key *keys;
       +        int num_keys;
       +};
       +
       +static struct lang_keys keys[] = {
       +        {"English (US)", keys_en, LENGTH(keys_en)},
       +        {"German", keys_en, LENGTH(keys_en)},
       +        {"Urdu (Pakistan)", keys_ur, LENGTH(keys_ur)},
       +        {"Hindi (Bolnagri)", keys_hi, LENGTH(keys_hi)}
       +};
       +
       +static struct lang_keys *cur_keys = &keys[0];
       +
       +static void change_keyboard(char *lang) {
       +        printf("%s\n", lang);
       +        for (int i = 0; i < LENGTH(keys); i++) {
       +                if (!strcmp(keys[i].lang, lang)) {
       +                        cur_keys = &keys[i];
       +                        break;
       +                }
       +        }
       +}
       +
       +static void
       +key_press(XEvent event) {
       +        XWindowAttributes attrs;
       +        char buf[32];
       +        KeySym sym;
       +        /* FIXME: X_HAVE_UTF8_STRING See XmbLookupString(3) */
       +        int n = Xutf8LookupString(state.xic, &event.xkey, buf, sizeof(buf), &sym, NULL);
       +        int found = 0;
       +        for (int i = 0; i < cur_keys->num_keys; i++) {
       +                if (cur_keys->keys[i].text) {
       +                        if (n > 0 && (cur_keys->keys[i].modes & cur_mode) && !strncmp(cur_keys->keys[i].text, buf, n)) {
       +                                cur_keys->keys[i].func();
       +                                found = 1;
       +                        }
       +                } else if ((cur_keys->keys[i].modes & cur_mode) && cur_keys->keys[i].keysym == sym) {
       +                        cur_keys->keys[i].func();
       +                        found = 1;
       +                }
       +        }
       +        if (cur_mode == INSERT && !found && n > 0) {
       +                insert_text(&lines[cur_line], cur_index, buf, n);
       +                cur_index += n;
       +        }
       +}