neo/neo.c
2025-02-13 10:48:51 +00:00

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;
}