/* xmenu * a simple fuzzy-selection menu made with Xlib */ #include #include #include #define ARENA_IMPL #define STR_IMPL #include "arena.h" #include "dynarr.h" #include "str.h" #include "ui.h" #include "fuzzy.h" typedef DYNARR(char) DynStr; int umod(int a, int b) { if (a < 0) { return b + a % b; } else { return a % b; } } int txt_insert(DynStr *s, int i, Str ins) { DA_FIT(s, s->n + ins.n); if (i < s->n) { memmove(s->v + i + ins.n, s->v + i, s->n - i); } memcpy(s->v + i, ins.s, ins.n); s->n += ins.n; return i + ins.n; } int txt_delete_byte(DynStr *s, int i, int n) { if (n > i) n = i; if (n > 0) { memmove(s->v + i - n, s->v + i, s->n - i); s->n -= n; } return i - n; } int txt_delete(DynStr *s, int i, int n) { while (n--) { i = txt_delete_byte(s, i, 1); while (i > 0 && (s->v[i-1] & 0x80)) i = txt_delete_byte(s, i, 1); } return i; } int delete_word(DynStr *s, int i) { while (i > 0 && is_space(s->v[i-1])) i = txt_delete(s, i, 1); while (i > 0 && !is_space(s->v[i-1])) i = txt_delete(s, i, 1); return i; } int prev_word(DynStr *s, int i) { while (i > 0 && is_space(s->v[i-1])) i--; while (i > 0 && !is_space(s->v[i-1])) i--; return i; } int next_word(DynStr *s, int i) { while (i < s->n && is_space(s->v[i])) i++; while (i < s->n && !is_space(s->v[i])) i++; return i; } int read_all(FILE *f, DynStr *out, Arena *a) { char buf[256]; while (!feof(f)) { size_t n = fread(buf, 1, sizeof buf, f); if (ferror(f)) return -1; DA_APUSH_MULT(out, a, buf, n); } return 0; } int main(int argc, char **argv) { Arena perm = { 0 }; Arena scratch = { 0 }; DynStr input = { 0 }; int inpi = 0; int seli = 0; UiOpts opt_src = { 0 }; DynStr buf = { 0 }; if (read_all(stdin, &buf, &perm)) err(1, "stdin"); for (Str src = { buf.v, buf.n }; src.n > 0; ) { Cut c = str_cut(src, '\n'); DA_APUSH(&opt_src, &perm, c.head); src = c.tail; } if (!opt_src.n) return 0; UiOpts opt = { .v = new_arr(&perm, Str, opt_src.n), .n = opt_src.n, }; ui_init(argc, argv, opt_src); DA_FIT(&input, 32); for (UiEvent ev; ui_wait_event(&ev); ) { switch (ev.type) { case UI_KEY_DOWN: switch (ev.key.key) { case UIK_ESCAPE: goto done; case UIK_BACKSPACE: if (ev.key.mod & UIM_CTRL) { inpi = delete_word(&input, inpi); } else { inpi = txt_delete(&input, inpi, 1); } goto chg; case UIK_RETURN: { Str o = opt.v[seli]; printf("%.*s\n", (int)o.n, o.s); } goto done; case UIK_DOWN: seli = umod(seli + 1, opt.n); goto draw; case UIK_UP: seli = umod(seli - 1, opt.n); goto draw; case UIK_PGDN: seli += 8; if (seli >= opt.n) seli = opt.n - 1; goto draw; case UIK_PGUP: seli -= 8; if (seli < 0) seli = 0; goto draw; case UIK_LEFT: if (ev.key.mod & UIM_CTRL) inpi = prev_word(&input, inpi); else if (inpi > 0) inpi--; goto draw; case UIK_RIGHT: if (ev.key.mod & UIM_CTRL) inpi = next_word(&input, inpi); else if (inpi < input.n) inpi++; goto draw; case UIK_HOME: if (ev.key.mod & UIM_CTRL) seli = 0; else inpi = 0; goto draw; case UIK_END: if (ev.key.mod & UIM_CTRL) seli = opt.n - 1; else inpi = input.n; goto draw; default: if (!ev.key.strn) break; /* ^W */ if (ev.key.strn == 1 && ev.key.str[0] == 0x17) { inpi = delete_word(&input, inpi); goto chg; } inpi = txt_insert(&input, inpi, (Str) { ev.key.str, ev.key.strn }); chg: seli = 0; goto draw; } break; case UI_REDRAW: draw: { Str s = { input.v, input.n }; opt.n = fz_sort(opt.v, opt_src.v, opt_src.n, s, &scratch); ui_draw(s, inpi, seli, opt); } break; case UI_QUIT: goto done; default: break; } } done: ui_fini(); arena_free(&perm); arena_free(&scratch); return 0; }