Skip to content

Commit

Permalink
Clean-up multiselect code
Browse files Browse the repository at this point in the history
Allocate memory for the multiselect buffer in chunks, similar to how the
`choices` buffer is resized. This results in potentially more unused
(i.e. wasted) memory, but in fewer invocations of `malloc`/`realloc`,
which is probably more important, considering the size of the
multiselect array will never be that large.
  • Loading branch information
Greg Anders committed Oct 14, 2019
1 parent a976159 commit 368d4e1
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 35 deletions.
53 changes: 34 additions & 19 deletions src/choices.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
/* Initial size of choices array */
#define INITIAL_CHOICE_CAPACITY 128

/* Initial size of multi-selection buffer */
#define INITIAL_SELECTIONS_CAPACITY 8

static int cmpchoice(const void *_idx1, const void *_idx2) {
const struct scored_result *a = _idx1;
const struct scored_result *b = _idx2;
Expand Down Expand Up @@ -92,6 +95,11 @@ static void choices_resize(choices_t *c, size_t new_capacity) {
c->capacity = new_capacity;
}

static void choices_resize_selections(choices_t *c, size_t new_capacity) {
c->selections.strings = safe_realloc(c->selections.strings, new_capacity * sizeof(const char *));
c->selections.capacity = new_capacity;
}

static void choices_reset_search(choices_t *c) {
free(c->results);
c->results = NULL;
Expand All @@ -101,8 +109,8 @@ static void choices_reset_search(choices_t *c) {
void choices_init(choices_t *c, options_t *options) {
c->strings = NULL;
c->results = NULL;
c->selections = NULL;
c->num_selections = 0;
c->selections.strings = NULL;
c->selections.capacity = c->selections.size = 0;

c->buffer_size = 0;
c->buffer = NULL;
Expand Down Expand Up @@ -132,9 +140,9 @@ void choices_destroy(choices_t *c) {
c->results = NULL;
c->available = c->selection = 0;

free(c->selections);
c->selections = NULL;
c->num_selections = 0;
free(c->selections.strings);
c->selections.strings = NULL;
c->selections.capacity = c->selections.size = 0;
}

void choices_add(choices_t *c, const char *choice) {
Expand All @@ -148,30 +156,37 @@ void choices_add(choices_t *c, const char *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;
if (c->selections.size == c->selections.capacity) {
if (c->selections.capacity == 0) {
choices_resize_selections(c, INITIAL_SELECTIONS_CAPACITY);
} else {
choices_resize_selections(c, c->selections.capacity * 2);
}
}

if (!choices_selected(c, choice)) {
c->selections.strings[c->selections.size++] = 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--;
size_t index = c->selections.size;
for (size_t i = 0; i < c->selections.size; i++) {
if (c->selections.strings[i] == choice) {
c->selections.size--;
index = i;
break;
}
}

for (size_t i = index; i < c->selections.size; i++) {
c->selections.strings[i] = c->selections.strings[i+1];
}
}

bool choices_selected(choices_t *c, const char *choice) {
for (size_t i = 0; i < c->num_selections; i++) {
if (c->selections[i] == choice) {
for (size_t i = 0; i < c->selections.size; i++) {
if (c->selections.strings[i] == choice) {
return true;
}
}
Expand Down
8 changes: 6 additions & 2 deletions src/choices.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ typedef struct {
size_t available;
size_t selection;

const char **selections;
size_t num_selections;
struct {
const char **strings;

size_t capacity;
size_t size;
} selections;

unsigned int worker_count;
} choices_t;
Expand Down
6 changes: 3 additions & 3 deletions src/tty_interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ static void action_emit(tty_interface_t *state) {

/* If no choices were selected with multi-select, use the choice under
* the cursor */
if (!state->choices->num_selections) {
if (!state->choices->selections.size) {
const char *selection = choices_get(state->choices, state->choices->selection);
if (selection) {
/* output the result */
Expand All @@ -160,8 +160,8 @@ static void action_emit(tty_interface_t *state) {
printf("%s\n", state->search);
}
} else {
for (size_t i = 0; i < state->choices->num_selections; i++) {
printf("%s\n", state->choices->selections[i]);
for (size_t i = 0; i < state->choices->selections.size; i++) {
printf("%s\n", state->choices->selections.strings[i]);
}
}

Expand Down
23 changes: 12 additions & 11 deletions test/test_choices.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,28 @@
#include "greatest/greatest.h"

#define ASSERT_SIZE_T_EQ(a,b) ASSERT_EQ_FMT((size_t)(a), (b), "%zu")
#define ASSERT_TRUE(cond) ASSERT_FALSE(!cond)

static options_t default_options;
static choices_t choices;

static void setup(void *udata) {
(void)udata;
(void)udata;

options_init(&default_options);
choices_init(&choices, &default_options);
options_init(&default_options);
choices_init(&choices, &default_options);
}

static void teardown(void *udata) {
(void)udata;
choices_destroy(&choices);
(void)udata;
choices_destroy(&choices);
}

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);
ASSERT_SIZE_T_EQ(0, choices.selections.size);

choices_prev(&choices);
ASSERT_SIZE_T_EQ(0, choices.selection);
Expand Down Expand Up @@ -166,18 +167,18 @@ TEST test_choices_multi_select() {
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);
ASSERT_TRUE(choices_selected(&choices, first_choice));
ASSERT_SIZE_T_EQ(1, choices.selections.size);

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);
ASSERT_TRUE(choices_selected(&choices, second_choice));
ASSERT_SIZE_T_EQ(2, choices.selections.size);

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

PASS();
}
Expand Down

0 comments on commit 368d4e1

Please sign in to comment.