diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/choices.c | 140 | ||||
-rw-r--r-- | src/choices.h | 18 | ||||
-rw-r--r-- | src/common.h | 2 | ||||
-rw-r--r-- | src/config.def.h | 1 | ||||
-rw-r--r-- | src/fzy.c | 7 | ||||
-rw-r--r-- | src/options.c | 125 | ||||
-rw-r--r-- | src/options.h | 16 | ||||
-rw-r--r-- | src/tty_interface.c | 9 |
8 files changed, 277 insertions, 41 deletions
diff --git a/src/choices.c b/src/choices.c index fe2f80b..998fb4b 100644 --- a/src/choices.c +++ b/src/choices.c @@ -5,6 +5,7 @@ #include <unistd.h> #include <errno.h> +#include "common.h" #include "options.h" #include "choices.h" #include "match.h" @@ -20,11 +21,10 @@ static int cmpchoice(const void *_idx1, const void *_idx2) { const struct scored_result *b = _idx2; if (a->score == b->score) { - /* To ensure a stable sort, we must also sort by the string - * pointers. We can do this since we know all the strings are - * from a contiguous memory segment (buffer in choices_t). + /* To ensure a stable sort, we must also sort by the line + * indices. */ - if (a->str < b->str) { + if (a->idx < b->idx) { return -1; } else { return 1; @@ -46,6 +46,66 @@ static void *safe_realloc(void *buffer, size_t size) { return buffer; } +static char *line_split(const char *line, const char *delim, + const FieldSelector *fs) +{ + int nb_fields; + struct { + int start; + int len; + } *fields = NULL; + + char *ret = NULL; + size_t offset, ret_len; + const char *p; + + /* split line into fields */ + p = line; + nb_fields = 0; + while (*p) { + p += strspn(p, delim); + if (!*p) + break; + + fields = safe_realloc(fields, (nb_fields + 1) * sizeof(*fields)); + + fields[nb_fields].start = p - line; + fields[nb_fields].len = strcspn(p, delim); + p += fields[nb_fields].len; + nb_fields++; + } + + /* count output length */ + ret_len = offset = 0; + for (size_t i = 0; i < fs->nb_ranges; i++) { + size_t range_len; + + int start = fs->ranges[i].start; + int end = fs->ranges[i].end; + + if (start < 0) + start = MAX(nb_fields + start, 0); + start = MIN(nb_fields - 1, start); + + if (end < 0) + end = MAX(nb_fields + end, 0); + end = MIN(nb_fields - 1, end); + + if (end < start) + continue; + + range_len = fields[end].start + fields[end].len - fields[start].start; + + ret = safe_realloc(ret, ret_len + range_len + 1); + + strncpy(ret + offset, line + fields[start].start, range_len); + offset += range_len; + ret[offset] = 0; + } + + return ret; +} + void choices_fread(choices_t *c, FILE *file, char input_delimiter) { /* Save current position for parsing later */ size_t buffer_start = c->buffer_size; @@ -88,7 +148,11 @@ void choices_fread(choices_t *c, FILE *file, char input_delimiter) { } static void choices_resize(choices_t *c, size_t new_capacity) { - c->strings = safe_realloc(c->strings, new_capacity * sizeof(const char *)); + c->input_items = safe_realloc(c->input_items, + new_capacity * sizeof(*c->input_items)); + memset(c->input_items + c->capacity, 0, + (new_capacity - c->capacity) * sizeof(*c->input_items)); + c->capacity = new_capacity; } @@ -99,13 +163,18 @@ static void choices_reset_search(choices_t *c) { } void choices_init(choices_t *c, options_t *options) { - c->strings = NULL; + c->input_items = NULL; c->results = NULL; c->buffer_size = 0; c->buffer = NULL; c->capacity = c->size = 0; + + c->delimiters = options->delimiters; + c->search_fields = &options->search_fields; + c->output_fields = &options->output_fields; + choices_resize(c, INITIAL_CHOICE_CAPACITY); if (options->workers) { @@ -122,8 +191,11 @@ void choices_destroy(choices_t *c) { c->buffer = NULL; c->buffer_size = 0; - free(c->strings); - c->strings = NULL; + for (size_t i = 0; i < c->size; i++) + free(c->input_items[i].allocated); + + free(c->input_items); + c->input_items = NULL; c->capacity = c->size = 0; free(c->results); @@ -131,14 +203,26 @@ void choices_destroy(choices_t *c) { c->available = c->selection = 0; } -void choices_add(choices_t *c, const char *choice) { - /* Previous search is now invalid */ - choices_reset_search(c); +void choices_add(choices_t *c, const char *line) +{ + InputItem *it; if (c->size == c->capacity) { choices_resize(c, c->capacity * 2); } - c->strings[c->size++] = choice; + it = &c->input_items[c->size++]; + + it->input_line = line; + it->search_buf = it->input_line; + + /* extract the fields to be searched, if requested */ + if (c->search_fields->nb_ranges) { + it->allocated = line_split(line, c->delimiters, c->search_fields); + it->search_buf = it->allocated; + } + + /* Previous search is now invalid */ + choices_reset_search(c); } size_t choices_available(choices_t *c) { @@ -230,9 +314,12 @@ static void *choices_search_worker(void *data) { } for(size_t i = start; i < end; i++) { - if (has_match(job->search, c->strings[i])) { - result->list[result->size].str = c->strings[i]; - result->list[result->size].score = match(job->search, c->strings[i]); + InputItem *it = &c->input_items[i]; + const char *str = it->search_buf; + + if (has_match(job->search, str)) { + result->list[result->size].idx = i; + result->list[result->size].score = match(job->search, str); result->size++; } } @@ -300,12 +387,23 @@ void choices_search(choices_t *c, const char *search) { free(job); } -const char *choices_get(choices_t *c, size_t n) { - if (n < c->available) { - return c->results[n].str; - } else { - return NULL; - } +const char *choices_get_search(choices_t *c, size_t n) { + if (n < c->available) + return c->input_items[c->results[n].idx].search_buf; + return NULL; +} + +char *choices_get_output(choices_t *c, size_t n) +{ + const char *line; + + if (n >= c->available) + return NULL; + + line = c->input_items[c->results[n].idx].input_line; + if (c->output_fields->nb_ranges) + return line_split(line, c->delimiters, c->output_fields); + return strdup(line); } score_t choices_getscore(choices_t *c, size_t n) { diff --git a/src/choices.h b/src/choices.h index 925478e..70ef13f 100644 --- a/src/choices.h +++ b/src/choices.h @@ -8,9 +8,16 @@ struct scored_result { score_t score; - const char *str; + size_t idx; }; +typedef struct InputItem { + const char *input_line; + const char *search_buf; + + char *allocated; +} InputItem; + typedef struct { char *buffer; size_t buffer_size; @@ -18,13 +25,17 @@ typedef struct { size_t capacity; size_t size; - const char **strings; + InputItem *input_items; struct scored_result *results; size_t available; size_t selection; unsigned int worker_count; + + const char *delimiters; + const FieldSelector *search_fields; + const FieldSelector *output_fields; } choices_t; void choices_init(choices_t *c, options_t *options); @@ -33,7 +44,8 @@ void choices_destroy(choices_t *c); void choices_add(choices_t *c, const char *choice); size_t choices_available(choices_t *c); void choices_search(choices_t *c, const char *search); -const char *choices_get(choices_t *c, size_t n); +const char *choices_get_search(choices_t *c, size_t n); +char *choices_get_output(choices_t *c, size_t n); score_t choices_getscore(choices_t *c, size_t n); void choices_prev(choices_t *c); void choices_next(choices_t *c); diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..7395adb --- /dev/null +++ b/src/common.h @@ -0,0 +1,2 @@ +#define MIN(x, y) ((x) <= (y) ? (x) : (y)) +#define MAX(x, y) ((x) >= (y) ? (x) : (y)) diff --git a/src/config.def.h b/src/config.def.h index fcdcc03..5b33f81 100644 --- a/src/config.def.h +++ b/src/config.def.h @@ -17,3 +17,4 @@ #define DEFAULT_NUM_LINES 10 #define DEFAULT_WORKERS 0 #define DEFAULT_SHOW_INFO 0 +#define DEFAULT_DELIMITERS " " @@ -34,9 +34,14 @@ int main(int argc, char *argv[]) { choices_fread(&choices, stdin, options.input_delimiter); choices_search(&choices, options.filter); for (size_t i = 0; i < choices_available(&choices); i++) { + char *output; + if (options.show_scores) printf("%f\t", choices_getscore(&choices, i)); - printf("%s\n", choices_get(&choices, i)); + + output = choices_get_output(&choices, i); + printf("%s\n", output); + free(output); } } else { /* interactive */ diff --git a/src/options.c b/src/options.c index e35402f..e06b421 100644 --- a/src/options.c +++ b/src/options.c @@ -1,3 +1,5 @@ +#include <ctype.h> +#include <errno.h> #include <getopt.h> #include <limits.h> #include <stdio.h> @@ -11,17 +13,20 @@ static const char *usage_str = "" "Usage: fzy [OPTION]...\n" - " -l, --lines=LINES Specify how many lines of results to show (default 10)\n" - " -p, --prompt=PROMPT Input prompt (default '> ')\n" - " -q, --query=QUERY Use QUERY as the initial search string\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" - " -0, --read-null Read input delimited by ASCII NUL characters\n" - " -j, --workers NUM Use NUM workers for searching. (default is # of CPUs)\n" - " -i, --show-info Show selection info line\n" - " -h, --help Display this help and exit\n" - " -v, --version Output version information and exit\n"; + " -l, --lines=LINES Specify how many lines of results to show (default 10)\n" + " -p, --prompt=PROMPT Input prompt (default '> ')\n" + " -q, --query=QUERY Use QUERY as the initial search string\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" + " -0, --read-null Read input delimited by ASCII NUL characters\n" + " -j, --workers NUM Use NUM workers for searching. (default is # of CPUs)\n" + " -i, --show-info Show selection info line\n" + " -f, --search-fields=SELECTOR Use these fields for searching. Default is the whole line.\n" + " -F, --output-fields=SELECTOR Use these fields for output. Default is the whole line)\n" + " -d, --delimiters=DELIM Use these delimiters to split input lines into fields. Default is '<space><Tab>'.\n" + " -h, --help Display this help and exit\n" + " -v, --version Output version information and exit\n"; static void usage(const char *argv0) { fprintf(stderr, usage_str, argv0); @@ -38,11 +43,87 @@ static struct option longopts[] = {{"show-matches", required_argument, NULL, 'e' {"benchmark", optional_argument, NULL, 'b'}, {"workers", required_argument, NULL, 'j'}, {"show-info", no_argument, NULL, 'i'}, + {"search-fields", required_argument, NULL, 'f'}, + {"output-fields", required_argument, NULL, 'F'}, + {"delimiter", required_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}}; +static void field_selector_uninit(FieldSelector *fs) +{ + if (!fs) + return; + + free(fs->ranges); + fs->ranges = NULL; + fs->nb_ranges = 0; +} + +static int field_selector_parse(FieldSelector *fs, char *str) +{ + size_t nb_ranges; + int cur_range; + + field_selector_uninit(fs); + + if (!str || !*str) + return 0; + + nb_ranges = 1; + for (const char *p = str; *p; p++) { + if (*p == ',') + nb_ranges++; + } + + fs->ranges = calloc(nb_ranges, sizeof(*fs->ranges)); + if (!fs->ranges) + return -ENOMEM; + fs->nb_ranges = nb_ranges; + + cur_range = 0; + while (*str) { + int start, end; + + start = 0; + if (*str == '-' || isdigit(*str)) + start = strtol(str, &str, 0); + + if (*str == ':') { + str++; + end = (*str == '-' || isdigit(*str)) ? + strtol(str, &str, 0) : -1; + } else + end = start; + + if (*str) { + if (*str != ',') { + field_selector_uninit(fs); + return -EINVAL; + } + + str++; + } + + fs->ranges[cur_range].start = start; + fs->ranges[cur_range].end = end; + cur_range++; + } + + return 0; +} + +void options_uninit(options_t *options) +{ + field_selector_uninit(&options->search_fields); + field_selector_uninit(&options->output_fields); + + memset(options, 0, sizeof(*options)); +} + void options_init(options_t *options) { /* set defaults */ + memset(options, 0, sizeof(*options)); + options->benchmark = 0; options->filter = NULL; options->init_search = NULL; @@ -54,13 +135,14 @@ void options_init(options_t *options) { options->workers = DEFAULT_WORKERS; options->input_delimiter = '\n'; options->show_info = DEFAULT_SHOW_INFO; + options->delimiters = DEFAULT_DELIMITERS; } void options_parse(options_t *options, int argc, char *argv[]) { options_init(options); int c; - while ((c = getopt_long(argc, argv, "vhs0e:q:l:t:p:j:i", longopts, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "vhs0e:q:l:t:p:j:id:f:F:", longopts, NULL)) != -1) { switch (c) { case 'v': printf("%s " VERSION " © 2014-2018 John Hawthorn\n", argv[0]); @@ -114,6 +196,25 @@ void options_parse(options_t *options, int argc, char *argv[]) { case 'i': options->show_info = 1; break; + case 'd': + options->delimiters = optarg; + break; + case 'f': { + int ret = field_selector_parse(&options->search_fields, optarg); + if (ret < 0) { + fprintf(stderr, "Invalid format for --search-fields: %s\n", optarg); + usage(argv[0]); + exit(EXIT_FAILURE); + } + } break; + case 'F': { + int ret = field_selector_parse(&options->output_fields, optarg); + if (ret < 0) { + fprintf(stderr, "Invalid format for --output-field: %s\n", optarg); + usage(argv[0]); + exit(EXIT_FAILURE); + } + } break; case 'h': default: usage(argv[0]); diff --git a/src/options.h b/src/options.h index 4be4cb6..e8419b1 100644 --- a/src/options.h +++ b/src/options.h @@ -1,6 +1,16 @@ #ifndef OPTIONS_H #define OPTIONS_H OPTIONS_H +typedef struct FieldRange { + int start; + int end; +} FieldRange; + +typedef struct FieldSelector { + FieldRange *ranges; + size_t nb_ranges; +} FieldSelector; + typedef struct { int benchmark; const char *filter; @@ -13,9 +23,15 @@ typedef struct { unsigned int workers; char input_delimiter; int show_info; + + const char *delimiters; + + FieldSelector search_fields; + FieldSelector output_fields; } options_t; void options_init(options_t *options); void options_parse(options_t *options, int argc, char *argv[]); +void options_uninit(options_t *options); #endif diff --git a/src/tty_interface.c b/src/tty_interface.c index 343dde8..305c0eb 100644 --- a/src/tty_interface.c +++ b/src/tty_interface.c @@ -103,7 +103,7 @@ static void draw(tty_interface_t *state) { for (size_t i = start; i < start + num_lines; i++) { tty_printf(tty, "\n"); tty_clearline(tty); - const char *choice = choices_get(choices, i); + const char *choice = choices_get_search(choices, i); if (choice) { draw_match(state, choice, i == choices->selection); } @@ -140,10 +140,11 @@ static void action_emit(tty_interface_t *state) { /* ttyout should be flushed before outputting on stdout */ tty_close(state->tty); - const char *selection = choices_get(state->choices, state->choices->selection); + char *selection = choices_get_output(state->choices, state->choices->selection); if (selection) { /* output the selected result */ printf("%s\n", selection); + free(selection); } else { /* No match, output the query instead */ printf("%s\n", state->search); @@ -237,9 +238,9 @@ static void action_pagedown(tty_interface_t *state) { static void action_autocomplete(tty_interface_t *state) { update_state(state); - const char *current_selection = choices_get(state->choices, state->choices->selection); + const char *current_selection = choices_get_search(state->choices, state->choices->selection); if (current_selection) { - strncpy(state->search, choices_get(state->choices, state->choices->selection), SEARCH_SIZE_MAX); + strncpy(state->search, choices_get_search(state->choices, state->choices->selection), SEARCH_SIZE_MAX); state->cursor = strlen(state->search); } } |