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);
}