234 lines
4.4 KiB
C
234 lines
4.4 KiB
C
#include <ctype.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
|
|
#define MAX_LINES 10000
|
|
#define MAX_LINE 4096
|
|
#define RESET "\033[0m"
|
|
#define RED "\033[31m"
|
|
#define GREEN "\033[32m"
|
|
#define YELLOW "\033[33m"
|
|
#define BLUE "\033[34m"
|
|
|
|
char **lines;
|
|
int count;
|
|
int selected;
|
|
char query[MAX_LINE];
|
|
int query_len;
|
|
struct termios orig_termios;
|
|
int rows = 24, cols = 80;
|
|
int tty_fd;
|
|
|
|
void get_window_size(void)
|
|
{
|
|
struct winsize ws;
|
|
if (ioctl(tty_fd, TIOCGWINSZ, &ws) != -1) {
|
|
rows = ws.ws_row;
|
|
cols = ws.ws_col;
|
|
}
|
|
}
|
|
|
|
void handle_sigwinch(int sig)
|
|
{
|
|
get_window_size();
|
|
}
|
|
|
|
int fuzzy_match(const char *pattern, const char *str)
|
|
{
|
|
if (!*pattern) return 1;
|
|
|
|
while (*str) {
|
|
if (tolower(*pattern) == tolower(*str)) {
|
|
const char *p = pattern + 1;
|
|
const char *s = str + 1;
|
|
while (*p && *s) {
|
|
if (tolower(*p) == tolower(*s)) p++;
|
|
s++;
|
|
}
|
|
if (!*p) return 1;
|
|
}
|
|
str++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void highlight_matches(const char *str, const char *pattern, int selected)
|
|
{
|
|
if (!*pattern) {
|
|
printf("%s" RESET, str);
|
|
return;
|
|
}
|
|
|
|
const char *s = str;
|
|
const char *p = pattern;
|
|
|
|
while (*s) {
|
|
if (p && tolower(*p) == tolower(*s)) {
|
|
printf(YELLOW "%c" RESET, *s);
|
|
p++;
|
|
} else {
|
|
printf("%s%c", selected ? BLUE: RESET, *s);
|
|
}
|
|
s++;
|
|
}
|
|
}
|
|
|
|
int count_matches(void)
|
|
{
|
|
int matches = 0;
|
|
for (int i = 0; i < count; i++) {
|
|
if (fuzzy_match(query, lines[i])) matches++;
|
|
}
|
|
return matches;
|
|
}
|
|
|
|
void draw(void)
|
|
{
|
|
printf("\033[H\033[J" GREEN "> " RESET "%s\n", query);
|
|
|
|
/* 1 for prompt, 1 off by one */
|
|
int visible_items = rows - 2;
|
|
|
|
int start = 0;
|
|
int end = count;
|
|
|
|
if (selected >= visible_items) {
|
|
start = selected - visible_items + 1;
|
|
}
|
|
|
|
if (end > start + visible_items) {
|
|
end = start + visible_items;
|
|
}
|
|
|
|
for (int i = start; i < end; i++) {
|
|
if (fuzzy_match(query, lines[i])) {
|
|
if (i == selected) {
|
|
printf(RED "> " RESET BLUE);
|
|
}
|
|
if (i != selected) {
|
|
printf(" ");
|
|
}
|
|
highlight_matches(lines[i], query, i == selected);
|
|
printf("\n");
|
|
}
|
|
}
|
|
/* Move cursor to query */
|
|
printf("\033[%d;%dH", 1, query_len + 3);
|
|
fflush(stdout);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
/* stdin already occupied by pipe, so we need /dev/tty */
|
|
tty_fd = open("/dev/tty", O_RDWR);
|
|
if (tty_fd == -1) {
|
|
perror("open");
|
|
return 1;
|
|
}
|
|
|
|
if (isatty(STDIN_FILENO)) {
|
|
fprintf(stderr, "neo: No input from pipe\n");
|
|
close(tty_fd);
|
|
return 1;
|
|
}
|
|
|
|
lines = malloc(sizeof(char *) * MAX_LINES);
|
|
if (!lines) {
|
|
perror("malloc");
|
|
return 1;
|
|
}
|
|
|
|
/* Read from stdin (pipe) */
|
|
char buf[MAX_LINE];
|
|
while (count < MAX_LINES && fgets(buf, sizeof(buf), stdin)) {
|
|
size_t len = strlen(buf);
|
|
if (len && buf[len - 1] == '\n') buf[len - 1] = '\0';
|
|
lines[count++] = strdup(buf);
|
|
}
|
|
printf("count: %d\n", count);
|
|
|
|
if (!count) {
|
|
fprintf(stderr, "neo: No input received\n");
|
|
free(lines);
|
|
close(tty_fd);
|
|
return 1;
|
|
}
|
|
|
|
signal(SIGWINCH, handle_sigwinch);
|
|
get_window_size();
|
|
|
|
/* Raw mode */
|
|
tcgetattr(tty_fd, &orig_termios);
|
|
struct termios raw = orig_termios;
|
|
raw.c_iflag &= ~(ICRNL | IXON);
|
|
raw.c_lflag &= ~(ECHO | ICANON);
|
|
raw.c_cc[VMIN] = 1;
|
|
raw.c_cc[VTIME] = 0;
|
|
tcsetattr(tty_fd, TCSAFLUSH, &raw);
|
|
|
|
draw();
|
|
printf("\033[?25h");
|
|
|
|
while (1) {
|
|
char c;
|
|
if (read(tty_fd, &c, 1) == 1) {
|
|
if (c == 'q') break;
|
|
else if (c == 27) {
|
|
char seq[3];
|
|
if (read(tty_fd, &seq[0], 1) != 1) continue;
|
|
if (read(tty_fd, &seq[1], 1) != 1) continue;
|
|
|
|
if (seq[0] == '[') {
|
|
switch (seq[1]) {
|
|
/* Up */
|
|
case 'A':
|
|
if (selected > 0) selected--;
|
|
break;
|
|
/* Down */
|
|
case 'B':
|
|
if (selected < count_matches() - 1)
|
|
selected++;
|
|
break;
|
|
}
|
|
}
|
|
} else if (c == 127 || c == '\b') {
|
|
if (query_len > 0) {
|
|
query_len--;
|
|
query[query_len] = '\0';
|
|
selected = 0;
|
|
}
|
|
} else if (c == '\r' || c == '\n') {
|
|
int matches = 0;
|
|
for (int i = 0; i < count; i++) {
|
|
if (fuzzy_match(query, lines[i])) {
|
|
if (matches == selected) {
|
|
printf("\033[H\033[J%s\n", lines[i]);
|
|
goto cleanup;
|
|
}
|
|
matches++;
|
|
}
|
|
}
|
|
} else if (query_len < sizeof(query) - 1 && isprint(c)) {
|
|
query[query_len++] = c;
|
|
query[query_len] = '\0';
|
|
selected = 0;
|
|
}
|
|
|
|
draw();
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
tcsetattr(tty_fd, TCSAFLUSH, &orig_termios);
|
|
close(tty_fd);
|
|
for (int i = 0; i < count; i++)
|
|
free(lines[i]);
|
|
free(lines);
|
|
return 0;
|
|
}
|