diff --git a/cmd/menu/Makefile b/cmd/menu/Makefile index af872017..e30304b4 100644 --- a/cmd/menu/Makefile +++ b/cmd/menu/Makefile @@ -13,6 +13,7 @@ LDFLAGS += -lm $(LIBX11) -lXext -lXrandr -lXinerama \ CFLAGS += $(INCX11) -DVERSION=\"$(VERSION)\" \ -DIXP_NEEDAPI=86 OBJ = main \ + caret \ event \ menu \ ../wmii/geom \ diff --git a/cmd/menu/caret.c b/cmd/menu/caret.c new file mode 100644 index 00000000..9d586490 --- /dev/null +++ b/cmd/menu/caret.c @@ -0,0 +1,140 @@ +#include "dat.h" +#include +#include +#include "fns.h" + +static int +iswordrune(Rune r) { + if(isalpharune(r)) + return 1; + return r < 0x80 && (r == '_' || isdigit(r)); +} + +static char* +prev_rune(char *start, char *p, Rune *r) { + + *r = 0; + if(p == start) + return p; + while(p > start && (*(--p)&0xC0) == 0x80) + ; + chartorune(r, p); + return p; +} + +static char* +next_rune(char *p, Rune *r) { + int i; + + *r = 0; + if(!*p) + return p; + i = chartorune(r, p); + return p + i; +} + +char* +caret_find(int dir, int type) { + char *end; + char *next, *p; + Rune r; + int res; + + p = input.pos; + if(dir == FORWARD) { + end = input.end; + switch(type) { + case LINE: + return end; + case WORD: + chartorune(&r, p); + res = iswordrune(r); + while(next=next_rune(p, &r), r && iswordrune(r) == res && !isspacerune(r)) + p = next; + while(next=next_rune(p, &r), r && isspacerune(r)) + p = next; + return p; + case CHAR: + if(p < end) + return p+1; + return p; + } + } + else if(dir == BACKWARD) { + end = input.string; + switch(type) { + case LINE: + return end; + case WORD: + while(next=prev_rune(end, p, &r), r && isspacerune(r)) + p = next; + prev_rune(end, p, &r); + res = iswordrune(r); + while(next=prev_rune(end, p, &r), r && iswordrune(r) == res && !isspacerune(r)) + p = next; + return p; + case CHAR: + if(p > end) + return p-1; + return end; + } + } + die("not reached"); + return nil; /* shut up ken */ +} + +void +caret_move(int dir, int type) { + input.pos = caret_find(dir, type); +} + +void +caret_delete(int dir, int type) { + char *pos, *p; + int n; + + p = caret_find(dir, type); + pos = input.pos; + if(p == input.end) + input.end = pos; + else { + if(p < pos) { + pos = p; + p = input.pos; + } + n = input.end - p; + memmove(pos, p, n); + input.pos = pos; + input.end = pos + n; + } + *input.end = '\0'; +} + +void +caret_insert(char *s, bool clear) { + int pos, end, len, size; + + if(clear) { + input.pos = input.string; + input.end = input.string; + } + len = strlen(s); + pos = input.pos - input.string; + end = input.end - input.string; + + size = input.size; + if(input.size == 0) + input.size = 1; + while(input.size < end + len + 1) + input.size <<= 2; + if(input.size != size) + input.string = erealloc(input.string, input.size); + + input.pos = input.string + pos; + input.end = input.string + end + len; + *input.end = '\0'; + memmove(input.pos + len, input.pos, end - pos); + memmove(input.pos, s, len); + input.pos += len; +} + diff --git a/cmd/menu/dat.h b/cmd/menu/dat.h index 04addf71..209461d6 100644 --- a/cmd/menu/dat.h +++ b/cmd/menu/dat.h @@ -15,6 +15,15 @@ # define EXTERN extern #endif +enum { + FORWARD, + BACKWARD, + LINE, + WORD, + CHAR, + CARET_LAST, +}; + typedef struct Item Item; struct Item { @@ -27,6 +36,14 @@ struct Item { int width; }; +EXTERN struct { + char* string; + char* end; + char* pos; + int len; + int size; +} input; + EXTERN long xtime; EXTERN Image* ibuf; EXTERN Font* font; @@ -46,8 +63,6 @@ EXTERN Item* matchidx; EXTERN Item* histidx; -EXTERN char filter[1024]; - EXTERN int maxwidth; EXTERN int result; diff --git a/cmd/menu/fns.h b/cmd/menu/fns.h index 2d0bb446..ffdbc971 100644 --- a/cmd/menu/fns.h +++ b/cmd/menu/fns.h @@ -1,4 +1,8 @@ +void caret_delete(int, int); +char* caret_find(int, int); +void caret_insert(char*, bool); +void caret_move(int, int); void check_x_event(IxpConn*); void debug(int, const char*, ...); void dispatch_event(XEvent*); diff --git a/cmd/menu/main.c b/cmd/menu/main.c index 33a05b57..268c0247 100644 --- a/cmd/menu/main.c +++ b/cmd/menu/main.c @@ -151,7 +151,7 @@ update_filter(void) { /* TODO: Perhaps filter only previous matches unless filter * has been truncated. */ - matchfirst = matchstart = matchidx = filter_list(items, filter); + matchfirst = matchstart = matchidx = filter_list(items, input.string); } /* @@ -264,6 +264,7 @@ main(int argc, char *argv[]) { inbuf = Bfdopen(0, OREAD); items = populate_list(inbuf, false); + caret_insert("", true); update_filter(); Bterm(inbuf); diff --git a/cmd/menu/menu.c b/cmd/menu/menu.c index 66907339..baccb036 100644 --- a/cmd/menu/menu.c +++ b/cmd/menu/menu.c @@ -12,13 +12,11 @@ static int numlock; static void menu_draw(void); enum { - ACCEPT, + ACCEPT = CARET_LAST, REJECT, HIST_NEXT, HIST_PREV, - KILL_CHAR, - KILL_WORD, - KILL_LINE, + KILL, CMPL_NEXT, CMPL_PREV, CMPL_FIRST, @@ -57,47 +55,29 @@ histtext(Item *i) { if(!histidx->string) { free(orig); - orig = strdup(filter); + orig = strdup(input.string); } return i->string ? i->string : orig; } static void -menu_cmd(int op) { - bool res; - int i; +menu_cmd(int op, int motion) { - i = strlen(filter); switch(op) { case HIST_NEXT: if(histidx->next) { - strncpy(filter, histtext(histidx->next), sizeof filter); + caret_insert(histtext(histidx->next), true); histidx = histidx->next; } break; case HIST_PREV: if(histidx->prev) { - strncpy(filter, histtext(histidx->prev), sizeof filter); + caret_insert(histtext(histidx->prev), true); histidx = histidx->prev; } break; - case KILL_CHAR: - if(i > 0) - filter[i-1] = '\0'; - break; - case KILL_WORD: - if(i == 0) - break; - for(i--; i >= 0 && isspace(filter[i]); i--) - filter[i] = '\0'; - if(i >= 0) - res = !isalnum(filter[i]); - for(; i >= 0 && !isalnum(filter[i]) == res && !isspace(filter[i]); i--) - filter[i] = '\0'; - break; - case KILL_LINE: - /* TODO: Add a caret. */ - filter[0] = '\0'; + case KILL: + caret_delete(BACKWARD, motion); break; default: goto next; @@ -116,6 +96,10 @@ next: srv.running = false; result = 1; break; + case BACKWARD: + case FORWARD: + caret_move(op, motion); + break; case CMPL_NEXT: matchidx = matchidx->next; break; @@ -203,9 +187,9 @@ menu_draw(void) { drawstring(ibuf, font, r2, East, ">", cnorm.fg); r2 = r; r2.max.x = inputw; - drawstring(ibuf, font, r2, West, filter, cnorm.fg); + drawstring(ibuf, font, r2, West, input.string, cnorm.fg); - r2.min.x = textwidth(font, filter) + pad/2; + r2.min.x = textwidth_l(font, input.string, input.pos - input.string) + pad/2; r2.max.x = r2.min.x + 2; r2.min.y++; r2.max.y--; @@ -244,7 +228,7 @@ menu_show(void) { static void kdown_event(Window *w, XKeyEvent *e) { char buf[32]; - int num, i; + int num; KeySym ksym; buf[0] = 0; @@ -268,37 +252,41 @@ kdown_event(Window *w, XKeyEvent *e) { default: return; case XK_bracketleft: /* Esc */ - menu_cmd(REJECT); + menu_cmd(REJECT, 0); return; case XK_j: case XK_J: case XK_m: case XK_M: - menu_cmd(ACCEPT); + menu_cmd(ACCEPT, 0); return; case XK_n: case XK_N: - menu_cmd(HIST_NEXT); + menu_cmd(HIST_NEXT, 0); return; case XK_p: case XK_P: - menu_cmd(HIST_PREV); + menu_cmd(HIST_PREV, 0); return; case XK_i: /* Tab */ case XK_I: - menu_cmd(CMPL_NEXT); + if(e->state & ShiftMask) + menu_cmd(CMPL_PREV, 0); + else + menu_cmd(CMPL_NEXT, 0); return; case XK_h: case XK_H: - menu_cmd(KILL_CHAR); + menu_cmd(KILL, CHAR); return; + case XK_BackSpace: case XK_w: case XK_W: - menu_cmd(KILL_WORD); + menu_cmd(KILL, WORD); return; case XK_u: case XK_U: - menu_cmd(KILL_LINE); + menu_cmd(KILL, LINE); return; } } @@ -308,74 +296,70 @@ kdown_event(Window *w, XKeyEvent *e) { default: return; case XK_h: - menu_cmd(CMPL_PREV); + menu_cmd(CMPL_PREV, 0); return; case XK_l: - menu_cmd(CMPL_NEXT); + menu_cmd(CMPL_NEXT, 0); return; case XK_j: - menu_cmd(CMPL_NEXT_PAGE); + menu_cmd(CMPL_NEXT_PAGE, 0); return; case XK_k: - menu_cmd(CMPL_PREV_PAGE); + menu_cmd(CMPL_PREV_PAGE, 0); return; case XK_g: - menu_cmd(CMPL_FIRST); + menu_cmd(CMPL_FIRST, 0); return; case XK_G: - menu_cmd(CMPL_LAST); + menu_cmd(CMPL_LAST, 0); return; } } switch(ksym) { default: if(num && !iscntrl(buf[0])) { - i = strlen(filter); - if(i < sizeof filter - 1) { - filter[i] = buf[0]; - filter[i+1] = '\0'; - } + caret_insert(buf, false); update_filter(); menu_draw(); } break; case XK_Escape: - menu_cmd(REJECT); + menu_cmd(REJECT, 0); return; case XK_Return: - menu_cmd(ACCEPT); + menu_cmd(ACCEPT, 0); return; case XK_BackSpace: - menu_cmd(KILL_CHAR); + menu_cmd(KILL, CHAR); return; case XK_Up: - menu_cmd(HIST_PREV); + menu_cmd(HIST_PREV, 0); return; case XK_Down: - menu_cmd(HIST_NEXT); + menu_cmd(HIST_NEXT, 0); return; case XK_Home: /* TODO: Caret. */ - menu_cmd(CMPL_FIRST); + menu_cmd(CMPL_FIRST, 0); return; case XK_End: /* TODO: Caret. */ - menu_cmd(CMPL_LAST); + menu_cmd(CMPL_LAST, 0); return; case XK_Left: - menu_cmd(CMPL_PREV); + menu_cmd(BACKWARD, CHAR); return; case XK_Right: - menu_cmd(CMPL_NEXT); + menu_cmd(FORWARD, CHAR); return; case XK_Next: - menu_cmd(CMPL_NEXT_PAGE); + menu_cmd(CMPL_NEXT_PAGE, 0); return; case XK_Prior: - menu_cmd(CMPL_PREV_PAGE); + menu_cmd(CMPL_PREV_PAGE, 0); return; case XK_Tab: - menu_cmd(CMPL_NEXT); + menu_cmd(CMPL_NEXT, 0); return; } }