ref: ffc34e1ca04664f0bc4dfe40522190b0d33cc2f0
dir: /dmenu.c/
#include <u.h> #include <libc.h> #include <bio.h> #include <draw.h> #include <event.h> #include <keyboard.h> #define Ctl(c) ((c) - 64) #define PROMPT " > " enum { BACK, HBACK, TEXT, HTEXT, NCOLORS }; Image *color[NCOLORS]; char **lines, **matches, *buffer; usize nlines, nmatches, scroll; Rune kbinput[512]; int selected; static void readbuffer(void) { Biobuf *bp; char *line, *s; if((bp = Bfdopen(0, OREAD)) == nil) sysfatal("setting buffering on fd0: %r"); werrstr("empty"); if((buffer = Brdstr(bp, '\0', 1)) == nil) sysfatal("reading input lines: %r"); for(line = s = buffer; s = strchr(s, '\n'); line = ++s){ *s = '\0'; if((lines = realloc(lines, ++nlines * sizeof *lines)) == nil) sysfatal("malloc: %r"); lines[nlines-1] = line; } if((matches = malloc(nlines * sizeof *lines)) == nil) sysfatal("malloc: %r"); memmove(matches, lines, nlines * sizeof *lines); nmatches = nlines; } static Point linetopoint(int ln) { ln -= scroll; return Pt(screen->r.min.x, screen->r.min.y + (ln + 2) * font->height); } static int pointtoline(Point pt) { return (pt.y - screen->r.min.y) / font->height - 2 + scroll; } static void drawbackground(Point pt, Image *color) { draw(screen, Rect(screen->r.min.x, pt.y, screen->r.max.x, pt.y + font->height), color, nil, ZP); } static Point tabstring(Image *dst, Point dp, Image *src, Point sp, Font *f, char *s) { Rune r[2] = {L'0', L'\0'}; int n, w0, x; Point op; op = dp; w0 = stringwidth(f, "0"); for(; *s && (n = chartorune(r, s)) > 0; s += n){ if(r[0] == '\t'){ x = 8 * w0 - (dp.x - op.x) % (8 * w0); sp.x += x; dp.x += x; }else{ dp = runestring(dst, dp, src, sp, f, r); } } return dp; } static void drawprompt(void) { Point pt; char buf[512]; snprint(buf, sizeof buf, PROMPT"%S▏", kbinput); pt = Pt(screen->r.max.x, screen->r.min.y + font->height * 2); draw(screen, Rpt(screen->r.min, pt), color[BACK], nil, ZP); pt = Pt(screen->r.min.x, screen->r.min.y + font->height); tabstring(screen, pt, color[TEXT], ZP, font, buf); } static int drawline(int ln) { Point pt = linetopoint(ln); Image *bgcolor, *txcolor; if(ln < scroll) return 1; if(ln == selected){ bgcolor = color[HBACK]; txcolor = color[HTEXT]; }else{ bgcolor = color[BACK]; txcolor = color[TEXT]; } pt.x += stringwidth(font, PROMPT); drawbackground(pt, bgcolor); if(ln < nmatches) tabstring(screen, pt, txcolor, ZP, font, matches[ln]); return pt.y < screen->r.max.y; } void eresized(int new) { int i; if(new && getwindow(display, Refnone) < 0) sysfatal("resize failed: %r"); drawprompt(); for(i = scroll; drawline(i); i++); } int match(char *s, char **words, int nwords) { char **w; if(nwords == 0) return 1; for(w = words; w < words + nwords; w++) if(cistrstr(s, *w) == nil) return 0; return 1; } void filter(char **lines, int nlines) { char *buf, *words[64], **l, **m; int nwords, old; if((buf = smprint("%S", kbinput)) == nil) sysfatal("malloc"); nwords = tokenize(buf, words, sizeof words / sizeof *words); old = nmatches; nmatches = 0; m = matches; for(l = lines; l < lines + nlines; l++) if(match(*l, words, nwords)){ *m++ = *l; nmatches++; } selected = 0; scroll = 0; free(buf); if (nmatches == old) /* optimization */ drawprompt(); else eresized(0); } static void kbadd(Rune r) { int len = runestrlen(kbinput); if(len == sizeof kbinput / sizeof *kbinput) return; kbinput[len++] = r; kbinput[len] = L'\0'; filter(matches, nmatches); } static void kbbackspace(void) { usize len = runestrlen(kbinput); if(len == 0) return; kbinput[len - 1] = L'\0'; filter(lines, nlines); } static void kbdelword(void) { usize len = runestrlen(kbinput); if(len == 0) return; while(len > 0 && isspacerune(kbinput[len-1])) len--; while(len > 0 && !isspacerune(kbinput[len-1])) len--; kbinput[len] = L'\0'; filter(lines, nlines); } static void kbclear(void) { kbinput[0] = L'\0'; filter(lines, nlines); } static void kbmove(int n) { int old = selected; if(selected + n < 0) selected = 0; else if(selected + n >= nmatches) selected = nmatches - 1; else selected += n; drawline(old); drawline(selected); } static void kbscroll(int percent) { int ln = Dy(screen->r) / font->height * percent / 100; if(ln < 0 && abs(ln) > scroll) scroll = 0; else if(ln > 0 && scroll + ln >= nmatches) scroll = nmatches - 1 + (nmatches > 0); else scroll += ln; eresized(0); } static void mselect(Point pt) { int old, new = pointtoline(pt); if(new < 0) new = 0; if(nmatches > 0 && new >= nmatches) new = nmatches - 1; if(new != selected){ old = selected; selected = new; drawline(old); drawline(new); } } static void usage(void) { print("usage: %s [-b] <choices\n", argv0); exits("usage"); } void main(int argc, char **argv) { Event e; int bflag = 0; ARGBEGIN{ case 'b': bflag = 1; break; default: usage(); }ARGEND; readbuffer(); if(initdraw(nil, nil, "linesel") < 0) sysfatal("initdraw: %r"); if(bflag){ color[TEXT] = display->white; color[BACK] = display->black; }else{ color[TEXT] = display->black; color[BACK] = display->white; } color[HTEXT] = display->black; color[HBACK] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPaleyellow); eresized(0); einit(Emouse|Ekeyboard); for(;;){ switch(event(&e)){ case -1: sysfatal("watching events: %r"); case Ekeyboard: switch(e.kbdc){ case Kdel: exits("interrupted with Del"); case '\n': goto End; case Kbs: kbbackspace(); break; case Ctl('W'): kbdelword(); break; case Ctl('U'): kbclear(); break; case Ctl('P'): case Kup: kbmove(-1); break; case Ctl('N'): case Kdown: kbmove(+1); break; case Kpgdown: kbscroll(+40); break; case Kpgup: kbscroll(-40); break; default: kbadd(e.kbdc); break; } break; case Emouse: if(e.mouse.buttons&1) mselect(e.mouse.xy); if(e.mouse.buttons&4) goto End; if(e.mouse.buttons&8) kbscroll(-40); if(e.mouse.buttons&16) kbscroll(+40); break; } } End: if(nmatches > 0) print("%s\n", matches[selected]); exits(nil); }