Compare commits

...

10 Commits

Author SHA1 Message Date
Érico Nogueira a265492706 Add intial query command line option.
It has an existing bug where the filter is only applied after the first
user interaction.
2022-08-13 00:19:05 -03:00
Érico Nogueira 69efce1d95 Add print_prompt funtion. 2022-08-13 00:11:32 -03:00
Érico Nogueira 55ae15eb6f Token length is now limited.
Otherwise it's necessary to be able to propagate back into the token
list the new location of a resized string.
2022-08-13 00:05:50 -03:00
Érico Nogueira a0951f2aa5 Only deal with ASCII printable characters.
At least for now, it's easier than dealing with unicode properly.
2022-08-13 00:05:50 -03:00
Érico Nogueira 8bf7ceef41 Clean up ef.c.
Use do{ /*things*/ }while(0) for macro with code, update comments and remove
unnecessary one.
2022-08-12 23:48:50 -03:00
Érico Nogueira ec6014a0e5 Duplicate stdin fd before reading from it.
Otherwise, stdin will have an error state of EOF, leading to wgetch()
returning -1, which is unexpected, given that we polled the file
descriptor for data.
2022-08-12 23:48:50 -03:00
Érico Nogueira 8f38a81690 Add some command line options.
- delimiter choice
- compatibility with fzf
2022-08-12 23:48:50 -03:00
Érico Nogueira cbb98853d1 Fix bug in entry reading.
- deal with empty lines correctly: single letter options that don't get
  a newline were being missed
- entirely empty lines (empty line without a newline) weren't being used
  correctly
- fix UB from not setting line=NULL
- use the delim parameter instead of hardcoding for newlines
2022-08-12 23:48:50 -03:00
Érico Nogueira b734660c96 Use poll(3) and self pipe for signal handling.
Actually safe to perform the terminal cleanup here. We keep using
exit(3) and quick_exit(3) so that only one of them actually prints the
results to the screen.
2022-08-12 23:48:36 -03:00
Érico Nogueira a0ce797775 Use /dev/tty instead of stderr for interactivity. 2022-08-12 15:03:10 -03:00
3 changed files with 139 additions and 49 deletions

162
ef.c
View File

@ -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
View File

@ -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
View File

@ -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);