#include #include #include #include #include #include #include #include #include #include #include #define ESC "\033" #define CSI ESC "[" #define prn(...) dprintf(ttyfd, __VA_ARGS__) #define rerr(...) do { raw(false); err(__VA_ARGS__); } while (0) char **lines; int lines_c; int lines_s; int current = -1; /* currently selected item */ int x, y; /* original cursor position in cols, rows */ int ttyfd; /* enable/disable "raw" mode */ void raw(bool enable) { int r; static struct termios orig; struct termios raw; if (enable) { r = tcgetattr(ttyfd, &orig); if (r == -1) err(1, "tcgetattr"); raw = orig; raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); raw.c_oflag &= ~OPOST; raw.c_cflag |= CS8; raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); r = tcsetattr(ttyfd, TCSAFLUSH, &raw); if (r == -1) err(1, "tcsetattr"); } else tcsetattr(ttyfd, TCSAFLUSH, &orig); } /* cursor position report */ int cpr(int *x, int *y) { char c, xbuf[4], ybuf[4]; int i; prn(CSI "6n"); /* get CSI */ read(ttyfd, &c, 1); if (c != '\033') return -1; read(ttyfd, &c, 1); if (c != '[') return -1; /* get x */ i = 0; while (read(ttyfd, &c, 1) && c != ';') { if (i > 3 || !isdigit(c)) return -1; ybuf[i++] = c; } if (i == 0) return -1; ybuf[i] = '\0'; /* get y */ i = 0; while (read(ttyfd, &c, 1) && c != 'R') { if (i > 3 || !isdigit(c)) return -1; xbuf[i++] = c; } if (i == 0) return -1; xbuf[i] = '\0'; *x = atoi(xbuf); *y = atoi(ybuf); return 0; } /* select item */ int item(int n) { if (n < 1 || n > lines_c) return -1; if (current != -1) prn("%s", lines[current - 1]); current = n; prn("%s%d;%dH", CSI, y + n - 1, 0); prn(CSI "7m"); prn("%s", lines[n - 1]); prn(CSI "0m"); prn("%s%d;%dH", CSI, y + n - 1, 0); return 0; } int up() { return item(current-1); } int down() { return item(current+1); } int right() {} int left() {} int main() { char c, *line, *p, *phrase, *tmp; int i, line_s, r; struct winsize w; phrase = NULL; ttyfd = open("/dev/tty", O_RDWR); if (ttyfd == -1) rerr(1, "open"); /* read from standard input */ line_s = 100; line = malloc((line_s + 1) * sizeof(char)); if (line == NULL) err(1, "malloc"); lines_c = 0; lines_s = 50; lines = malloc(lines_s * sizeof(char *)); if (lines == NULL) err(1, "malloc"); i = 0; while (read(STDIN_FILENO, &c, 1) != 0) { if (c == '\n') { line[i] = '\0'; lines[lines_c] = malloc((strlen(line) + 1) * sizeof(char)); if (lines[lines_c] == NULL) err(1, "malloc"); strcpy(lines[lines_c], line); lines_c++; i = 0; } else { if (i > line_s) { line_s += 50; tmp = realloc(line, (line_s + 1) * sizeof(char)); if (tmp == NULL) err(1, "realloc"); line = tmp; } line[i++] = c; } } raw(true); /* save original cursor position */ r = cpr(&x, &y); if (r == -1) { fprintf(stderr, "could not retrieve cursor position"); goto quit; } /* print output */ for (i = 0; i < lines_c; i++) prn("%s\r\n", lines[i]); /* get height of terminal */ r = ioctl(ttyfd, TIOCGWINSZ, &w); if (r == -1) rerr(1, "ioctl"); /* correct cursor position if original cursor was near bottom */ r = y + lines_c - w.ws_row; if (r > 0) y = y - r; /* restore original cursor position (CUP) */ prn("%s%d;%dH", CSI, y, x); /* select first item */ item(1); while (read(ttyfd, &c, 1) != 0) { switch (c) { case 'q': goto quit; break; case '\033': read(ttyfd, &c, 1); if (c != '[') break; read(ttyfd, &c, 1); if (c == 'A') up(); if (c == 'B') down(); if (c == 'C') right(); if (c == 'D') left(); break; case 13: /* enter */ phrase = lines[current - 1]; goto quit; } } quit: prn("%s%d;%dH", CSI, y, x); prn(CSI "J"); /* delete from cursor to end of display (ED) */ raw(false); if (phrase != NULL) printf("%s\n", phrase); return 0; die: raw(false); puts("died"); return 0; }