From: Michael D. Lowis Date: Thu, 2 Feb 2017 18:54:07 +0000 (-0500) Subject: Initial commit of new win module to commonize the ui X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=f447e1b7dd2251dc4a484ebc0794f6cd94827d71;p=projs%2Ftide.git Initial commit of new win module to commonize the ui --- diff --git a/Makefile b/Makefile index 9f2f0a5..f69808a 100644 --- 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) $@ $^ diff --git a/config.mk b/config.mk index 3a9bc82..df3f115 100644 --- 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) diff --git a/inc/edit.h b/inc/edit.h index 43e925b..ec851e4 100644 --- a/inc/edit.h +++ b/inc/edit.h @@ -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 index 0000000..1c9b7fe --- /dev/null +++ b/inc/win.h @@ -0,0 +1,45 @@ +typedef enum { + STATUS = 0, + TAGS = 1, + EDIT = 2, + SCROLL = 3, + NREGIONS = 4, + FOCUSED = 4 +} WinRegion; + +#if 0 +typedef struct { + size_t x; + size_t y; + size_t height; + size_t width; + size_t csrx; + size_t csry; + bool warp_ptr; + View view; +} Region; +#endif + +typedef void (*MouseFunc)(WinRegion id, size_t count, size_t row, size_t col); + +typedef struct { + MouseFunc left; + MouseFunc middle; + MouseFunc right; +} MouseConfig; + +void win_init(char* name); +void win_loop(void); +void win_settext(WinRegion id, char* text); +void win_setkeys(KeyBinding* bindings); +void win_setmouse(MouseConfig* mconfig); +View* win_view(WinRegion id); +Buf* win_buf(WinRegion id); +Sel* win_sel(WinRegion id); + +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); + + + diff --git a/lib/win.c b/lib/win.c new file mode 100644 index 0000000..b333781 --- /dev/null +++ b/lib/win.c @@ -0,0 +1,251 @@ +#include +#include +#include +#include +#include + +static void draw_glyphs(size_t x, size_t y, UGlyph* glyphs, size_t rlen, size_t ncols); +static WinRegion getregion(size_t x, size_t y); +static void onredraw(int height, int width); +static void oninput(int mods, Rune key); +static void onmouse(MouseAct act, MouseBtn btn, int x, int y); +static void onshutdown(void); +static void mouse_wheelup(WinRegion id, size_t count, size_t row, size_t col); +static void mouse_wheeldn(WinRegion id, size_t count, size_t row, size_t col); + +static XFont Font; +static XConfig Config = { + .redraw = onredraw, + .handle_key = oninput, + .handle_mouse = onmouse, + .shutdown = onshutdown, + .palette = COLOR_PALETTE +}; + +void (*MouseActs[MOUSE_BTN_COUNT])(WinRegion 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 WinRegion Focused = EDIT; +static Region Regions[NREGIONS] = {0}; +static ButtonState MouseBtns[MOUSE_BTN_COUNT] = {0}; +KeyBinding* Keys = NULL; + +void win_init(char* name) { + win_settext(STATUS, "This is a status line"); + for (int i = 1; i < SCROLL; i++) + view_init(&(Regions[i].view), NULL); + x11_init(&Config); + Font = x11_font_load(FONTNAME); + x11_window(name, Width, Height); + x11_show(); +} + +void win_loop(void) { + x11_loop(); +} + +void win_settext(WinRegion id, char* text) { + view_init(win_view(id), NULL); + view_putstr(win_view(id), text); + view_selprev(win_view(id)); // clear the selection +} + +void win_setkeys(KeyBinding* bindings) { + Keys = bindings; +} + +View* win_view(WinRegion id) { + if (id == FOCUSED) id = Focused; + return &(Regions[id].view); +} + +Buf* win_buf(WinRegion id) { + if (id == FOCUSED) id = Focused; + return &(Regions[id].view.buffer); +} + +Sel* win_sel(WinRegion id) { + if (id == FOCUSED) id = Focused; + return &(Regions[id].view.selection); +} + +static void layout(int width, int height) { + size_t fheight = x11_font_height(Font); + size_t fwidth = x11_font_width(Font); + View* statview = win_view(STATUS); + View* tagview = win_view(TAGS); + View* editview = win_view(EDIT); + + /* update the text views and region positions and sizes */ + for (int i = 0; i < SCROLL; i++) { + Regions[i].x = 2; + Regions[i].y = 2; + Regions[i].csrx = SIZE_MAX; + Regions[i].csry = SIZE_MAX; + Regions[i].width = (width - 4); + Regions[i].height = fheight; + } + + /* place the status region */ + view_resize(statview, 1, Regions[STATUS].width / fwidth); + view_update(statview, &(Regions[STATUS].csrx), &(Regions[STATUS].csry)); + + /* 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(tagview, maxtagrows, tagcols); + Regions[TAGS].height = tagrows * fheight; + view_resize(tagview, tagrows, tagcols); + view_update(tagview, &(Regions[TAGS].csrx), &(Regions[TAGS].csry)); + + /* Place the edit region relative to status */ + Regions[EDIT].y = 5 + Regions[TAGS].y + Regions[TAGS].height; + Regions[EDIT].height = (height - Regions[EDIT].y - 5); + view_resize(editview, Regions[EDIT].height / fheight, Regions[EDIT].width / fwidth); + view_update(editview, &(Regions[EDIT].csrx), &(Regions[EDIT].csry)); +} + +static void onredraw(int width, int height) { + size_t fheight = x11_font_height(Font); + size_t fwidth = x11_font_width(Font); + + /* layout and draw the three text regions */ + layout(width, height); + for (int i = 0; i < SCROLL; i++) { + View* view = win_view(i); + x11_draw_rect((i == TAGS ? CLR_BASE02 : CLR_BASE03), + 0, Regions[i].y - 3, width, Regions[i].height + 8); + x11_draw_rect(CLR_BASE01, 0, Regions[i].y - 3, Regions[i].width + 4, 1); + for (size_t y = 0; y < view->nrows; y++) { + Row* row = view_getrow(view, y); + draw_glyphs(Regions[i].x, Regions[i].y + ((y+1) * fheight), row->cols, row->rlen, row->len); + } + } + + /* draw the scroll region */ + + /* place the cursor on screen */ + x11_draw_rect(CLR_BASE3, + Regions[Focused].x + (Regions[Focused].csrx * fwidth), + Regions[Focused].y + (Regions[Focused].csry * fheight), + 1, fheight); + + /* adjust the mouse location */ + if (Regions[Focused].warp_ptr) { + Regions[Focused].warp_ptr = false; + size_t x = Regions[Focused].x + (Regions[Focused].csrx * fwidth) - (fwidth/2); + size_t y = Regions[Focused].y + (Regions[Focused].csry * fheight) + (fheight/2); + x11_warp_mouse(x,y); + } +} + +static void oninput(int mods, Rune key) { + /* handle the proper line endings */ + if (key == '\r') key = '\n'; + if (key == '\n' && win_view(FOCUSED)->buffer.crlf) key = RUNE_CRLF; + /* search for a key binding entry */ + uint32_t mkey = tolower(key); + for (KeyBinding* bind = Keys; bind && bind->mods; bind++) { + if ((mkey == bind->key) && (bind->mods == ModAny || bind->mods == mods)) { + bind->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(win_view(FOCUSED), true, key); +} + +static void onmouse(MouseAct act, MouseBtn btn, int x, int y) { + WinRegion 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(win_view(id), row, col); + MouseBtns[MOUSE_BTN_LEFT].count = 0; + } else { + view_selext(win_view(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 onshutdown(void) { + x11_deinit(); +} + +static void mouse_wheelup(WinRegion id, size_t count, size_t row, size_t col) { + view_scroll(win_view(id), -ScrollLines); +} + +static void mouse_wheeldn(WinRegion id, size_t count, size_t row, size_t col) { + view_scroll(win_view(id), +ScrollLines); +} + +/*****************************************************************************/ + +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 WinRegion 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 (WinRegion)i; + } + return NREGIONS; +} + + diff --git a/term.c b/term.c new file mode 100644 index 0000000..ca167f2 --- /dev/null +++ b/term.c @@ -0,0 +1,718 @@ +#include +#include +#include +#include +#include +#include + +/* 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