]> git.mdlowis.com Git - projs/tide.git/commitdiff
Initial commit of new win module to commonize the ui
authorMichael D. Lowis <mike.lowis@gentex.com>
Thu, 2 Feb 2017 18:54:07 +0000 (13:54 -0500)
committerMichael D. Lowis <mike.lowis@gentex.com>
Thu, 2 Feb 2017 18:54:07 +0000 (13:54 -0500)
Makefile
config.mk
inc/edit.h
inc/win.h [new file with mode: 0644]
lib/win.c [new file with mode: 0644]
term.c [new file with mode: 0644]

index 9f2f0a5b465ac77610b671b0cf158272218316de..f69808a7cda8f1b174998e7a11cae1b043358597 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,8 @@ LIBEDIT_OBJS =     \
        lib/utils.o    \
        lib/exec.o     \
        lib/view.o     \
-       lib/x11.o
+       lib/x11.o      \
+       lib/win.o
 
 TEST_OBJS =      \
        unittests.o  \
@@ -17,10 +18,10 @@ TEST_OBJS =      \
 
 include config.mk
 
-all: xedit xpick
+all: xedit xpick term
 
 clean:
-       $(RM) *.o lib*/*.o tests/*.o *.a xpick xedit unittests
+       $(RM) *.o lib*/*.o tests/*.o *.a xpick xedit term unittests
        $(RM) *.d lib*/*.d tests/*.d
        $(RM) *.gcno lib*/*.gcno tests/*.gcno
        $(RM) *.gcda lib*/*.gcda tests/*.gcda
@@ -51,6 +52,9 @@ xedit: xedit.o libedit.a
 
 xpick: xpick.o libedit.a
        $(LD) -o $@ $^ $(LDFLAGS)
+       
+term: term.o libedit.a
+       $(LD) -o $@ $^ $(LDFLAGS)
 
 libedit.a: $(LIBEDIT_OBJS)
        $(AR) $(ARFLAGS) $@ $^
index 3a9bc823fc44f6ed15ace4f24be9766c42fcb049..df3f115b2520d3dd5083ade0b943ad7e8261001b 100644 (file)
--- a/config.mk
+++ b/config.mk
@@ -7,9 +7,7 @@ PREFIX = $(HOME)
 CC = cc
 CFLAGS = --std=c99 -MMD -g -O0 $(INCS)
 
-#CC = gcc
-#CFLAGS  = --std=c99 -Wall -Wextra -Werror $(INCS)
-#CFLAGS += -Wno-sign-compare -Wno-unused-variable -Wno-unused-parameter -Wno-missing-field-initializers
+#CFLAGS += -Wall -Wextra -Werror
 
 # Linker Setup
 LD = $(CC)
index 43e925be28f916c782457accb0e2fdf172bb3b2b..ec851e4754be412ac00d87ab8de315961f4dcb4c 100644 (file)
@@ -231,6 +231,8 @@ typedef struct {
     size_t y;
     size_t height;
     size_t width;
+    size_t csrx;
+    size_t csry;
     bool warp_ptr;
     View view;
 } Region;
diff --git a/inc/win.h b/inc/win.h
new file mode 100644 (file)
index 0000000..1c9b7fe
--- /dev/null
+++ b/inc/win.h
@@ -0,0 +1,45 @@
+typedef enum {\r
+    STATUS   = 0,\r
+    TAGS     = 1,\r
+    EDIT     = 2,\r
+    SCROLL   = 3,\r
+    NREGIONS = 4,\r
+    FOCUSED  = 4\r
+} WinRegion;\r
+\r
+#if 0\r
+typedef struct {\r
+    size_t x;\r
+    size_t y;\r
+    size_t height;\r
+    size_t width;\r
+    size_t csrx;\r
+    size_t csry;\r
+    bool warp_ptr;\r
+    View view;\r
+} Region;\r
+#endif\r
+\r
+typedef void (*MouseFunc)(WinRegion id, size_t count, size_t row, size_t col);\r
+\r
+typedef struct {\r
+    MouseFunc left;\r
+    MouseFunc middle;\r
+    MouseFunc right;\r
+} MouseConfig;\r
+\r
+void win_init(char* name);\r
+void win_loop(void);\r
+void win_settext(WinRegion id, char* text);\r
+void win_setkeys(KeyBinding* bindings);\r
+void win_setmouse(MouseConfig* mconfig);\r
+View* win_view(WinRegion id);\r
+Buf* win_buf(WinRegion id);\r
+Sel* win_sel(WinRegion id);\r
+\r
+void mouse_left(WinRegion id, size_t count, size_t row, size_t col);\r
+void mouse_middle(WinRegion id, size_t count, size_t row, size_t col);\r
+void mouse_right(WinRegion id, size_t count, size_t row, size_t col);\r
+\r
+\r
+\r
diff --git a/lib/win.c b/lib/win.c
new file mode 100644 (file)
index 0000000..b333781
--- /dev/null
+++ b/lib/win.c
@@ -0,0 +1,251 @@
+#include <stdc.h>\r
+#include <utf.h>\r
+#include <edit.h>\r
+#include <win.h>\r
+#include <x11.h>\r
+\r
+static void draw_glyphs(size_t x, size_t y, UGlyph* glyphs, size_t rlen, size_t ncols);\r
+static WinRegion getregion(size_t x, size_t y);\r
+static void onredraw(int height, int width);\r
+static void oninput(int mods, Rune key);\r
+static void onmouse(MouseAct act, MouseBtn btn, int x, int y);\r
+static void onshutdown(void);\r
+static void mouse_wheelup(WinRegion id, size_t count, size_t row, size_t col);\r
+static void mouse_wheeldn(WinRegion id, size_t count, size_t row, size_t col);\r
+\r
+static XFont Font;\r
+static XConfig Config = {\r
+    .redraw       = onredraw,\r
+    .handle_key   = oninput,\r
+    .handle_mouse = onmouse,\r
+    .shutdown     = onshutdown,\r
+    .palette      = COLOR_PALETTE\r
+};\r
+\r
+void (*MouseActs[MOUSE_BTN_COUNT])(WinRegion id, size_t count, size_t row, size_t col) = {\r
+    [MOUSE_BTN_LEFT]      = mouse_left,\r
+    [MOUSE_BTN_MIDDLE]    = mouse_middle,\r
+    [MOUSE_BTN_RIGHT]     = mouse_right,\r
+    [MOUSE_BTN_WHEELUP]   = mouse_wheelup,\r
+    [MOUSE_BTN_WHEELDOWN] = mouse_wheeldn,\r
+};\r
+\r
+static WinRegion Focused = EDIT;\r
+static Region Regions[NREGIONS] = {0};\r
+static ButtonState MouseBtns[MOUSE_BTN_COUNT] = {0};\r
+KeyBinding* Keys = NULL;\r
+\r
+void win_init(char* name) {\r
+    win_settext(STATUS, "This is a status line");\r
+    for (int i = 1; i < SCROLL; i++)\r
+        view_init(&(Regions[i].view), NULL);\r
+    x11_init(&Config);\r
+    Font = x11_font_load(FONTNAME);\r
+    x11_window(name, Width, Height);\r
+    x11_show();\r
+}\r
+\r
+void win_loop(void) {\r
+    x11_loop();\r
+}\r
+\r
+void win_settext(WinRegion id, char* text) {\r
+    view_init(win_view(id), NULL);\r
+    view_putstr(win_view(id), text);\r
+    view_selprev(win_view(id)); // clear the selection\r
+}\r
+\r
+void win_setkeys(KeyBinding* bindings) {\r
+    Keys = bindings;\r
+}\r
+\r
+View* win_view(WinRegion id) {\r
+    if (id == FOCUSED) id = Focused;\r
+    return &(Regions[id].view);\r
+}\r
+\r
+Buf* win_buf(WinRegion id) {\r
+    if (id == FOCUSED) id = Focused;\r
+    return &(Regions[id].view.buffer);\r
+}\r
+\r
+Sel* win_sel(WinRegion id) {\r
+    if (id == FOCUSED) id = Focused;\r
+    return &(Regions[id].view.selection);\r
+}\r
+\r
+static void layout(int width, int height) {\r
+    size_t fheight = x11_font_height(Font);\r
+    size_t fwidth  = x11_font_width(Font);\r
+    View* statview = win_view(STATUS);\r
+    View* tagview  = win_view(TAGS);\r
+    View* editview = win_view(EDIT);\r
+    \r
+    /* update the text views and region positions and sizes */\r
+    for (int i = 0; i < SCROLL; i++) {\r
+        Regions[i].x      = 2;\r
+        Regions[i].y      = 2;\r
+        Regions[i].csrx   = SIZE_MAX;\r
+        Regions[i].csry   = SIZE_MAX;\r
+        Regions[i].width  = (width - 4);\r
+        Regions[i].height = fheight;\r
+    }\r
+    \r
+    /* place the status region */\r
+    view_resize(statview, 1, Regions[STATUS].width / fwidth);\r
+    view_update(statview, &(Regions[STATUS].csrx), &(Regions[STATUS].csry));\r
+    \r
+    /* Place the tag region relative to status */\r
+    Regions[TAGS].y = 5 + Regions[STATUS].y + Regions[STATUS].height;\r
+    size_t maxtagrows = ((height - Regions[TAGS].y - 5) / 4) / fheight;\r
+    size_t tagcols    = Regions[TAGS].width / fwidth;\r
+    size_t tagrows    = view_limitrows(tagview, maxtagrows, tagcols);\r
+    Regions[TAGS].height = tagrows * fheight;\r
+    view_resize(tagview, tagrows, tagcols);\r
+    view_update(tagview, &(Regions[TAGS].csrx), &(Regions[TAGS].csry));\r
+    \r
+    /* Place the edit region relative to status */\r
+    Regions[EDIT].y      = 5 + Regions[TAGS].y + Regions[TAGS].height;\r
+    Regions[EDIT].height = (height - Regions[EDIT].y - 5);\r
+    view_resize(editview, Regions[EDIT].height / fheight, Regions[EDIT].width / fwidth);\r
+    view_update(editview, &(Regions[EDIT].csrx), &(Regions[EDIT].csry));\r
+}\r
+\r
+static void onredraw(int width, int height) {\r
+    size_t fheight = x11_font_height(Font);\r
+    size_t fwidth  = x11_font_width(Font);\r
+    \r
+    /* layout and draw the three text regions */\r
+    layout(width, height);\r
+    for (int i = 0; i < SCROLL; i++) {\r
+        View* view = win_view(i);\r
+        x11_draw_rect((i == TAGS ? CLR_BASE02 : CLR_BASE03), \r
+            0, Regions[i].y - 3, width, Regions[i].height + 8);\r
+        x11_draw_rect(CLR_BASE01, 0, Regions[i].y - 3, Regions[i].width + 4, 1);\r
+        for (size_t y = 0; y < view->nrows; y++) {\r
+            Row* row = view_getrow(view, y);\r
+            draw_glyphs(Regions[i].x, Regions[i].y + ((y+1) * fheight), row->cols, row->rlen, row->len);\r
+        }\r
+    }\r
+    \r
+    /* draw the scroll region */\r
+    \r
+    /* place the cursor on screen */\r
+    x11_draw_rect(CLR_BASE3, \r
+        Regions[Focused].x + (Regions[Focused].csrx * fwidth), \r
+        Regions[Focused].y + (Regions[Focused].csry * fheight), \r
+        1, fheight);\r
+    \r
+    /* adjust the mouse location */\r
+    if (Regions[Focused].warp_ptr) {\r
+        Regions[Focused].warp_ptr = false;\r
+        size_t x = Regions[Focused].x + (Regions[Focused].csrx * fwidth)  - (fwidth/2);\r
+        size_t y = Regions[Focused].y + (Regions[Focused].csry * fheight) + (fheight/2);\r
+        x11_warp_mouse(x,y);\r
+    }\r
+}\r
+\r
+static void oninput(int mods, Rune key) {\r
+    /* handle the proper line endings */\r
+    if (key == '\r') key = '\n';\r
+    if (key == '\n' && win_view(FOCUSED)->buffer.crlf) key = RUNE_CRLF;\r
+    /* search for a key binding entry */\r
+    uint32_t mkey = tolower(key);\r
+    for (KeyBinding* bind = Keys; bind && bind->mods; bind++) {\r
+        if ((mkey == bind->key) && (bind->mods == ModAny || bind->mods == mods)) {\r
+            bind->action();\r
+            return;\r
+        }\r
+    }\r
+    \r
+    /* fallback to just inserting the rune if it doesn't fall in the private use area.\r
+     * the private use area is used to encode special keys */\r
+    if (key < 0xE000 || key > 0xF8FF)\r
+        view_insert(win_view(FOCUSED), true, key);\r
+}\r
+\r
+static void onmouse(MouseAct act, MouseBtn btn, int x, int y) {\r
+    WinRegion id = getregion(x, y);\r
+    if (id != TAGS && id != EDIT) return;\r
+    if (Focused != id) Focused = id;\r
+    size_t row = (y-Regions[id].y) / x11_font_height(Font);\r
+    size_t col = (x-Regions[id].x) / x11_font_width(Font);\r
+    if (act == MOUSE_ACT_MOVE) {\r
+        if (MouseBtns[MOUSE_BTN_LEFT].pressed) {\r
+            if (MouseBtns[MOUSE_BTN_LEFT].count == 1) {\r
+                view_setcursor(win_view(id), row, col);\r
+                MouseBtns[MOUSE_BTN_LEFT].count = 0;\r
+            } else {\r
+                view_selext(win_view(id), row, col);\r
+            }\r
+        }\r
+    } else {\r
+        MouseBtns[btn].pressed = (act == MOUSE_ACT_DOWN);\r
+        if (MouseBtns[btn].pressed) {\r
+            /* update the number of clicks and click time */\r
+            uint32_t now = getmillis();\r
+            uint32_t elapsed = now - MouseBtns[btn].time;\r
+            MouseBtns[btn].time = now;\r
+            MouseBtns[btn].region = id;\r
+            if (elapsed <= 250)\r
+                MouseBtns[btn].count++;\r
+            else\r
+                MouseBtns[btn].count = 1;\r
+        } else if (MouseBtns[btn].count > 0) {\r
+            /* execute the action on button release */\r
+            if (MouseActs[btn])\r
+                MouseActs[btn](id, MouseBtns[btn].count, row, col);\r
+        }\r
+    }\r
+}\r
+\r
+static void onshutdown(void) {\r
+    x11_deinit();\r
+}\r
+\r
+static void mouse_wheelup(WinRegion id, size_t count, size_t row, size_t col) {\r
+    view_scroll(win_view(id), -ScrollLines);\r
+}\r
+\r
+static void mouse_wheeldn(WinRegion id, size_t count, size_t row, size_t col) {\r
+    view_scroll(win_view(id), +ScrollLines);\r
+}\r
+\r
+/*****************************************************************************/\r
+\r
+static void draw_glyphs(size_t x, size_t y, UGlyph* glyphs, size_t rlen, size_t ncols) {\r
+    XGlyphSpec specs[rlen];\r
+    size_t i = 0;\r
+    while (rlen && i < ncols) {\r
+        int numspecs = 0;\r
+        uint32_t attr = glyphs[i].attr;\r
+        while (i < ncols && glyphs[i].attr == attr) {\r
+            x11_font_getglyph(Font, &(specs[numspecs]), glyphs[i].rune);\r
+            specs[numspecs].x = x;\r
+            specs[numspecs].y = y - x11_font_descent(Font);\r
+            x += x11_font_width(Font);\r
+            numspecs++;\r
+            i++;\r
+            /* skip over null chars which mark multi column runes */\r
+            for (; i < ncols && !glyphs[i].rune; i++)\r
+                x += x11_font_width(Font);\r
+        }\r
+        /* Draw the glyphs with the proper colors */\r
+        uint8_t bg = attr >> 8;\r
+        uint8_t fg = attr & 0xFF;\r
+        x11_draw_glyphs(fg, bg, specs, numspecs);\r
+        rlen -= numspecs;\r
+    }\r
+}\r
+\r
+static WinRegion getregion(size_t x, size_t y) {\r
+    for (int i = 0; i < NREGIONS; i++) {\r
+        size_t startx = Regions[i].x, endx = startx + Regions[i].width;\r
+        size_t starty = Regions[i].y, endy = starty + Regions[i].height;\r
+        if ((startx <= x && x <= endx) && (starty <= y && y <= endy))\r
+            return (WinRegion)i;\r
+    }\r
+    return NREGIONS;\r
+}\r
+\r
+\r
diff --git a/term.c b/term.c
new file mode 100644 (file)
index 0000000..ca167f2
--- /dev/null
+++ b/term.c
@@ -0,0 +1,718 @@
+#include <stdc.h>
+#include <x11.h>
+#include <utf.h>
+#include <edit.h>
+#include <ctype.h>
+#include <win.h>
+
+/* Mouse Handling
+ *****************************************************************************/
+void mouse_left(WinRegion id, size_t count, size_t row, size_t col) {
+    
+}
+
+void mouse_middle(WinRegion id, size_t count, size_t row, size_t col) {
+    
+}
+
+void mouse_right(WinRegion id, size_t count, size_t row, size_t col) {
+    
+}
+
+MouseConfig* MouseHandlers[NREGIONS] = {
+    [EDIT] = &(MouseConfig){
+        .left   = mouse_left,
+        .middle = mouse_middle,
+        .right  = mouse_right
+    }
+};
+
+/* Keyboard Handling
+ *****************************************************************************/
+
+/* Main Routine
+ *****************************************************************************/
+int main(int argc, char** argv) {
+    win_init("term");
+    //win_setkeys(&Bindings);
+    //win_setmouse(&MouseHandlers);
+    win_loop();
+    return 0;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//static void redraw(int width, int height) {
+#if 0
+    x11_draw_rect(CLR_BASE03, 0, 0, width, height);
+    size_t fheight = x11_font_height(Font);
+    size_t fwidth  = x11_font_width(Font);
+    /* if the window is too small, don't bother updating. */
+    if (width < fwidth || height < (4 * fheight))
+        return;
+    layout(width, height);
+    x11_draw_rect(CLR_BASE03, 0, 0, width, height);
+    x11_draw_rect(CLR_BASE02, 0, Regions[EDIT].y-2, fwidth + 4, height - Regions[EDIT].y + 2);
+    
+    draw_status(CLR_BASE1, (width - 4) / x11_font_width(Font));
+    draw_region(TAGS);
+    draw_region(EDIT);
+    cmdreap(); // cleanup any zombie child processes
+#endif
+//}
+
+#if 0
+#ifdef TEST
+#define exit mockexit
+#endif
+
+enum RegionId {
+    STATUS   = 0,
+    TAGS     = 1,
+    EDIT     = 2,
+    SCROLL   = 3,
+    NREGIONS = 4
+};
+
+// Input Handlers
+static void mouse_handler(MouseAct act, MouseBtn btn, int x, int y);
+static void key_handler(int mods, Rune key);
+
+// Drawing Routines
+static void draw_runes(size_t x, size_t y, int fg, int bg, UGlyph* glyphs, size_t rlen);
+static void draw_glyphs(size_t x, size_t y, UGlyph* glyphs, size_t rlen, size_t ncols);
+static void draw_status(int fg, size_t ncols);
+static void draw_region(enum RegionId id);
+static void layout(int width, int height);
+static void redraw(int width, int height);
+
+// UI Callbacks
+static void delete(void);
+static void del_to_bol(void);
+static void del_to_eol(void);
+static void del_to_bow(void);
+static void backspace(void);
+static void cursor_bol(void);
+static void cursor_eol(void);
+static void cursor_home(void);
+static void cursor_end(void);
+static void cursor_up(void);
+static void cursor_dn(void);
+static void cursor_left(void);
+static void cursor_right(void);
+static void page_up(void);
+static void page_dn(void);
+static void change_focus(void);
+static void quit(void);
+static void undo(void);
+static void redo(void);
+static void tag_undo(void);
+static void tag_redo(void);
+static void cut(void);
+static void copy(void);
+static void paste(void);
+
+// Tag/Cmd Execution
+static Tag* tag_lookup(char* cmd);
+static void tag_exec(Tag* tag, char* arg);
+static void cmd_exec(char* cmd);
+static void exec(char* cmd);
+
+// Mouse Handling
+static void mouse_left(enum RegionId id, size_t count, size_t row, size_t col);
+static void mouse_middle(enum RegionId id, size_t count, size_t row, size_t col);
+static void mouse_right(enum RegionId id, size_t count, size_t row, size_t col);
+static void mouse_wheelup(enum RegionId id, size_t count, size_t row, size_t col);
+static void mouse_wheeldn(enum RegionId id, size_t count, size_t row, size_t col);
+
+// Region Utils
+static View* getview(enum RegionId id);
+static Buf* getbuf(enum RegionId id);
+static View* currview(void);
+static Buf* currbuf(void);
+static enum RegionId getregion(size_t x, size_t y);
+static Sel* getsel(enum RegionId id);
+static Sel* currsel(void);
+
+/* Global Data
+ *****************************************************************************/
+static int SearchDir = DOWN;
+static enum RegionId Focused = EDIT;
+static Region Regions[NREGIONS] = { 0 };
+static ButtonState MouseBtns[MOUSE_BTN_COUNT] = { 0 };
+static XFont Font;
+static XConfig Config = {
+    .redraw       = redraw,
+    .handle_key   = key_handler,
+    .handle_mouse = mouse_handler,
+    .shutdown     = quit,
+    .palette      = COLOR_PALETTE
+};
+
+Tag Builtins[] = {
+    { .tag = "Quit",   .action.noarg = quit     },
+    { .tag = "Cut",    .action.noarg = cut      },
+    { .tag = "Copy",   .action.noarg = copy     },
+    { .tag = "Paste",  .action.noarg = paste    },
+    { .tag = "Undo",   .action.noarg = tag_undo },
+    { .tag = "Redo",   .action.noarg = tag_redo },
+    { .tag = NULL,     .action.noarg = NULL     }
+};
+
+void (*MouseActs[MOUSE_BTN_COUNT])(enum RegionId id, size_t count, size_t row, size_t col) = {
+    [MOUSE_BTN_LEFT]      = mouse_left,
+    [MOUSE_BTN_MIDDLE]    = mouse_middle,
+    [MOUSE_BTN_RIGHT]     = mouse_right,
+    [MOUSE_BTN_WHEELUP]   = mouse_wheelup,
+    [MOUSE_BTN_WHEELDOWN] = mouse_wheeldn,
+};
+
+static KeyBinding Bindings[] = {
+    /* Cursor Movements */
+    { ModAny, KEY_HOME,  cursor_home  },
+    { ModAny, KEY_END,   cursor_end   },
+    { ModAny, KEY_UP,    cursor_up    },
+    { ModAny, KEY_DOWN,  cursor_dn    },
+    { ModAny, KEY_LEFT,  cursor_left  },
+    { ModAny, KEY_RIGHT, cursor_right },
+    
+    /* Standard Unix Shortcuts */
+    { ModCtrl, 'u', del_to_bol  },
+    { ModCtrl, 'k', del_to_eol  },
+    { ModCtrl, 'w', del_to_bow  },
+    { ModCtrl, 'h', backspace   },
+    { ModCtrl, 'a', cursor_bol  },
+    { ModCtrl, 'e', cursor_eol  },
+
+    /* Standard Text Editing Shortcuts */
+    { ModCtrl, 'z', undo  },
+    { ModCtrl, 'y', redo  },
+    { ModCtrl, 'x', cut   },
+    { ModCtrl, 'c', copy  },
+    { ModCtrl, 'v', paste },
+
+    /* Common Special Keys */
+    { ModNone, KEY_PGUP,      page_up   },
+    { ModNone, KEY_PGDN,      page_dn   },
+    { ModAny,  KEY_DELETE,    delete    },
+    { ModAny,  KEY_BACKSPACE, backspace },
+
+    /* Implementation Specific */
+    { ModCtrl, 't', change_focus },
+    { ModCtrl, 'q', quit         },
+};
+
+/* External Commands
+ *****************************************************************************/
+/* The shell: Filled in with $SHELL. Used to execute commands */
+static char* ShellCmd[] = { NULL, "-c", NULL, NULL };
+static char* SedCmd[] = { "sed", "-e", NULL, NULL };
+
+/* Main Routine
+ *****************************************************************************/
+#ifndef TEST
+int main(int argc, char** argv) {
+    /* setup the shell */
+    ShellCmd[0] = getenv("SHELL");
+    if (!ShellCmd[0]) ShellCmd[0] = "/bin/sh";
+    /* load the buffer views */
+    char* tags = getenv("EDITTAGS");
+    view_init(getview(TAGS), NULL);
+    view_putstr(getview(TAGS), (tags ? tags : DEFAULT_TAGS));
+    view_selprev(getview(TAGS)); // clear the selection
+    buf_logclear(getbuf(TAGS));
+    view_init(getview(EDIT), (argc > 1 ? argv[1] : NULL));
+    /* initialize the display engine */
+    x11_init(&Config);
+    x11_window("edit", Width, Height);
+    x11_show();
+    Font = x11_font_load(FONTNAME);
+    x11_loop();
+    return 0;
+}
+#endif
+
+static void mouse_handler(MouseAct act, MouseBtn btn, int x, int y) {
+    enum RegionId id = getregion(x, y);
+    if (id != TAGS && id != EDIT) return;
+    if (Focused != id) Focused = id;
+    size_t row = (y-Regions[id].y) / x11_font_height(Font);
+    size_t col = (x-Regions[id].x) / x11_font_width(Font);
+    if (act == MOUSE_ACT_MOVE) {
+        if (MouseBtns[MOUSE_BTN_LEFT].pressed) {
+            if (MouseBtns[MOUSE_BTN_LEFT].count == 1) {
+                view_setcursor(getview(id), row, col);
+                MouseBtns[MOUSE_BTN_LEFT].count = 0;
+            } else {
+                view_selext(getview(id), row, col);
+            }
+        }
+    } else {
+        MouseBtns[btn].pressed = (act == MOUSE_ACT_DOWN);
+        if (MouseBtns[btn].pressed) {
+            /* update the number of clicks and click time */
+            uint32_t now = getmillis();
+            uint32_t elapsed = now - MouseBtns[btn].time;
+            MouseBtns[btn].time = now;
+            MouseBtns[btn].region = id;
+            if (elapsed <= 250)
+                MouseBtns[btn].count++;
+            else
+                MouseBtns[btn].count = 1;
+        } else if (MouseBtns[btn].count > 0) {
+            /* execute the action on button release */
+            if (MouseActs[btn])
+                MouseActs[btn](id, MouseBtns[btn].count, row, col);
+        }
+    }
+}
+
+static void key_handler(int mods, Rune key) {
+    /* handle the proper line endings */
+    if (key == '\r') key = '\n';
+    if (key == '\n' && currview()->buffer.crlf) key = RUNE_CRLF;
+    /* search for a key binding entry */
+    uint32_t mkey = tolower(key);
+    for (int i = 0; i < nelem(Bindings); i++) {
+        int keymods = Bindings[i].mods;
+        if ((mkey == Bindings[i].key) && (keymods == ModAny || keymods == mods)) {
+            Bindings[i].action();
+            return;
+        }
+    }
+    /* fallback to just inserting the rune if it doesn't fall in the private use area.
+     * the private use area is used to encode special keys */
+    if (key < 0xE000 || key > 0xF8FF)
+        view_insert(currview(), true, key);
+}
+
+/* Drawing Routines
+ *****************************************************************************/
+#ifndef TEST
+static void draw_runes(size_t x, size_t y, int fg, int bg, UGlyph* glyphs, size_t rlen) {
+    XGlyphSpec specs[rlen];
+    while (rlen) {
+        size_t nspecs = x11_font_getglyphs(specs, (XGlyph*)glyphs, rlen, Font, x, y);
+        x11_draw_glyphs(fg, bg, specs, nspecs);
+        rlen -= nspecs;
+    }
+}
+
+static void draw_glyphs(size_t x, size_t y, UGlyph* glyphs, size_t rlen, size_t ncols) {
+    XGlyphSpec specs[rlen];
+    size_t i = 0;
+    while (rlen && i < ncols) {
+        int numspecs = 0;
+        uint32_t attr = glyphs[i].attr;
+        while (i < ncols && glyphs[i].attr == attr) {
+            x11_font_getglyph(Font, &(specs[numspecs]), glyphs[i].rune);
+            specs[numspecs].x = x;
+            specs[numspecs].y = y - x11_font_descent(Font);
+            x += x11_font_width(Font);
+            numspecs++;
+            i++;
+            /* skip over null chars which mark multi column runes */
+            for (; i < ncols && !glyphs[i].rune; i++)
+                x += x11_font_width(Font);
+        }
+        /* Draw the glyphs with the proper colors */
+        uint8_t bg = attr >> 8;
+        uint8_t fg = attr & 0xFF;
+        x11_draw_glyphs(fg, bg, specs, numspecs);
+        rlen -= numspecs;
+    }
+}
+
+static void draw_status(int fg, size_t ncols) {
+    Buf* buf = getbuf(EDIT);
+    UGlyph glyphs[ncols], *status = glyphs;
+    (status++)->rune = (buf->charset == BINARY ? 'B' : 'U');
+    (status++)->rune = (buf->crlf ? 'C' : 'N');
+    (status++)->rune = (buf->expand_tabs ? 'S' : 'T');
+    (status++)->rune = (buf->copy_indent ? 'I' : 'i');
+    (status++)->rune = (SearchDir < 0 ? '<' : '>');
+    (status++)->rune = (buf->modified ? '*' : ' ');
+    (status++)->rune = ' ';
+    char* path = (buf->path ? buf->path : "*scratch*");
+    size_t len = strlen(path);
+    if (len > ncols-4) {
+        (status++)->rune = '.';
+        (status++)->rune = '.';
+        (status++)->rune = '.';
+        path += (len - ncols) + 6;
+    }
+    while (*path)
+        (status++)->rune = *path++;
+    draw_runes(2, 2, fg, 0, glyphs, status - glyphs);
+}
+
+static void draw_region(enum RegionId id) {
+    size_t fheight = x11_font_height(Font);
+    size_t fwidth  = x11_font_width(Font);
+    /* update the screen buffer and retrieve cursor coordinates */
+    View* view = getview(id);
+    size_t csrx = SIZE_MAX, csry = SIZE_MAX;
+    view_update(view, &csrx, &csry);
+    /* draw the region to the frame buffer */
+    if (id == TAGS)
+        x11_draw_rect(CLR_BASE02, Regions[id].x-2, Regions[id].y-2, Regions[id].width+4, Regions[id].height+4);
+    x11_draw_rect(CLR_BASE01, 0, Regions[id].y - 3, Regions[id].width + 4, 1);
+    for (size_t y = 0; y < view->nrows; y++) {
+        Row* row = view_getrow(view, y);
+        draw_glyphs(Regions[id].x, Regions[id].y + ((y+1) * fheight), row->cols, row->rlen, row->len);
+    }
+    /* Place cursor on screen */
+    if (id == Focused && csrx != SIZE_MAX && csry != SIZE_MAX)
+        x11_draw_rect(CLR_BASE3, Regions[id].x + csrx * fwidth, Regions[id].y + (csry * fheight), 1, fheight);
+
+    if (Regions[id].warp_ptr) {
+        Regions[id].warp_ptr = false;
+        size_t x = Regions[id].x + (csrx * fwidth)  - (fwidth/2);
+        size_t y = Regions[id].y + (csry * fheight) + (fheight/2);
+        x11_warp_mouse(x,y);
+    }
+}
+
+static void layout(int width, int height) {
+    /* initialize all of the regions to overlap the status region */
+    size_t fheight = x11_font_height(Font);
+    size_t fwidth  = x11_font_width(Font);
+    for (int i = 0; i < NREGIONS; i++) {
+        Regions[i].x      = 2;
+        Regions[i].y      = 2;
+        Regions[i].width  = (width - 4);
+        Regions[i].height = fheight;
+    }
+    /* Place the tag region relative to status */
+    Regions[TAGS].y = 5 + Regions[STATUS].y + Regions[STATUS].height;
+    size_t maxtagrows = ((height - Regions[TAGS].y - 5) / 4) / fheight;
+    size_t tagcols    = Regions[TAGS].width / fwidth;
+    size_t tagrows    = view_limitrows(getview(TAGS), maxtagrows, tagcols);
+    Regions[TAGS].height = tagrows * fheight;
+    view_resize(getview(TAGS), tagrows, tagcols);
+    /* Place the edit region relative to status */
+    Regions[EDIT].x = 50;
+    Regions[EDIT].y      = 5 + Regions[TAGS].y + Regions[TAGS].height;
+    Regions[EDIT].height = (height - Regions[EDIT].y - 5);
+    view_resize(getview(EDIT), Regions[EDIT].height / fheight, Regions[EDIT].width / fwidth);
+}
+
+static void redraw(int width, int height) {
+    size_t fheight = x11_font_height(Font);
+    size_t fwidth  = x11_font_width(Font);
+    /* if the window is too small, don't bother updating. */
+    if (width < fwidth || height < (4 * fheight))
+        return;
+    layout(width, height);
+    x11_draw_rect(CLR_BASE03, 0, 0, width, height);
+    x11_draw_rect(CLR_BASE02, 0, Regions[EDIT].y-2, fwidth + 4, height - Regions[EDIT].y + 2);
+    
+    draw_status(CLR_BASE1, (width - 4) / x11_font_width(Font));
+    draw_region(TAGS);
+    draw_region(EDIT);
+    cmdreap(); // cleanup any zombie child processes
+}
+#endif
+
+/* UI Callbacks
+ *****************************************************************************/
+static void delete(void) {
+    bool byword = x11_keymodsset(ModCtrl);
+    view_delete(currview(), RIGHT, byword);
+}
+
+static void del_to_bol(void) {
+    view_bol(currview(), true);
+    if (view_selsize(currview()) > 0) 
+        delete();
+}
+
+static void del_to_eol(void) {
+    view_eol(currview(), true);
+    if (view_selsize(currview()) > 0) 
+        delete();
+}
+
+static void del_to_bow(void) {
+    view_byword(currview(), LEFT, true);
+    if (view_selsize(currview()) > 0) 
+        delete();
+}
+
+static void backspace(void) {
+    bool byword = x11_keymodsset(ModCtrl);
+    view_delete(currview(), LEFT, byword);
+}
+
+static void cursor_bol(void) {
+    view_bol(currview(), false);
+}
+
+static void cursor_eol(void) {
+    view_eol(currview(), false);
+}
+
+static void cursor_home(void) {
+    bool extsel = x11_keymodsset(ModShift);
+    if (x11_keymodsset(ModCtrl))
+        view_bof(currview(), extsel);
+    else
+        view_bol(currview(), extsel);
+}
+
+static void cursor_end(void) {
+    bool extsel = x11_keymodsset(ModShift);
+    if (x11_keymodsset(ModCtrl))
+        view_eof(currview(), extsel);
+    else
+        view_eol(currview(), extsel);
+}
+
+static void cursor_up(void) {
+    bool extsel = x11_keymodsset(ModShift);
+    view_byline(currview(), UP, extsel);
+}
+
+static void cursor_dn(void) {
+    bool extsel = x11_keymodsset(ModShift);
+    view_byline(currview(), DOWN, extsel);
+}
+
+static void cursor_left(void) {
+    bool extsel = x11_keymodsset(ModShift);
+    if (x11_keymodsset(ModCtrl))
+        view_byword(currview(), LEFT, extsel);
+    else
+        view_byrune(currview(), LEFT, extsel);
+}
+
+static void cursor_right(void) {
+    bool extsel = x11_keymodsset(ModShift);
+    if (x11_keymodsset(ModCtrl))
+        view_byword(currview(), RIGHT, extsel);
+    else
+        view_byrune(currview(), RIGHT, extsel);
+}
+
+static void page_up(void) {
+    view_scrollpage(currview(), UP);
+}
+
+static void page_dn(void) {
+    view_scrollpage(currview(), DOWN);
+}
+
+static void change_focus(void) {
+    Focused = (Focused == TAGS ? EDIT : TAGS);
+}
+
+static void quit(void) {
+    x11_deinit();
+}
+
+static void undo(void) {
+    view_undo(currview());
+}
+
+static void redo(void) {
+    view_redo(currview());
+}
+
+static void tag_undo(void) {
+    view_undo(getview(EDIT));
+}
+
+static void tag_redo(void) {
+    view_redo(getview(EDIT));
+}
+
+static void cut(void) {
+    char* str = view_getstr(currview(), NULL);
+    x11_setsel(CLIPBOARD, str);
+    if (str && *str) delete();
+}
+
+static void copy(void) {
+    char* str = view_getstr(currview(), NULL);
+    x11_setsel(CLIPBOARD, str);
+}
+
+static void onpaste(char* text) {
+    view_putstr(currview(), text);
+}
+
+static void paste(void) {
+    assert(x11_getsel(CLIPBOARD, onpaste));
+}
+
+/* Tag/Cmd Execution
+ *****************************************************************************/
+static Tag* tag_lookup(char* cmd) {
+    size_t len = 0;
+    Tag* tags = Builtins;
+    for (char* tag = cmd; *tag && !isspace(*tag); tag++, len++);
+    while (tags->tag) {
+        if (!strncmp(tags->tag, cmd, len))
+            return tags;
+        tags++;
+    }
+    return NULL;
+}
+
+static void tag_exec(Tag* tag, char* arg) {
+    /* if we didnt get an arg, find one in the selection */
+    if (!arg) arg = view_getstr(getview(TAGS), NULL);
+    if (!arg) arg = view_getstr(getview(EDIT), NULL);
+    /* execute the tag handler */
+    tag->action.arg(arg);
+    free(arg);
+}
+
+static void cmd_exec(char* cmd) {
+    char op = '\0';
+    if (*cmd == ':' || *cmd == '!' || *cmd == '<' || *cmd == '|' || *cmd == '>')
+        op = *cmd, cmd++;
+    ShellCmd[2] = cmd;
+    /* execute the command */
+    char *input = NULL, *output = NULL, *error = NULL;
+    enum RegionId dest = EDIT;
+    if (op && op != '<' && op != '!' && 0 == view_selsize(getview(EDIT)))
+        getview(EDIT)->selection = (Sel){ .beg = 0, .end = buf_end(getbuf(EDIT)) };
+    input = view_getstr(getview(EDIT), NULL);
+
+    if (op == '!') {
+        cmdrun(ShellCmd, NULL);
+    } else if (op == '>') {
+        dest = TAGS;
+        output = cmdwriteread(ShellCmd, input, &error);
+    } else if (op == '|') {
+        output = cmdwriteread(ShellCmd, input, &error);
+    } else if (op == ':') {
+        SedCmd[2] = cmd;
+        output = cmdwriteread(SedCmd, input, &error);
+    } else {
+        if (op != '<') dest = Focused;
+        output = cmdread(ShellCmd, &error);
+    }
+    
+    if (error)
+        view_append(getview(TAGS), chomp(error));
+    
+    if (output) {
+        if (op == '>')
+            view_append(getview(dest), chomp(output));
+        else
+            view_putstr(getview(dest), output);
+        Focused = dest;
+    }
+    /* cleanup */
+    free(input);
+    free(output);
+    free(error);
+}
+
+static void exec(char* cmd) {
+    /* skip leading space */
+    for (; *cmd && isspace(*cmd); cmd++);
+    if (!*cmd) return;
+    /* see if it matches a builtin tag */
+    Tag* tag = tag_lookup(cmd);
+    if (tag) {
+        while (*cmd && !isspace(*cmd++));
+        tag_exec(tag, (*cmd ? stringdup(cmd) : NULL));
+    } else {
+        cmd_exec(cmd);
+    }
+}
+
+/* Mouse Handling
+ *****************************************************************************/
+static void mouse_left(enum RegionId id, size_t count, size_t row, size_t col) {
+    if (count == 1) {
+        if (x11_keymodsset(ModShift))
+            view_selext(getview(id), row, col);
+        else
+            view_setcursor(getview(id), row, col);
+    } else if (count == 2) {
+        view_select(getview(id), row, col);
+    } else if (count == 3) {
+        view_selword(getview(id), row, col);
+    }
+}
+
+static void mouse_middle(enum RegionId id, size_t count, size_t row, size_t col) {
+    if (MouseBtns[MOUSE_BTN_LEFT].pressed) {
+        cut();
+    } else {
+        char* str = view_fetchcmd(getview(id), row, col);
+        if (str) exec(str);
+        free(str);
+    }
+}
+
+static void mouse_right(enum RegionId id, size_t count, size_t row, size_t col) {
+    if (MouseBtns[MOUSE_BTN_LEFT].pressed) {
+        paste();
+    } else {
+        SearchDir *= (x11_keymodsset(ModShift) ? -1 : +1);
+        view_find(getview(id), SearchDir, row, col);
+        Regions[id].warp_ptr = true;
+    }
+}
+
+static void mouse_wheelup(enum RegionId id, size_t count, size_t row, size_t col) {
+    view_scroll(getview(id), -ScrollLines);
+}
+
+static void mouse_wheeldn(enum RegionId id, size_t count, size_t row, size_t col) {
+    view_scroll(getview(id), +ScrollLines);
+}
+
+/* Region Utils
+ *****************************************************************************/
+static View* getview(enum RegionId id) {
+    return &(Regions[id].view);
+}
+
+static Buf* getbuf(enum RegionId id) {
+    return &(getview(id)->buffer);
+}
+
+static View* currview(void) {
+    return getview(Focused);
+}
+
+static Buf* currbuf(void) {
+    return getbuf(Focused);
+}
+
+static enum RegionId getregion(size_t x, size_t y) {
+    for (int i = 0; i < NREGIONS; i++) {
+        size_t startx = Regions[i].x, endx = startx + Regions[i].width;
+        size_t starty = Regions[i].y, endy = starty + Regions[i].height;
+        if ((startx <= x && x <= endx) && (starty <= y && y <= endy))
+            return (enum RegionId)i;
+    }
+    return NREGIONS;
+}
+
+static Sel* getsel(enum RegionId id) {
+    return &(getview(id)->selection);
+}
+
+static Sel* currsel(void) {
+    return getsel(Focused);
+}
+#endif