#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <getopt.h>
#include "match.h"
#include "tty.h"
#include "choices.h"
int flag_show_scores = 0;
size_t num_lines = 10;
size_t scrolloff = 1;
void read_choices(choices_t *c){
char *line = NULL;
size_t len = 0;
ssize_t read;
while ((read = getline(&line, &len, stdin)) != -1) {
char *nl;
if((nl = strchr(line, '\n')))
*nl = '\0';
choices_add(c, line);
line = NULL;
}
free(line);
}
#define SEARCH_SIZE_MAX 4096
int search_size;
char search[SEARCH_SIZE_MAX + 1] = {0};
void clear(tty_t *tty){
tty_setcol(tty, 0);
size_t line = 0;
while(line++ < num_lines){
tty_newline(tty);
}
tty_moveup(tty, line-1);
tty_flush(tty);
}
#define TTY_COLOR_HIGHLIGHT TTY_COLOR_YELLOW
void draw_match(tty_t *tty, const char *choice, int selected){
int n = strlen(search);
size_t positions[n + 1];
for(int i = 0; i < n + 1; i++)
positions[i] = -1;
double score = match_positions(search, choice, &positions[0]);
size_t maxwidth = tty_getwidth(tty);
if(flag_show_scores)
tty_printf(tty, "(%5.2f) ", score);
if(selected)
tty_setinvert(tty);
for(size_t i = 0, p = 0; choice[i] != '\0'; i++){
if(i+1 < maxwidth){
if(positions[p] == i){
tty_setfg(tty, TTY_COLOR_HIGHLIGHT);
p++;
}else{
tty_setfg(tty, TTY_COLOR_NORMAL);
}
tty_printf(tty, "%c", choice[i]);
}else{
tty_printf(tty, "$");
break;
}
}
tty_setnormal(tty);
}
void draw(tty_t *tty, choices_t *choices){
size_t start = 0;
size_t current_selection = choices->selection;
if(current_selection + scrolloff >= num_lines){
start = current_selection + scrolloff - num_lines + 1;
if(start + num_lines >= choices_available(choices)){
start = choices_available(choices) - num_lines;
}
}
const char *prompt = "> ";
tty_setcol(tty, 0);
tty_printf(tty, "%s%s", prompt, search);
tty_clearline(tty);
for(size_t i = start; i < start + num_lines; i++){
tty_printf(tty, "\n");
tty_clearline(tty);
const char *choice = choices_get(choices, i);
if(choice){
draw_match(tty, choice, i == choices->selection);
}
}
tty_moveup(tty, num_lines);
tty_setcol(tty, strlen(prompt) + strlen(search));
tty_flush(tty);
}
void emit(choices_t *choices){
const char *selection = choices_get(choices, choices->selection);
if(selection){
/* output the selected result */
printf("%s\n", selection);
}else{
/* No match, output the query instead */
printf("%s\n", search);
}
exit(EXIT_SUCCESS);
}
void run(tty_t *tty, choices_t *choices){
choices_search(choices, search);
char ch;
do {
draw(tty, choices);
ch = tty_getchar(tty);
if(isprint(ch)){
if(search_size < SEARCH_SIZE_MAX){
search[search_size++] = ch;
search[search_size] = '\0';
choices_search(choices, search);
}
}else if(ch == 127 || ch == 8){ /* DEL || backspace */
if(search_size)
search[--search_size] = '\0';
choices_search(choices, search);
}else if(ch == 21){ /* C-U */
search_size = 0;
search[0] = '\0';
choices_search(choices, search);
}else if(ch == 23){ /* C-W */
if(search_size)
search[--search_size] = '\0';
while(search_size && !isspace(search[--search_size]))
search[search_size] = '\0';
choices_search(choices, search);
}else if(ch == 14){ /* C-N */
choices_next(choices);
}else if(ch == 16){ /* C-P */
choices_prev(choices);
}else if(ch == 9){ /* TAB */
strncpy(search, choices_get(choices, choices->selection), SEARCH_SIZE_MAX);
search_size = strlen(search);
}else if(ch == 10){ /* Enter */
clear(tty);
/* ttyout should be flushed before outputting on stdout */
fclose(tty->fout);
emit(choices);
}else if(ch == 27){ /* ESC */
ch = tty_getchar(tty);
if(ch == '[' || ch == 'O'){
ch = tty_getchar(tty);
if(ch == 'A'){ /* UP ARROW */
choices_prev(choices);
}else if(ch == 'B'){ /* DOWN ARROW */
choices_next(choices);
}
}
}
}while(1);
}
static const char *usage_str = ""
"Usage: fzy [OPTION]...\n"
" -l, --lines=LINES Specify how many lines of results to show (default 10)\n"
" -e, --show-matches=QUERY output the sorted matches of QUERY\n"
" -t, --tty=TTY Specify file to use as TTY device (default /dev/tty)\n"
" -s, --show-scores show the scores of each match\n"
" -h, --help display this help and exit\n"
" -v, --version output version information and exit\n";
void usage(const char *argv0){
fprintf(stderr, usage_str, argv0);
}
static struct option longopts[] = {
{ "show-matches", required_argument, NULL, 'e' },
{ "lines", required_argument, NULL, 'l' },
{ "tty", required_argument, NULL, 't' },
{ "show-scores", no_argument, NULL, 's' },
{ "version", no_argument, NULL, 'v' },
{ "benchmark", no_argument, NULL, 'b' },
{ "help", no_argument, NULL, 'h' },
{ NULL, 0, NULL, 0 }
};
int main(int argc, char *argv[]){
int benchmark = 0;
char *initial_query = NULL;
char *tty_filename = "/dev/tty";
char c;
while((c = getopt_long(argc, argv, "vhse:l:t:", longopts, NULL)) != -1){
switch(c){
case 'v':
printf("%s " VERSION " (c) 2014 John Hawthorn\n", argv[0]);
exit(EXIT_SUCCESS);
case 's':
flag_show_scores = 1;
break;
case 'e':
initial_query = optarg;
break;
case 'b':
benchmark = 1;
break;
case 't':
tty_filename = optarg;
break;
case 'l':
{
int l;
if(sscanf(optarg, "%d", &l) != 1 || l < 3){
fprintf(stderr, "Invalid format for --lines: %s\n", optarg);
fprintf(stderr, "Must be integer in range 3..\n");
usage(argv[0]);
exit(EXIT_FAILURE);
}
num_lines = l;
}
break;
case 'h':
default:
usage(argv[0]);
exit(EXIT_SUCCESS);
}
}
if(optind != argc){
usage(argv[0]);
exit(EXIT_FAILURE);
}
choices_t choices;
choices_init(&choices);
read_choices(&choices);
if(benchmark){
if(!initial_query){
fprintf(stderr, "Must specify -e/--show-matches with --benchmark\n");
exit(EXIT_FAILURE);
}
for(int i = 0; i < 100; i++)
choices_search(&choices, initial_query);
}else if(initial_query){
choices_search(&choices, initial_query);
for(size_t i = 0; i < choices_available(&choices); i++){
if(flag_show_scores)
printf("%f\t", choices_getscore(&choices, i));
printf("%s\n", choices_get(&choices, i));
}
}else{
/* interactive */
tty_t tty;
tty_init(&tty, tty_filename);
run(&tty, &choices);
}
return 0;
}