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