tquery.c (12213B)
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 (child_fd) close(child_fd); 160 if (!in || !out || !err) { 161 int zfd = open("/dev/null", O_RDWR); 162 if (zfd < 0) die("open /dev/null"); 163 if (!in) dup2(zfd, 0); 164 if (!out) dup2(zfd, 1); 165 if (!err) dup2(zfd, 2); 166 close(zfd); 167 } 168 if (fd) { 169 if (out) dup2(out_pipe[1], 1); 170 close(out_pipe[0]); 171 close(out_pipe[1]); 172 } 173 execvp(argv[0], (char *const *) argv); 174 die("execv '%s':"); 175 } else { 176 if (fd) { 177 *fd = out_pipe[0]; 178 close(out_pipe[1]); 179 } 180 } 181} 182 183static void * 184addcap(void *alloc, size_t dsize, size_t min, size_t *cap) 185{ 186 if (min > *cap) { 187 *cap = *cap * 2; 188 if (*cap < min) *cap = min; 189 alloc = realloc(alloc, dsize * *cap); 190 if (!alloc) die("realloc:"); 191 } 192 193 return alloc; 194} 195 196static void 197load(void) 198{ 199 ssize_t nread; 200 size_t i; 201 char *start; 202 203 nread = read(child_fd, loadbuf, LOAD_BUFSIZ); 204 if (nread <= 0) { 205 child_pid = 0; 206 close(child_fd); 207 child_fd = -1; 208 return; 209 } 210 211 output = addcap(output, 1, output_len + (size_t) nread, &output_cap); 212 memcpy(output + output_len, loadbuf, (size_t) nread); 213 214 start = output + output_len; 215 216 for (i = output_len; i < output_len + (size_t) nread; i++, start++) { 217 if (*start == delim) { 218 *start = '\0'; 219 delims = addcap(delims, sizeof(size_t), 220 delims_len + 1, &delims_cap); 221 delims[delims_len++] = i; 222 } 223 } 224 225 output_len += (size_t) nread; 226} 227 228static void 229unload(void) 230{ 231 int status; 232 233 if (!child_pid) return; 234 235 kill(child_pid, SIGKILL); 236 do { 237 waitpid(child_pid, &status, 0); 238 } while (!WIFEXITED(status) && !WIFSIGNALED(status)); 239 240 close(child_fd); 241 child_fd = -1; 242 child_pid = 0; 243} 244 245static char * 246entry(size_t i) 247{ 248 return output + (i ? delims[i-1] + 1 : 0); 249} 250 251static void 252update(void) 253{ 254 const ssize_t miny = 3; 255 int width, height; 256 ssize_t i, y; 257 258 erase(); 259 260 width = getmaxx(stdscr); 261 height = getmaxy(stdscr); 262 263 mvprintw(1, 1, "%lu %li %lu %i", output_len, 264 output_nav.sel, delims_len, child_pid); 265 mvprintw(2, 1, "> %.*s", width - 1, inputbuf); 266 267 nav_update_bounds(&output_nav, 0, (int) delims_len); 268 nav_update_wlen(&output_nav, (int) (height - miny)); 269 270 if (output_len) { 271 i = output_nav.wmin; 272 for (y = miny; y < height && i < output_nav.wmax; y++, i++) { 273 if (i == output_nav.sel) 274 attr_on(A_REVERSE, NULL); 275 276 mvprintw((int) y, 1, "%.*s", width - 1, entry((size_t) i)); 277 278 if (i == output_nav.sel) 279 attr_off(A_REVERSE, NULL); 280 } 281 } 282 283 move(2, (int) (1 + 2 + inputlen)); 284 285 refresh(); 286} 287 288static void 289spawn(const char *query) 290{ 291 static const char *argv[64]; 292 const char **arg; 293 const char *tok, *ntok, *end; 294 size_t argc; 295 296 delims_len = 0; 297 output_len = 0; 298 299 argc = 0; 300 for (arg = child_argv; *arg && argc < 64; arg++) { 301 if (!strcmp(*arg, "{}")) { 302 arg += 1; 303 break; 304 } 305 argv[argc++] = *arg; 306 } 307 308 if (split_args) { 309 ntok = tok = query; 310 while (ntok && *ntok && argc < 64) { 311 ntok = strchr(tok, ' '); 312 end = ntok ? ntok : tok + strlen(tok); 313 argv[argc++] = strndup(tok, (size_t) (end - tok)); 314 tok = ntok + 1; 315 } 316 } else if (argc < 64 && query) { 317 argv[argc++] = query; 318 } 319 320 for (; *arg && argc < 64; arg++) { 321 argv[argc++] = *arg; 322 } 323 324 if (argc < 64) { 325 argv[argc++] = NULL; 326 } else { 327 die("Too many arguments"); 328 } 329 330 if (child_pid) unload(); 331 invoke(argv, &child_pid, &child_fd, false, true, errlog); 332} 333 334static void 335run(char *input) 336{ 337 if (run_io) { 338 def_prog_mode(); 339 endwin(); 340 } 341 342 int status, pid; 343 run_argv[1] = input; 344 invoke((const char **)run_argv, &pid, NULL, run_io, run_io, errlog); 345 do { 346 int rc = waitpid(pid, &status, 0); 347 if (rc != pid) die("waitpid:"); 348 } while (!WIFEXITED(status) && !WIFSIGNALED(status)); 349 350 if (oneshot) exit(0); 351 352 if (run_io) { 353 reset_prog_mode(); 354 } 355 356 if (WIFEXITED(status) && WEXITSTATUS(status) == 3) 357 spawn(inputbuf); 358 update(); 359} 360 361static void 362hook(uint8_t key, const char *line) 363{ 364 int status, rc; 365 366 if (!hook_argv[0]) return; 367 368 snprintf(hook_argv[1], 6, "%u", key); 369 free(hook_argv[2]); 370 hook_argv[2] = strdup(line); 371 372 if (hook_io) { 373 def_prog_mode(); 374 endwin(); 375 } 376 377 invoke((const char **)hook_argv, &hook_pid, NULL, hook_io, hook_io, errlog); 378 do { 379 rc = waitpid(hook_pid, &status, 0); 380 if (rc != hook_pid) die("waitpid:"); 381 } while (!WIFEXITED(status) && !WIFSIGNALED(status)); 382 383 if (hook_io) { 384 reset_prog_mode(); 385 } 386 387 if (WIFEXITED(status) && WEXITSTATUS(status) == 3) 388 spawn(inputbuf); 389 update(); 390} 391 392static void 393input(void) 394{ 395 bool dirty; 396 bool reload; 397 int c; 398 399 dirty = false; 400 reload = false; 401 while ((c = getch()) != ERR) { 402 switch (c) { 403 case KEY_BACKSPACE: 404 if (inputlen) inputlen--; 405 dirty = true; 406 reload = true; 407 break; 408 case KEY_PPAGE: 409 nav_update_sel(&output_nav, 410 output_nav.sel - output_nav.wlen / 2); 411 dirty = true; 412 break; 413 case KEY_NPAGE: 414 nav_update_sel(&output_nav, 415 output_nav.sel + output_nav.wlen / 2); 416 dirty = true; 417 break; 418 case KEY_DOWN: 419 nav_update_sel(&output_nav, output_nav.sel + 1); 420 dirty = true; 421 break; 422 case KEY_UP: 423 nav_update_sel(&output_nav, output_nav.sel - 1); 424 dirty = true; 425 break; 426 case '\x1b': 427 /* TODO: word-wise seek */ 428 switch (getch()) { 429 case 'c': 430 break; 431 case 'd': 432 break; 433 } 434 break; 435 case CTRL('w'): 436 inputlen = 0; 437 dirty = true; 438 reload = true; 439 break; 440 case CTRL('l'): 441 clear(); 442 dirty = true; 443 break; 444 case CTRL('b'): 445 nav_update_sel(&output_nav, output_nav.min); 446 dirty = true; 447 break; 448 case CTRL('g'): 449 nav_update_sel(&output_nav, output_nav.max-1); 450 dirty = true; 451 break; 452 case CTRL('x'): 453 if (output_nav.max > 0 && output_nav.sel >= 0) { 454 timeout(-1); 455 hook((uint8_t)getch(), entry((size_t)output_nav.sel)); 456 timeout(0); 457 } 458 break; 459 case '\n': 460 if (output_nav.max > 0 && output_nav.sel >= 0) { 461 if (run_cmd) { 462 run(entry((size_t) output_nav.sel)); 463 } else { 464 puts(entry((size_t) output_nav.sel)); 465 if (oneshot) exit(0); 466 } 467 } 468 break; 469 default: 470 if (c < 32 || c >= 127) 471 continue; 472 if (inputlen < INPUT_BUFSIZ - 1) 473 inputbuf[inputlen++] = (char) c; 474 reload = true; 475 dirty = true; 476 break; 477 } 478 inputbuf[inputlen] = '\0'; 479 } 480 481 if (dirty) { 482 if (reload) spawn(inputbuf); 483 update(); 484 } 485} 486 487static void 488sigint_handler(int sig) 489{ 490 if (child_pid) { 491 unload(); 492 update(); 493 } else { 494 exit(0); 495 } 496} 497 498static void 499usage(int rc, bool full) 500{ 501 fprintf(stderr, "Usage: tquery [OPT..] -- CMD [ARG..]\n"); 502 if (full) { 503 fprintf(stderr, "\n"); 504 fprintf(stderr, " -h, --help Show this message\n"); 505 fprintf(stderr, " -d, --delim Set input entry delim\n"); 506 fprintf(stderr, " -o, --oneshot Exit after oneshot selection\n"); 507 fprintf(stderr, " -e, --stderr Dont close child stderr\n"); 508 fprintf(stderr, " -s, --split Split the query into args\n"); 509 fprintf(stderr, " -r, --run Program to run on Enter\n"); 510 fprintf(stderr, " -R, --run-io Program to run interactively on Enter\n"); 511 fprintf(stderr, " -x, --hook Program to run on Ctrl-X\n"); 512 fprintf(stderr, " -X, --hook-io Program to run interactively on Ctrl-X\n"); 513 fprintf(stderr, "\n"); 514 } 515 exit(rc); 516} 517 518static void 519parse(int argc, char **argv) 520{ 521 char **arg; 522 523 for (arg = &argv[1]; *arg; arg++) { 524 if (!strcmp(*arg, "-h") || !strcmp(*arg, "--help")) { 525 usage(0, true); 526 } else if (!strcmp(*arg, "-o") || !strcmp(*arg, "--oneshot")) { 527 oneshot = true; 528 } else if (!strcmp(*arg, "-e") || !strcmp(*arg, "--stderr")) { 529 errlog = true; 530 } else if (!strcmp(*arg, "-s") || !strcmp(*arg, "--split")) { 531 split_args = true; 532 } else if (!strcmp(*arg, "-d") || !strcmp(*arg, "--delim")) { 533 delim = **++arg; 534 } else if (!strcmp(*arg, "-r") || !strcmp(*arg, "--run")) { 535 run_argv[0] = *++arg; 536 run_cmd = true; 537 } else if (!strcmp(*arg, "-R") || !strcmp(*arg, "--run-io")) { 538 run_argv[0] = *++arg; 539 run_cmd = true; 540 run_io = true; 541 } else if (!strcmp(*arg, "-x") || !strcmp(*arg, "--hook")) { 542 hook_argv[0] = *++arg; 543 } else if (!strcmp(*arg, "-X") || !strcmp(*arg, "--hook-io")) { 544 hook_argv[0] = *++arg; 545 hook_io = true; 546 } else if (!strcmp(*arg, "--")) { 547 child_argv = (const char **)arg + 1; 548 break; 549 } else { 550 die("unknown option '%s'", arg[0]); 551 } 552 } 553 554 if (!child_argv || !*child_argv) 555 usage(0, false); 556} 557 558int 559main(int argc, char **argv) 560{ 561 struct pollfd fds[2]; 562 int rc; 563 564 parse(argc, argv); 565 566 nav_init(&output_nav); 567 568 atexit((void (*)()) endwin); 569 if (!newterm(NULL, stderr, stdin)) 570 die("newterm stdin -> stderr"); 571 cbreak(); 572 noecho(); 573 keypad(stdscr, true); 574 575 sigaction(SIGINT, &sigint_action, NULL); 576 sigaction(SIGWINCH, NULL, &sigwinch_action); 577 sigwinch_action.sa_flags |= SA_RESTART; 578 sigaction(SIGWINCH, &sigwinch_action, NULL); 579 580 timeout(0); 581 582 spawn(""); 583 584 inputbuf = malloc(INPUT_BUFSIZ); 585 if (!inputbuf) die("malloc inputbuf:"); 586 587 loadbuf = malloc(LOAD_BUFSIZ); 588 if (!loadbuf) die("malloc loadbuf:"); 589 590 inputlen = 0; 591 inputbuf[inputlen] = '\0'; 592 593 update(); 594 595 while (1) { 596 fds[0].fd = 0; 597 fds[0].events = POLLIN; 598 fds[1].fd = child_fd; 599 fds[1].events = POLLIN; 600 601 rc = poll(fds, child_pid ? 2 : 1, -1); 602 if (rc < 0 && errno == EINTR) { 603 update(); 604 continue; 605 } else if (rc < 0) { 606 die("select:"); 607 } 608 609 if (fds[0].revents & POLLIN) { 610 input(); 611 } else if (fds[1].revents & POLLIN) { 612 load(); 613 update(); 614 } else if (fds[1].revents & POLLHUP) { 615 unload(); 616 update(); 617 } 618 } 619}