--- /dev/null
+#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
--- /dev/null
+#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