tquery.c (12180B)
1#include <curses.h> 2 3#include <fcntl.h> 4#include <sys/wait.h> 5#include <sys/poll.h> 6#include <sys/fcntl.h> 7#include <signal.h> 8#include <dirent.h> 9#include <unistd.h> 10#include <errno.h> 11#include <string.h> 12#include <stdarg.h> 13#include <stdio.h> 14#include <stdlib.h> 15 16#define LOAD_BUFSIZ 16384 17#define INPUT_BUFSIZ 2048 18 19#define CTRL(x) ((x) & 0x1f) 20 21#define MIN(a, b) ((a) > (b) ? (b) : (a)) 22#define MAX(a, b) ((a) > (b) ? (a) : (b)) 23 24struct nav { 25 ssize_t wmin, wmax, wlen; 26 ssize_t sel, min, max; 27}; 28 29static void sigint_handler(int sig); 30 31static const struct sigaction sigint_action = { 32 .sa_flags = SA_RESTART, 33 .sa_handler = sigint_handler 34}; 35 36static struct sigaction sigwinch_action = { 37 .sa_flags = SA_RESTART, 38 .sa_handler = SIG_IGN 39}; 40 41static int child_fd = -1; 42static pid_t child_pid = 0; 43static const char **child_argv = NULL; 44 45static pid_t hook_pid = -1; 46static char hook_key_arg[6] = { 0 }; 47static char *hook_argv[4] = { NULL, hook_key_arg, NULL, NULL }; 48static bool hook_io = false; 49 50static char *run_argv[4] = { NULL, NULL, NULL }; 51static bool run_cmd = false; 52static bool run_io = false; 53 54static char *loadbuf = NULL; 55 56static char *output = NULL; 57static size_t output_len = 0; 58static size_t output_cap = 0; 59 60static size_t *delims = NULL; 61static size_t delims_len = 0; 62static size_t delims_cap = 0; 63 64static struct nav output_nav; 65 66static char *inputbuf = NULL; 67static size_t inputlen = 0; 68 69static bool split_args = false; 70static bool oneshot = false; 71static bool errlog = false; 72static char delim = '\n'; 73 74static void 75die(const char *fmt, ...) 76{ 77 va_list ap; 78 79 fputs("tquery: ", stderr); 80 81 va_start(ap, fmt); 82 vfprintf(stderr, fmt, ap); 83 va_end(ap); 84 85 if (fmt[0] && fmt[strlen(fmt)-1] == ':') { 86 fputc(' ', stderr); 87 perror(NULL); 88 } else { 89 fputc('\n', stderr); 90 } 91 92 exit(1); 93} 94 95static void 96nav_update_win(struct nav *nav) 97{ 98 nav->wmin = MAX(nav->wmax - nav->wlen, nav->min); 99 nav->wmax = MIN(nav->wmin + nav->wlen, nav->max); 100 nav->sel = MAX(MIN(nav->sel, nav->wmax - 1), nav->wmin); 101} 102 103static void 104nav_init(struct nav *nav) 105{ 106 nav->sel = 0; 107 nav->min = 0; 108 nav->max = 0; 109 nav->wlen = 0; 110 nav->wmin = 0; 111 nav->wmax = 0; 112} 113 114static void 115nav_update_bounds(struct nav *nav, int min, int max) 116{ 117 nav->min = min; 118 nav->max = max; 119 nav_update_win(nav); 120} 121 122static void 123nav_update_wlen(struct nav *nav, ssize_t wlen) 124{ 125 nav->wlen = wlen; 126 nav_update_win(nav); 127} 128 129static void 130nav_update_sel(struct nav *nav, ssize_t sel) 131{ 132 nav->sel = MAX(MIN(sel, nav->max - 1), nav->min); 133 if (nav->sel >= nav->wmax) { 134 nav->wmax = nav->sel + 1; 135 nav->wmin = MAX(nav->min, nav->wmax - nav->wlen); 136 } else if (nav->sel < nav->wmin) { 137 nav->wmin = nav->sel; 138 nav->wmax = MIN(nav->wmin + nav->wlen, nav->max); 139 } 140} 141 142static void 143invoke(const char **argv, pid_t *pid, int *fd, bool in, bool out, bool err) 144{ 145 int out_pipe[2]; 146 147 if (fd) { 148 if (*fd >= 0) { 149 close(*fd); 150 *fd = -1; 151 } 152 if (pipe(out_pipe)) 153 die("pipe:"); 154 } 155 156 *pid = fork(); 157 if (*pid < 0) die("fork:"); 158 if (!*pid) { 159 if (!in || !out || !err) { 160 int zfd = open("/dev/null", O_RDWR); 161 if (zfd < 0) die("open /dev/null"); 162 if (!in) dup2(zfd, 0); 163 if (!out) dup2(zfd, 1); 164 if (!err) dup2(zfd, 2); 165 close(zfd); 166 } 167 if (fd) { 168 if (out) dup2(out_pipe[1], 1); 169 close(out_pipe[0]); 170 close(out_pipe[1]); 171 } 172 execvp(argv[0], (char *const *) argv); 173 die("execv '%s':"); 174 } else { 175 if (fd) { 176 *fd = out_pipe[0]; 177 close(out_pipe[1]); 178 } 179 } 180} 181 182static void * 183addcap(void *alloc, size_t dsize, size_t min, size_t *cap) 184{ 185 if (min > *cap) { 186 *cap = *cap * 2; 187 if (*cap < min) *cap = min; 188 alloc = realloc(alloc, dsize * *cap); 189 if (!alloc) die("realloc:"); 190 } 191 192 return alloc; 193} 194 195static void 196load(void) 197{ 198 ssize_t nread; 199 size_t i; 200 char *start; 201 202 nread = read(child_fd, loadbuf, LOAD_BUFSIZ); 203 if (nread <= 0) { 204 child_pid = 0; 205 close(child_fd); 206 child_fd = -1; 207 return; 208 } 209 210 output = addcap(output, 1, output_len + (size_t) nread, &output_cap); 211 memcpy(output + output_len, loadbuf, (size_t) nread); 212 213 start = output + output_len; 214 215 for (i = output_len; i < output_len + (size_t) nread; i++, start++) { 216 if (*start == delim) { 217 *start = '\0'; 218 delims = addcap(delims, sizeof(size_t), 219 delims_len + 1, &delims_cap); 220 delims[delims_len++] = i; 221 } 222 } 223 224 output_len += (size_t) nread; 225} 226 227static void 228unload(void) 229{ 230 int status; 231 232 if (!child_pid) return; 233 234 kill(child_pid, SIGKILL); 235 do { 236 waitpid(child_pid, &status, 0); 237 } while (!WIFEXITED(status) && !WIFSIGNALED(status)); 238 239 close(child_fd); 240 child_fd = -1; 241 child_pid = 0; 242} 243 244static char * 245entry(size_t i) 246{ 247 return output + (i ? delims[i-1] + 1 : 0); 248} 249 250static void 251update(void) 252{ 253 const ssize_t miny = 3; 254 int width, height; 255 ssize_t i, y; 256 257 erase(); 258 259 width = getmaxx(stdscr); 260 height = getmaxy(stdscr); 261 262 mvprintw(1, 1, "%lu %li %lu %i", output_len, 263 output_nav.sel, delims_len, child_pid); 264 mvprintw(2, 1, "> %.*s", width - 1, inputbuf); 265 266 nav_update_bounds(&output_nav, 0, (int) delims_len); 267 nav_update_wlen(&output_nav, (int) (height - miny)); 268 269 if (output_len) { 270 i = output_nav.wmin; 271 for (y = miny; y < height && i < output_nav.wmax; y++, i++) { 272 if (i == output_nav.sel) 273 attr_on(A_REVERSE, NULL); 274 275 mvprintw((int) y, 1, "%.*s", width - 1, entry((size_t) i)); 276 277 if (i == output_nav.sel) 278 attr_off(A_REVERSE, NULL); 279 } 280 } 281 282 move(2, (int) (1 + 2 + inputlen)); 283 284 refresh(); 285} 286 287static void 288spawn(const char *query) 289{ 290 static const char *argv[64]; 291 const char **arg; 292 const char *tok, *ntok, *end; 293 size_t argc; 294 295 delims_len = 0; 296 output_len = 0; 297 298 argc = 0; 299 for (arg = child_argv; *arg && argc < 64; arg++) { 300 if (!strcmp(*arg, "{}")) { 301 arg += 1; 302 break; 303 } 304 argv[argc++] = *arg; 305 } 306 307 if (split_args) { 308 ntok = tok = query; 309 while (ntok && *ntok && argc < 64) { 310 ntok = strchr(tok, ' '); 311 end = ntok ? ntok : tok + strlen(tok); 312 argv[argc++] = strndup(tok, (size_t) (end - tok)); 313 tok = ntok + 1; 314 } 315 } else if (argc < 64 && query) { 316 argv[argc++] = query; 317 } 318 319 for (; *arg && argc < 64; arg++) { 320 argv[argc++] = *arg; 321 } 322 323 if (argc < 64) { 324 argv[argc++] = NULL; 325 } else { 326 die("Too many arguments"); 327 } 328 329 if (child_pid) unload(); 330 invoke(argv, &child_pid, &child_fd, false, true, errlog); 331} 332 333static void 334run(char *input) 335{ 336 if (run_io) { 337 def_prog_mode(); 338 endwin(); 339 } 340 341 int status, pid; 342 run_argv[1] = input; 343 invoke((const char **)run_argv, &pid, NULL, run_io, run_io, errlog); 344 do { 345 int rc = waitpid(pid, &status, 0); 346 if (rc != pid) die("waitpid:"); 347 } while (!WIFEXITED(status) && !WIFSIGNALED(status)); 348 349 if (oneshot) exit(0); 350 351 if (run_io) { 352 reset_prog_mode(); 353 } 354 355 if (WIFEXITED(status) && WEXITSTATUS(status) == 3) 356 spawn(inputbuf); 357 update(); 358} 359 360static void 361hook(uint8_t key, const char *line) 362{ 363 int status, rc; 364 365 if (!hook_argv[0]) return; 366 367 snprintf(hook_argv[1], 6, "%u", key); 368 free(hook_argv[2]); 369 hook_argv[2] = strdup(line); 370 371 if (hook_io) { 372 def_prog_mode(); 373 endwin(); 374 } 375 376 invoke((const char **)hook_argv, &hook_pid, NULL, hook_io, hook_io, errlog); 377 do { 378 rc = waitpid(hook_pid, &status, 0); 379 if (rc != hook_pid) die("waitpid:"); 380 } while (!WIFEXITED(status) && !WIFSIGNALED(status)); 381 382 if (hook_io) { 383 reset_prog_mode(); 384 } 385 386 if (WIFEXITED(status) && WEXITSTATUS(status) == 3) 387 spawn(inputbuf); 388 update(); 389} 390 391static void 392input(void) 393{ 394 bool dirty; 395 bool reload; 396 int c; 397 398 dirty = false; 399 reload = false; 400 while ((c = getch()) != ERR) { 401 switch (c) { 402 case KEY_BACKSPACE: 403 if (inputlen) inputlen--; 404 dirty = true; 405 reload = true; 406 break; 407 case KEY_PPAGE: 408 nav_update_sel(&output_nav, 409 output_nav.sel - output_nav.wlen / 2); 410 dirty = true; 411 break; 412 case KEY_NPAGE: 413 nav_update_sel(&output_nav, 414 output_nav.sel + output_nav.wlen / 2); 415 dirty = true; 416 break; 417 case KEY_DOWN: 418 nav_update_sel(&output_nav, output_nav.sel + 1); 419 dirty = true; 420 break; 421 case KEY_UP: 422 nav_update_sel(&output_nav, output_nav.sel - 1); 423 dirty = true; 424 break; 425 case '\x1b': 426 /* TODO: word-wise seek */ 427 switch (getch()) { 428 case 'c': 429 break; 430 case 'd': 431 break; 432 } 433 break; 434 case CTRL('w'): 435 inputlen = 0; 436 dirty = true; 437 reload = true; 438 break; 439 case CTRL('l'): 440 clear(); 441 dirty = true; 442 break; 443 case CTRL('b'): 444 nav_update_sel(&output_nav, output_nav.min); 445 dirty = true; 446 break; 447 case CTRL('g'): 448 nav_update_sel(&output_nav, output_nav.max-1); 449 dirty = true; 450 break; 451 case CTRL('x'): 452 if (output_nav.max > 0 && output_nav.sel >= 0) { 453 timeout(-1); 454 hook((uint8_t)getch(), entry((size_t)output_nav.sel)); 455 timeout(0); 456 } 457 break; 458 case '\n': 459 if (output_nav.max > 0 && output_nav.sel >= 0) { 460 if (run_cmd) { 461 run(entry((size_t) output_nav.sel)); 462 } else { 463 puts(entry((size_t) output_nav.sel)); 464 if (oneshot) exit(0); 465 } 466 } 467 break; 468 default: 469 if (c < 32 || c >= 127) 470 continue; 471 if (inputlen < INPUT_BUFSIZ - 1) 472 inputbuf[inputlen++] = (char) c; 473 reload = true; 474 dirty = true; 475 break; 476 } 477 inputbuf[inputlen] = '\0'; 478 } 479 480 if (dirty) { 481 if (reload) spawn(inputbuf); 482 update(); 483 } 484} 485 486static void 487sigint_handler(int sig) 488{ 489 if (child_pid) { 490 unload(); 491 update(); 492 } else { 493 exit(0); 494 } 495} 496 497static void 498usage(int rc, bool full) 499{ 500 fprintf(stderr, "Usage: tquery [OPT..] -- CMD [ARG..]\n"); 501 if (full) { 502 fprintf(stderr, "\n"); 503 fprintf(stderr, " -h, --help Show this message\n"); 504 fprintf(stderr, " -d, --delim Set input entry delim\n"); 505 fprintf(stderr, " -o, --oneshot Exit after oneshot selection\n"); 506 fprintf(stderr, " -e, --stderr Dont close child stderr\n"); 507 fprintf(stderr, " -s, --split Split the query into args\n"); 508 fprintf(stderr, " -r, --run Program to run on Enter\n"); 509 fprintf(stderr, " -R, --run-io Program to run interactively on Enter\n"); 510 fprintf(stderr, " -x, --hook Program to run on Ctrl-X\n"); 511 fprintf(stderr, " -X, --hook-io Program to run interactively on Ctrl-X\n"); 512 fprintf(stderr, "\n"); 513 } 514 exit(rc); 515} 516 517static void 518parse(int argc, char **argv) 519{ 520 char **arg; 521 522 for (arg = &argv[1]; *arg; arg++) { 523 if (!strcmp(*arg, "-h") || !strcmp(*arg, "--help")) { 524 usage(0, true); 525 } else if (!strcmp(*arg, "-o") || !strcmp(*arg, "--oneshot")) { 526 oneshot = true; 527 } else if (!strcmp(*arg, "-e") || !strcmp(*arg, "--stderr")) { 528 errlog = true; 529 } else if (!strcmp(*arg, "-s") || !strcmp(*arg, "--split")) { 530 split_args = true; 531 } else if (!strcmp(*arg, "-d") || !strcmp(*arg, "--delim")) { 532 delim = **++arg; 533 } else if (!strcmp(*arg, "-r") || !strcmp(*arg, "--run")) { 534 run_argv[0] = *++arg; 535 run_cmd = true; 536 } else if (!strcmp(*arg, "-R") || !strcmp(*arg, "--run-io")) { 537 run_argv[0] = *++arg; 538 run_cmd = true; 539 run_io = true; 540 } else if (!strcmp(*arg, "-x") || !strcmp(*arg, "--hook")) { 541 hook_argv[0] = *++arg; 542 } else if (!strcmp(*arg, "-X") || !strcmp(*arg, "--hook-io")) { 543 hook_argv[0] = *++arg; 544 hook_io = true; 545 } else if (!strcmp(*arg, "--")) { 546 child_argv = (const char **)arg + 1; 547 break; 548 } else { 549 die("unknown option '%s'", arg[0]); 550 } 551 } 552 553 if (!child_argv || !*child_argv) 554 usage(0, false); 555} 556 557int 558main(int argc, char **argv) 559{ 560 struct pollfd fds[2]; 561 int rc; 562 563 parse(argc, argv); 564 565 nav_init(&output_nav); 566 567 atexit((void (*)()) endwin); 568 if (!newterm(NULL, stderr, stdin)) 569 die("newterm stdin -> stderr"); 570 cbreak(); 571 noecho(); 572 keypad(stdscr, true); 573 574 sigaction(SIGINT, &sigint_action, NULL); 575 sigaction(SIGWINCH, NULL, &sigwinch_action); 576 sigwinch_action.sa_flags |= SA_RESTART; 577 sigaction(SIGWINCH, &sigwinch_action, NULL); 578 579 timeout(0); 580 581 spawn(""); 582 583 inputbuf = malloc(INPUT_BUFSIZ); 584 if (!inputbuf) die("malloc inputbuf:"); 585 586 loadbuf = malloc(LOAD_BUFSIZ); 587 if (!loadbuf) die("malloc loadbuf:"); 588 589 inputlen = 0; 590 inputbuf[inputlen] = '\0'; 591 592 update(); 593 594 while (1) { 595 fds[0].fd = 0; 596 fds[0].events = POLLIN; 597 fds[1].fd = child_fd; 598 fds[1].events = POLLIN; 599 600 rc = poll(fds, child_pid ? 2 : 1, -1); 601 if (rc < 0 && errno == EINTR) { 602 update(); 603 continue; 604 } else if (rc < 0) { 605 die("select:"); 606 } 607 608 if (fds[0].revents & POLLIN) { 609 input(); 610 } else if (fds[1].revents & POLLIN) { 611 load(); 612 update(); 613 } else if (fds[1].revents & POLLHUP) { 614 unload(); 615 update(); 616 } 617 } 618}