Skip to content

Commit

Permalink
Enable multi-select
Browse files Browse the repository at this point in the history
Default multi-select key is Ctrl+s
  • Loading branch information
gpanders committed Oct 14, 2019
1 parent f770d7d commit a976159
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 10 deletions.
8 changes: 6 additions & 2 deletions fzy.1
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Usage help.
.
.TP
.BR "ENTER"
Print the selected item to stdout and exit
Print the selected item (or all multi-selected items) to stdout and exit
.TP
.BR "Ctrl+c, Esc"
Exit with status 1, without making a selection.
Expand All @@ -63,7 +63,7 @@ Select the previous item
.BR "Down Arrow, Ctrl+n"
Select the next item
.TP
Tab
.BR Tab
Replace the current search string with the selected item
.TP
.BR "Backspace, Ctrl+h"
Expand All @@ -74,6 +74,10 @@ Delete the word before the cursor
.TP
.BR Ctrl+u
Delete the entire line
.TP
.BR Ctrl+s
Multi-select the current item. Each multi-selected item will be printed on
its own line.
.
.SH USAGE EXAMPLES
.
Expand Down
39 changes: 38 additions & 1 deletion src/choices.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,15 @@ static void choices_resize(choices_t *c, size_t new_capacity) {

static void choices_reset_search(choices_t *c) {
free(c->results);
c->selection = c->available = 0;
c->results = NULL;
c->selection = c->available = 0;
}

void choices_init(choices_t *c, options_t *options) {
c->strings = NULL;
c->results = NULL;
c->selections = NULL;
c->num_selections = 0;

c->buffer_size = 0;
c->buffer = NULL;
Expand Down Expand Up @@ -129,6 +131,10 @@ void choices_destroy(choices_t *c) {
free(c->results);
c->results = NULL;
c->available = c->selection = 0;

free(c->selections);
c->selections = NULL;
c->num_selections = 0;
}

void choices_add(choices_t *c, const char *choice) {
Expand All @@ -141,6 +147,37 @@ void choices_add(choices_t *c, const char *choice) {
c->strings[c->size++] = choice;
}

void choices_select(choices_t *c, const char *choice) {
if (!c->selections) {
c->num_selections = 1;
c->selections = malloc(sizeof(char *));
c->selections[0] = choice;
} else if (!choices_selected(c, choice)) {
c->num_selections++;
c->selections = realloc(c->selections, c->num_selections * sizeof(char *));
c->selections[c->num_selections - 1] = choice;
}
}

void choices_deselect(choices_t *c, const char *choice) {
for (size_t i = 0; i < c->num_selections; i++) {
if (c->selections[i] == choice) {
c->selections[i] = NULL;
c->num_selections--;
break;
}
}
}

bool choices_selected(choices_t *c, const char *choice) {
for (size_t i = 0; i < c->num_selections; i++) {
if (c->selections[i] == choice) {
return true;
}
}
return false;
}

size_t choices_available(choices_t *c) {
return c->available;
}
Expand Down
7 changes: 7 additions & 0 deletions src/choices.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef CHOICES_H
#define CHOICES_H CHOICES_H

#include <stdbool.h>
#include <stdio.h>

#include "match.h"
Expand All @@ -24,13 +25,19 @@ typedef struct {
size_t available;
size_t selection;

const char **selections;
size_t num_selections;

unsigned int worker_count;
} choices_t;

void choices_init(choices_t *c, options_t *options);
void choices_fread(choices_t *c, FILE *file, char input_delimiter);
void choices_destroy(choices_t *c);
void choices_add(choices_t *c, const char *choice);
void choices_select(choices_t *c, const char *choice);
void choices_deselect(choices_t *c, const char *choice);
bool choices_selected(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);
Expand Down
6 changes: 5 additions & 1 deletion src/tty.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,13 @@ void tty_setunderline(tty_t *tty) {
tty_sgr(tty, 4);
}

void tty_setbold(tty_t *tty) {
tty_sgr(tty, 1);
}

void tty_setnormal(tty_t *tty) {
tty_sgr(tty, 0);
tty->fgcolor = 9;
tty->fgcolor = TTY_COLOR_NORMAL;
}

void tty_setnowrap(tty_t *tty) {
Expand Down
1 change: 1 addition & 0 deletions src/tty.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ int tty_input_ready(tty_t *tty, long int timeout, int return_on_signal);
void tty_setfg(tty_t *tty, int fg);
void tty_setinvert(tty_t *tty);
void tty_setunderline(tty_t *tty);
void tty_setbold(tty_t *tty);
void tty_setnormal(tty_t *tty);
void tty_setnowrap(tty_t *tty);
void tty_setwrap(tty_t *tty);
Expand Down
37 changes: 31 additions & 6 deletions src/tty_interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ static void draw_match(tty_interface_t *state, const char *choice, int selected)
}
}

if (choices_selected(state->choices, choice)) {
tty_setbold(tty);
}

if (selected)
#ifdef TTY_SELECTION_UNDERLINE
tty_setunderline(tty);
Expand Down Expand Up @@ -124,6 +128,17 @@ static void update_state(tty_interface_t *state) {
}
}

static void action_select(tty_interface_t *state) {
update_state(state);

const char *selection = choices_get(state->choices, state->choices->selection);
if (choices_selected(state->choices, selection)) {
choices_deselect(state->choices, selection);
} else {
choices_select(state->choices, selection);
}
}

static void action_emit(tty_interface_t *state) {
update_state(state);

Expand All @@ -133,13 +148,21 @@ 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);
if (selection) {
/* output the selected result */
printf("%s\n", selection);
/* If no choices were selected with multi-select, use the choice under
* the cursor */
if (!state->choices->num_selections) {
const char *selection = choices_get(state->choices, state->choices->selection);
if (selection) {
/* output the result */
printf("%s\n", selection);
} else {
/* No match, output the query instead */
printf("%s\n", state->search);
}
} else {
/* No match, output the query instead */
printf("%s\n", state->search);
for (size_t i = 0; i < state->choices->num_selections; i++) {
printf("%s\n", state->choices->selections[i]);
}
}

state->exit = EXIT_SUCCESS;
Expand Down Expand Up @@ -258,6 +281,7 @@ static void append_search(tty_interface_t *state, char ch) {
void tty_interface_init(tty_interface_t *state, tty_t *tty, choices_t *choices, options_t *options) {
state->tty = tty;
state->choices = choices;

state->options = options;
state->ambiguous_key_pending = 0;

Expand Down Expand Up @@ -292,6 +316,7 @@ static const keybinding_t keybindings[] = {{"\x1b", action_exit}, /* ESC *
{KEY_CTRL('C'), action_exit}, /* C-C */
{KEY_CTRL('D'), action_exit}, /* C-D */
{KEY_CTRL('M'), action_emit}, /* CR */
{KEY_CTRL('S'), action_select}, /* C-S */
{KEY_CTRL('P'), action_prev}, /* C-P */
{KEY_CTRL('N'), action_next}, /* C-N */
{KEY_CTRL('K'), action_prev}, /* C-K */
Expand Down
26 changes: 26 additions & 0 deletions test/test_choices.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ TEST test_choices_empty() {
ASSERT_SIZE_T_EQ(0, choices.size);
ASSERT_SIZE_T_EQ(0, choices.available);
ASSERT_SIZE_T_EQ(0, choices.selection);
ASSERT_SIZE_T_EQ(0, choices.num_selections);

choices_prev(&choices);
ASSERT_SIZE_T_EQ(0, choices.selection);
Expand Down Expand Up @@ -157,6 +158,30 @@ TEST test_choices_large_input() {
PASS();
}

TEST test_choices_multi_select() {
choices_add(&choices, "tags");
choices_add(&choices, "test");
choices_search(&choices, "");

const char *first_choice = choices_get(&choices, 0);
ASSERT_FALSE(choices_selected(&choices, first_choice));
choices_select(&choices, first_choice);
ASSERT_FALSE(!choices_selected(&choices, first_choice));
ASSERT_SIZE_T_EQ(1, choices.num_selections);

const char *second_choice = choices_get(&choices, 1);
ASSERT_FALSE(choices_selected(&choices, second_choice));
choices_select(&choices, second_choice);
ASSERT_FALSE(!choices_selected(&choices, second_choice));
ASSERT_SIZE_T_EQ(2, choices.num_selections);

choices_deselect(&choices, second_choice);
ASSERT_FALSE(choices_selected(&choices, second_choice));
ASSERT_SIZE_T_EQ(1, choices.num_selections);

PASS();
}

SUITE(choices_suite) {
SET_SETUP(setup, NULL);
SET_TEARDOWN(teardown, NULL);
Expand All @@ -167,4 +192,5 @@ SUITE(choices_suite) {
RUN_TEST(test_choices_without_search);
RUN_TEST(test_choices_unicode);
RUN_TEST(test_choices_large_input);
RUN_TEST(test_choices_multi_select);
}

0 comments on commit a976159

Please sign in to comment.