mirror of https://github.com/ericonr/ef.git
Compare commits
10 Commits
4e174d9126
...
a265492706
Author | SHA1 | Date |
---|---|---|
Érico Nogueira | a265492706 | |
Érico Nogueira | 69efce1d95 | |
Érico Nogueira | 55ae15eb6f | |
Érico Nogueira | a0951f2aa5 | |
Érico Nogueira | 8bf7ceef41 | |
Érico Nogueira | ec6014a0e5 | |
Érico Nogueira | 8f38a81690 | |
Érico Nogueira | cbb98853d1 | |
Érico Nogueira | b734660c96 | |
Érico Nogueira | a0ce797775 |
162
ef.c
162
ef.c
|
@ -3,9 +3,13 @@
|
|||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <malloc.h>
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
#include <locale.h>
|
||||
|
||||
#include <curses.h>
|
||||
|
@ -13,9 +17,12 @@
|
|||
#include "string-array.h"
|
||||
#include "util.h"
|
||||
|
||||
#define MAX_TOK_LEN 128
|
||||
|
||||
static void endwin_void(void)
|
||||
{
|
||||
endwin();
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static const char *final_name;
|
||||
|
@ -25,49 +32,94 @@ static void print_name(void)
|
|||
if (stdout_save && final_name) fputs(final_name, stdout_save);
|
||||
}
|
||||
|
||||
static void finish(int sig)
|
||||
static int signal_pipe[2];
|
||||
static void signal_handler(int signum)
|
||||
{
|
||||
(void)sig;
|
||||
quick_exit(1);
|
||||
write(signal_pipe[1], &signum, sizeof signum);
|
||||
}
|
||||
|
||||
int main()
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
setlocale(LC_ALL, "");
|
||||
|
||||
const char delim = '\n';
|
||||
char delim = '\n';
|
||||
char *query = NULL;
|
||||
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "01q:")) != -1) {
|
||||
switch (opt) {
|
||||
case '0':
|
||||
/* set delimiter to NUL char instead of newline */
|
||||
delim = '\0';
|
||||
break;
|
||||
case '1':
|
||||
/* ignored for compatibility with fzf */
|
||||
break;
|
||||
case 'q':
|
||||
/* initial query */
|
||||
query = optarg;
|
||||
break;
|
||||
default:
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (pipe(signal_pipe)) {
|
||||
perror("pipe");
|
||||
exit(1);
|
||||
}
|
||||
if (signal(SIGINT, signal_handler) || signal(SIGTERM, signal_handler)) {
|
||||
perror("signal");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
struct str_array entries = { 0 };
|
||||
read_entries_from_stream(&entries, delim, stdin);
|
||||
/* create our own stream for standard input,
|
||||
* so stdin is fresh when we use it with our new file descriptor below */
|
||||
int stdin_dup;
|
||||
FILE *original_stdin;
|
||||
if ((stdin_dup = dup(STDIN_FILENO)) < 0 ||
|
||||
(original_stdin = fdopen(stdin_dup, "r")) == NULL) {
|
||||
perror("stdin handling");
|
||||
exit(1);
|
||||
}
|
||||
read_entries_from_stream(&entries, delim, original_stdin);
|
||||
fclose(original_stdin);
|
||||
|
||||
/* fast exit cases */
|
||||
if (entries.n == 0) exit(0);
|
||||
if (entries.n == 1) {
|
||||
puts(get_entry(&entries, 0));
|
||||
exit(0);
|
||||
}
|
||||
/* end of fast exit cases */
|
||||
sort_entries(&entries);
|
||||
filter_entries(&entries, NULL);
|
||||
|
||||
int tmp_fd;
|
||||
/* use stderr for input instead of stdin, since we get the entries from stdin */
|
||||
if (dup2(STDERR_FILENO, STDIN_FILENO) != STDIN_FILENO ||
|
||||
/* use stderr for output as well, since we should only print the result to stdout */
|
||||
(tmp_fd = dup(STDOUT_FILENO)) < 0 ||
|
||||
(stdout_save = fdopen(tmp_fd, "w")) == NULL ||
|
||||
dup2(STDERR_FILENO, STDOUT_FILENO) != STDOUT_FILENO) {
|
||||
/* use the controlling terminal as new stdin/stdout,
|
||||
* since the process ones are used for input and output */
|
||||
int tty_fd, stdout_save_fd;
|
||||
if ((tty_fd = open("/dev/tty", O_RDWR | O_CLOEXEC)) < 0 ||
|
||||
(stdout_save_fd = dup(STDOUT_FILENO)) < 0 ||
|
||||
(stdout_save = fdopen(stdout_save_fd, "we")) == NULL ||
|
||||
dup2(tty_fd, STDIN_FILENO) != STDIN_FILENO ||
|
||||
dup2(tty_fd, STDOUT_FILENO) != STDOUT_FILENO) {
|
||||
perror("fd dance");
|
||||
exit(1);
|
||||
}
|
||||
close(tty_fd);
|
||||
|
||||
enum { SIGNAL, STDIN, POLLFD_AMOUNT };
|
||||
struct pollfd polls[POLLFD_AMOUNT] = {
|
||||
[SIGNAL] = {signal_pipe[0], POLLIN, 0},
|
||||
[STDIN] = {STDIN_FILENO, POLLIN, 0},
|
||||
};
|
||||
|
||||
atexit(print_name);
|
||||
|
||||
signal(SIGINT, finish);
|
||||
atexit(endwin_void);
|
||||
at_quick_exit(endwin_void);
|
||||
|
||||
/* curses initialization */
|
||||
initscr();
|
||||
at_quick_exit(endwin_void);
|
||||
atexit(endwin_void);
|
||||
/* terminal configuration */
|
||||
nonl();
|
||||
cbreak();
|
||||
|
@ -114,16 +166,14 @@ int main()
|
|||
WINDOW *prompt = newwin(1, 0, LINES - 1, 0);
|
||||
if (!list || !prompt) {
|
||||
perror("newwin");
|
||||
exit(1);
|
||||
quick_exit(1);
|
||||
}
|
||||
keypad(prompt, TRUE);
|
||||
|
||||
/* initial prompt */
|
||||
mvwaddstr(prompt, 0, 0, "> ");
|
||||
wrefresh(prompt);
|
||||
|
||||
/* index in entries, index in matched */
|
||||
#define WRITELIST(idx, idxm) mvwaddstr(list, listsize - idxm - 1, 0, get_entry(&entries, idx))
|
||||
/* index in entries, index in matched;
|
||||
* this function could include a wclrtoeol call, but that only hides indexing issues */
|
||||
#define WRITELIST(idx, idxm) \
|
||||
do{mvwaddstr(list, listsize - idxm - 1, 0, get_entry(&entries, idx));}while(0)
|
||||
|
||||
/* inital dump of list */
|
||||
for (size_t i = 0; i < entries.n; i++) {
|
||||
|
@ -134,15 +184,44 @@ int main()
|
|||
prefresh(list, listy, listx, 0, 0, nrows-1, COLS);
|
||||
|
||||
/* variables to control current search token */
|
||||
size_t n, cap;
|
||||
const size_t cap = MAX_TOK_LEN; /* words bigger than this aren't allowed */
|
||||
size_t n;
|
||||
char *name = NULL;
|
||||
/* search tokens */
|
||||
struct str_array toks = { 0 };
|
||||
|
||||
if (query) {
|
||||
/* the intial query can be edited,
|
||||
* so it needs to be the same sort of storage */
|
||||
name = xmalloc(cap);
|
||||
strncpy(name, query, cap);
|
||||
add_entry(&toks, name);
|
||||
|
||||
name = NULL;
|
||||
}
|
||||
print_prompt(prompt, &toks, true);
|
||||
|
||||
/* index inside set of matches */
|
||||
size_t index_in_matched = 0;
|
||||
for (;;) {
|
||||
if (poll(polls, POLLFD_AMOUNT, -1) < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
|
||||
perror("poll");
|
||||
quick_exit(1);
|
||||
}
|
||||
|
||||
if (polls[SIGNAL].revents & POLLIN) {
|
||||
int signum;
|
||||
read(polls[SIGNAL].fd, &signum, sizeof signum);
|
||||
if (signum == SIGINT || signum == SIGTERM) {
|
||||
quick_exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(polls[STDIN].revents & POLLIN)) continue;
|
||||
|
||||
if (!name) {
|
||||
cap = 1024;
|
||||
name = xmalloc(cap);
|
||||
|
||||
n = 0;
|
||||
|
@ -154,7 +233,7 @@ int main()
|
|||
int c = wgetch(prompt);
|
||||
switch (c) {
|
||||
case 27: /* Ctrl+[ or ESC */
|
||||
exit(1);
|
||||
quick_exit(1);
|
||||
break;
|
||||
|
||||
case KEY_ENTER:
|
||||
|
@ -185,7 +264,6 @@ int main()
|
|||
if (toks.n > 1 && n == 0) {
|
||||
free(pop_entry(&toks));
|
||||
name = get_entry(&toks, toks.n - 1);
|
||||
cap = malloc_usable_size(name);
|
||||
n = strlen(name);
|
||||
break;
|
||||
}
|
||||
|
@ -200,14 +278,15 @@ int main()
|
|||
break;
|
||||
|
||||
default:
|
||||
if (n == cap) {
|
||||
cap *= 2;
|
||||
name = xrealloc(name, cap);
|
||||
/* XXX: not unicode aware;
|
||||
* this is reasonable for now, since otherwise we'd have to
|
||||
* implement character deletion with unicode */
|
||||
if (n < cap && isprint((unsigned char)c)) {
|
||||
name[n] = c;
|
||||
n++;
|
||||
/* clear chars from previous entries and/or dirty memory */
|
||||
name[n] = 0;
|
||||
}
|
||||
name[n] = c;
|
||||
n++;
|
||||
/* clear chars from previous entries and/or dirty memory */
|
||||
name[n] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -251,16 +330,9 @@ int main()
|
|||
continue;
|
||||
}
|
||||
|
||||
werase(prompt);
|
||||
mvwaddstr(prompt, 0, 0, ">");
|
||||
for (size_t i = 0; i < toks.n; i++) {
|
||||
const char *e = get_entry(&toks, i);
|
||||
waddch(prompt, ' ');
|
||||
waddstr(prompt, e);
|
||||
}
|
||||
/* show space if name is NULL */
|
||||
if (!name) waddch(prompt, ' ');
|
||||
wrefresh(prompt);
|
||||
print_prompt(prompt, &toks, !name);
|
||||
|
||||
/* name==NULL means the search results won't change,
|
||||
* so we don't need to search again */
|
||||
if (!name) continue;
|
||||
|
|
22
util.c
22
util.c
|
@ -9,12 +9,13 @@ void read_entries_from_stream(struct str_array *a, int delim, FILE *input)
|
|||
size_t tmp = 0;
|
||||
ssize_t n;
|
||||
while ((n = getdelim(&line, &tmp, delim, input)) >= 0) {
|
||||
if (n == 1) {
|
||||
if (n == 0 || n == 1 && line[0] == delim) {
|
||||
/* don't match empty line */
|
||||
free(line);
|
||||
line = NULL;
|
||||
continue;
|
||||
}
|
||||
if (line[n-1] == '\n') line[n-1] = 0;
|
||||
if (line[n-1] == delim) line[n-1] = '\0';
|
||||
add_entry(a, line);
|
||||
|
||||
line = NULL;
|
||||
|
@ -22,16 +23,29 @@ void read_entries_from_stream(struct str_array *a, int delim, FILE *input)
|
|||
}
|
||||
}
|
||||
|
||||
void print_prompt(WINDOW *w, struct str_array *a, bool show_space)
|
||||
{
|
||||
werase(w);
|
||||
mvwaddstr(w, 0, 0, ">");
|
||||
for (size_t i = 0; i < a->n; i++) {
|
||||
const char *e = get_entry(a, i);
|
||||
waddch(w, ' ');
|
||||
waddstr(w, e);
|
||||
}
|
||||
if (show_space) waddch(w, ' ');
|
||||
wrefresh(w);
|
||||
}
|
||||
|
||||
void *xmalloc(size_t s) {
|
||||
void *r = malloc(s);
|
||||
if (r) return r;
|
||||
perror("malloc");
|
||||
exit(1);
|
||||
quick_exit(1);
|
||||
}
|
||||
|
||||
void *xrealloc(void *p, size_t s) {
|
||||
void *r = realloc(p, s);
|
||||
if (r) return r;
|
||||
perror("realloc");
|
||||
exit(1);
|
||||
quick_exit(1);
|
||||
}
|
||||
|
|
4
util.h
4
util.h
|
@ -1,11 +1,15 @@
|
|||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <curses.h>
|
||||
|
||||
#include "string-array.h"
|
||||
|
||||
void read_entries_from_stream(struct str_array *, int, FILE *);
|
||||
void print_prompt(WINDOW *, struct str_array *, bool);
|
||||
void *xmalloc(size_t);
|
||||
void *xrealloc(void *, size_t);
|
||||
|
||||
|
|
Loading…
Reference in New Issue