~nloomans/ft_select

14e0f397f05217baf9ded0c681378fe6d9e81a24 — Noah Loomans 1 year, 8 months ago 1e1794e
rewrite the linked list implementation

The new linked list implementation has been inspired by the list.h from
the Linux kernel.
M inc/derive.h => inc/derive.h +3 -3
@@ 38,11 38,11 @@ struct	s_derived_dimensions
t_error	derive_dimensions(
			struct s_derived_dimensions *res,
			struct s_state_terminal terminal,
			t_list2 options);
			t_list2_meta options);
t_error	derive_column_width(
			size_t **res,
			struct s_derived_dimensions dimensions,
			t_list2 options);
			t_list2_meta options);
bool	derive_enough_columns(
			struct s_state_terminal terminal,
			struct s_derived_dimensions dimensions,


@@ 50,7 50,7 @@ bool	derive_enough_columns(
t_error	derive_rows(
			struct s_state_option ****res,
			struct s_derived_dimensions dimensions,
			t_list2 options);
			t_list2_meta options);
void	derive_free_rows(
			struct s_derived_dimensions dimensions,
			struct s_state_option ****rows);

M inc/list2.h => inc/list2.h +50 -34
@@ 3,47 3,63 @@

/*
** list2 - double linked list implementation.
**
** This linked list implementation has been inspired by the list.h from the
** Linux kernel.
**
** The s_list2_conn struct contains pointers to the next and previous
** s_list2_conn structs. The functions contained in this header will malipulate
** these pointers. The struct contains only those pointers and therefor no
** content. It should not be used standalone. Instead, put it inside of a
** parrent struct as follows:
**
** struct s_example_node
** {
**   char         *foo;
**   int          bar;
**   [etc...]
**   t_list2_conn conn;
** }
**
** To insert an element, malloc the aforementioned struct and give list2_insert
** a pointer to the conn member of the struct.
**
** To get the s_example_node struct from a conn, an unpack function must be
** written. In this case the function would look like this:
**
** struct s_state_option    *unpack_example(struct s_list2_conn *conn)
** {
**     if (conn == NULL)
**         return (NULL);
**     return (struct s_example_node *)(
**         (char *)conn - offsetof(struct s_example_node, conn));
** }
**
** This function takes a pointer to the conn member of the example struct, and
** returns a pointer to the example struct itself. It does this by calculating
** the offset of the conn member relative to the start of the example struct.
*/

typedef void	t_list2_content;

typedef struct	s_list2_item
typedef struct	s_list2_conn
{
	struct s_list2_item	*prev;
	struct s_list2_item	*next;
	t_list2_content		*content;
}				t_list2_item;
	struct s_list2_conn	*prev;
	struct s_list2_conn	*next;
}				t_list2_conn;

typedef struct	s_list2
typedef struct	s_list2_meta
{
	struct s_list2_item	*first;
	struct s_list2_item	*last;
	struct s_list2_conn	*first;
	struct s_list2_conn	*last;
	size_t				len;
}				t_list2;
}				t_list2_meta;

/*
** Create a new list item containing content and insert it into list after prev.
** Returns the created list item.
**
** If prev is not inside list behavior is undefined. prev may be NULL, in which
** case the item is inserted at the beginning of the list.
*/
t_list2_item	*list2_insert(
					t_list2 *list,
					t_list2_item *prev,
					t_list2_content *content);

typedef void	(*t_free_list2_content)(t_list2_content *content);
void			list2_insert(
					t_list2_meta *meta,
					t_list2_conn *prev,
					t_list2_conn *new);

/*
** Remove & free an item from the list. free_func may be NULL if the item
** content does not need to be freed.
**
** If item is not inside list behavior is undefined.
*/
void			list2_remove(
					t_list2 *list,
					t_list2_item **item,
					t_free_list2_content free_func);
void			list2_unlink(
					t_list2_meta *meta,
					t_list2_conn *conn);

#endif

M inc/state.h => inc/state.h +9 -6
@@ 13,18 13,21 @@ struct	s_state_terminal

struct	s_state_option
{
	char					*name;
	bool					selected;
	char				*name;
	bool				selected;
	struct s_list2_conn	conn;
};

struct	s_state
{
	struct s_state_terminal	terminal;
	t_list2					options;
	t_list2_item			*cursor;
	t_list2_meta			options;
	t_list2_conn			*cursor;
};

t_error	state_make_options(t_list2 *dest, int argc, char **argv);
void	state_debug(const struct s_state state);
t_error					state_make_options(
							t_list2_meta *dest, int argc, char **argv);
struct s_state_option	*unpack_option(struct s_list2_conn *conn);
void					state_debug(const struct s_state state);

#endif

M src/action/confirm.c => src/action/confirm.c +8 -8
@@ 6,22 6,22 @@

void			action_confirm(struct s_state *state)
{
	t_list2_item			*curr_item;
	struct s_state_option	*curr_option;
	t_list2_conn			*conn;
	struct s_state_option	*option;
	bool					is_first;

	terminal_configure(TERMINAL_CONFIGURE_RESTORE);
	curr_item = state->options.first;
	conn = state->options.first;
	is_first = true;
	while (curr_item != NULL)
	while (conn)
	{
		curr_option = (struct s_state_option *)curr_item->content;
		if (curr_option->selected)
		option = unpack_option(conn);
		if (option->selected)
		{
			ft_printf("%s%s", is_first ? "" : " ", curr_option->name);
			ft_printf("%s%s", is_first ? "" : " ", option->name);
			is_first = false;
		}
		curr_item = curr_item->next;
		conn = conn->next;
	}
	ft_printf("\n");
	exit(0);

M src/action/delete.c => src/action/delete.c +5 -3
@@ 1,19 1,21 @@
#include <stdlib.h>
#include <libft.h>
#include "action.h"
#include "list2.h"

void			action_delete(struct s_state *state)
{
	t_list2_item	*to_delete;
	struct s_state_option	*to_delete;

	to_delete = state->cursor;
	to_delete = unpack_option(state->cursor);
	if (state->cursor->next)
		state->cursor = state->cursor->next;
	else if (state->cursor->prev)
		state->cursor = state->cursor->prev;
	else
		state->cursor = NULL;
	list2_remove(&state->options, &to_delete, &free);
	list2_unlink(&state->options, &to_delete->conn);
	ft_memdel((void **)&to_delete);
	if (state->cursor == NULL)
		action_quit(state);
}

M src/action/select.c => src/action/select.c +3 -3
@@ 3,8 3,8 @@

void			action_select(struct s_state *state)
{
	struct s_state_option	*content;
	struct s_state_option	*option;

	content = (struct s_state_option *)state->cursor->content;
	content->selected = !content->selected;
	option = unpack_option(state->cursor);
	option->selected = !option->selected;
}

M src/derive.c => src/derive.c +13 -15
@@ 10,7 10,7 @@ static size_t	max(size_t a, size_t b)
t_error			derive_dimensions(
					struct s_derived_dimensions *dimensions,
					struct s_state_terminal terminal,
					t_list2 options)
					t_list2_meta options)
{
	if (DERIVE_PADDING_TOP + DERIVE_PADDING_BOTTOM + 1 > terminal.rows)
		return (errorf("terminal too short to display options"));


@@ 25,27 25,26 @@ t_error			derive_dimensions(
t_error			derive_column_width(
					size_t **res,
					struct s_derived_dimensions dimensions,
					t_list2 options)
					t_list2_meta options)
{
	size_t			row_index;
	size_t			column_index;
	t_list2_item	*curr_option;
	t_list2_conn	*conn;

	*res = ft_memalloc(dimensions.columns * sizeof(**res));
	if (*res == NULL)
		return errorf("unable to malloc column width array");
	curr_option = options.first;
	conn = options.first;
	column_index = 0;
	while (column_index < dimensions.columns)
	{
		row_index = 0;
		while (row_index < dimensions.rows && curr_option != NULL)
		while (row_index < dimensions.rows && conn != NULL)
		{
			(*res)[column_index] = max(
				ft_strlen(
					((struct s_state_option *)curr_option->content)->name),
				ft_strlen(unpack_option(conn)->name),
				(*res)[column_index]);
			curr_option = curr_option->next;
			conn = conn->next;
			row_index++;
		}
		column_index++;


@@ 76,26 75,25 @@ static t_error	malloc_rows(
t_error			derive_rows(
					struct s_state_option ****res,
					struct s_derived_dimensions dimensions,
					t_list2 options)
					t_list2_meta options)
{
	size_t			row_index;
	size_t			column_index;
	t_list2_item	*curr_option;
	t_list2_conn	*conn;
	t_error			error;

	error = malloc_rows(res, dimensions);
	if (is_error(error))
		return (error);
	curr_option = options.first;
	conn = options.first;
	column_index = 0;
	while (column_index < dimensions.columns)
	{
		row_index = 0;
		while (row_index < dimensions.rows && curr_option != NULL)
		while (row_index < dimensions.rows && conn != NULL)
		{
			(*res)[row_index][column_index] =
				(struct s_state_option *)curr_option->content;
			curr_option = curr_option->next;
			(*res)[row_index][column_index] = unpack_option(conn);
			conn = conn->next;
			row_index++;
		}
		column_index++;

M src/list2.c => src/list2.c +23 -34
@@ 1,55 1,44 @@
#include <libft.h>
#include "list2.h"

t_list2_item	*list2_insert(
					t_list2 *list,
					t_list2_item *prev,
					t_list2_content *content)
void	list2_insert(
			t_list2_meta *meta,
			t_list2_conn *prev,
			t_list2_conn *new)
{
	t_list2_item	*new;

	new = ft_memalloc(sizeof(*new));
	if (new == NULL)
		return (NULL);
	new->content = content;
	new->prev = prev;
	if (new->prev != NULL)
	if (new->prev)
	{
		new->next = new->prev->next;
		new->prev->next = new;
		if (new->next != NULL)
			new->next->prev = new;
		else
			list->last = new;
			meta->last = new;
	}
	else
	{
		new->next = list->first;
		new->next = meta->first;
		if (new->next)
			new->next->prev = new;
		list->first = new;
		if (list->last == NULL)
			list->last = new;
		meta->first = new;
		if (meta->last == NULL)
			meta->last = new;
	}
	list->len++;
	return (new);
	meta->len++;
}

void			list2_remove(
					t_list2 *list,
					t_list2_item **item,
					t_free_list2_content free_func)
void			list2_unlink(
					t_list2_meta *meta,
					t_list2_conn *conn)
{
	if (free_func)
		(*free_func)((*item)->content);
	if (list->first == *item)
		list->first = (*item)->next;
	if (list->last == *item)
		list->last = (*item)->prev;
	if ((*item)->prev)
		(*item)->prev->next = (*item)->next;
	if ((*item)->next)
		(*item)->next->prev = (*item)->prev;
	ft_memdel((void **)item);
	list->len--;
	if (meta->first == conn)
		meta->first = conn->next;
	if (meta->last == conn)
		meta->last = conn->prev;
	if (conn->prev)
		conn->prev->next = conn->next;
	if (conn->next)
		conn->next->prev = conn->prev;
	meta->len--;
}

M src/list2_test.c => src/list2_test.c +89 -83
@@ 1,118 1,124 @@
#include <criterion/criterion.h>
#include "list2.h"

struct	s_example
{
	int					data;
	struct s_list2_conn	conn;
};

static struct s_example	*unpack_example(struct s_list2_conn *conn)
{
	if (conn == NULL)
		return (NULL);
	return (struct s_example *)(
		(char *)conn - offsetof(struct s_example, conn));
}

Test(list2_insert, first_and_only)
{
	int		new_content = 42;
	t_list2	list = (t_list2){
	struct s_example	new = (struct s_example){
		.data = 42,
	};
	t_list2_meta		meta = (t_list2_meta){
		.first = NULL,
		.last = NULL,
		.len = 0,
	};

	t_list2_item *res = list2_insert(&list, NULL, &new_content);
	cr_assert_eq(*(int *)res->content, new_content);
	cr_assert_eq(res->prev, NULL);
	cr_assert_eq(res->next, NULL);
	cr_assert_eq(list.first, res);
	cr_assert_eq(list.last, res);
	cr_assert_eq(list.len, 1);

	list2_remove(&list, &res, NULL);
	list2_insert(&meta, NULL, &new.conn);
	cr_expect_eq(unpack_example(meta.first)->data, 42);
	cr_expect_eq(meta.first, meta.last);
	cr_expect_eq(meta.first->prev, NULL);
	cr_expect_eq(meta.first->next, NULL);
	cr_expect_eq(meta.len, 1);
}

Test(list2_insert, first_with_existing)
{
	int				new_content = 42;
	int				existing_content = 21;
	t_list2_item	item = (t_list2_item){
		.prev = NULL,
		.next = NULL,
		.content = &existing_content,
	struct s_example	new = (struct s_example){
		.data = 42,
	};
	t_list2			list = (t_list2){
		.first = &item,
		.last = &item,
	struct s_example	existing = (struct s_example){
		.data = 21,
		.conn = {NULL, NULL},
	};
	t_list2_meta		meta = (t_list2_meta){
		.first = &existing.conn,
		.last = &existing.conn,
		.len = 1,
	};

	t_list2_item *res = list2_insert(&list, NULL, &new_content);
	cr_assert_eq(*(int *)res->content, new_content);
	cr_assert_eq(res->prev, NULL);
	cr_assert_eq(res->next, &item);
	cr_assert_eq(item.prev, res);
	cr_assert_eq(item.next, NULL);
	cr_assert_eq(list.first, res);
	cr_assert_eq(list.last, &item);
	cr_assert_eq(list.len, 2);

	list2_remove(&list, &res, NULL);
	list2_insert(&meta, NULL, &new.conn);
	cr_expect_eq(unpack_example(meta.first), &new);
	cr_expect_eq(unpack_example(meta.last), &existing);
	cr_expect_eq(meta.first->prev, NULL);
	cr_expect_eq(meta.first->next, meta.last);
	cr_expect_eq(meta.last->prev, meta.first);
	cr_expect_eq(meta.last->next, NULL);
	cr_expect_eq(meta.len, 2);
}

Test(list2_insert, last)
{
	int				new_content = 42;
	int				existing_content = 21;
	t_list2_item	item = (t_list2_item){
		.prev = NULL,
		.next = NULL,
		.content = &existing_content,
	struct s_example	new = (struct s_example){
		.data = 42,
		.conn = {NULL, NULL},
	};
	t_list2			list = (t_list2){
		.first = &item,
		.last = &item,
	struct s_example	existing_first = (struct s_example){
		.data = 21,
		.conn = {NULL, NULL},
	};
	struct s_example	existing_middle = (struct s_example){
		.data = 22,
		.conn = {NULL, NULL},
	};
	existing_first.conn.next = &existing_middle.conn;
	existing_middle.conn.prev = &existing_first.conn;
	t_list2_meta		meta = (t_list2_meta){
		.first = &existing_first.conn,
		.last = &existing_middle.conn,
		.len = 1,
	};

	t_list2_item *res = list2_insert(&list, &item, &new_content);
	cr_assert_eq(*(int *)res->content, new_content);
	cr_assert_eq(res->prev, &item);
	cr_assert_eq(res->next, NULL);
	cr_assert_eq(item.prev, NULL);
	cr_assert_eq(item.next, res);
	cr_assert_eq(list.first, &item);
	cr_assert_eq(list.last, res);
	cr_assert_eq(list.len, 2);

	list2_remove(&list, &res, NULL);
	list2_insert(&meta, &existing_middle.conn, &new.conn);
	cr_expect_eq(unpack_example(meta.first), &existing_first);
	cr_expect_eq(unpack_example(meta.first->next), &existing_middle);
	cr_expect_eq(unpack_example(meta.first->next->next), &new);
	cr_expect_eq(meta.first->next->prev, meta.first);
	cr_expect_eq(meta.first->next->next->prev, meta.first->next);
	cr_expect_eq(meta.last->prev->next, meta.last);
	cr_expect_eq(meta.len, 2);
}

Test(list2_insert, between)
{
	int				new_content = 42;
	int				existing_content = 21;
	t_list2_item	item1;
	t_list2_item	item2;

	item1 = (t_list2_item){
		.prev = NULL,
		.next = &item2,
		.content = &existing_content,
	struct s_example	new = (struct s_example){
		.data = 42,
		.conn = {NULL, NULL},
	};

	item2 = (t_list2_item){
		.prev = &item1,
		.next = NULL,
		.content = &existing_content,
	struct s_example	existing_first = (struct s_example){
		.data = 42,
		.conn = {NULL, NULL},
	};

	t_list2			list = (t_list2){
		.first = &item1,
		.last = &item2,
		.len = 2,
	struct s_example	existing_last = (struct s_example){
		.data = 42,
		.conn = {NULL, NULL},
	};
	existing_first.conn.next = &existing_last.conn;
	existing_last.conn.prev = &existing_first.conn;
	t_list2_meta		meta = (t_list2_meta){
		.first = &existing_first.conn,
		.last = &existing_last.conn,
		.len = 1,
	};

	t_list2_item *res = list2_insert(&list, &item1, &new_content);
	cr_assert_eq(*(int *)res->content, new_content);
	cr_assert_eq(item1.prev, NULL);
	cr_assert_eq(item1.next, res);
	cr_assert_eq(res->prev, &item1);
	cr_assert_eq(res->next, &item2);
	cr_assert_eq(item2.prev, res);
	cr_assert_eq(item2.next, NULL);
	cr_assert_eq(list.first, &item1);
	cr_assert_eq(list.last, &item2);
	cr_assert_eq(list.len, 3);

	list2_remove(&list, &res, NULL);
	list2_insert(&meta, &existing_first.conn, &new.conn);
	cr_expect_eq(unpack_example(meta.first), &existing_first);
	cr_expect_eq(unpack_example(meta.first->next), &new);
	cr_expect_eq(unpack_example(meta.first->next->next), &existing_last);
	cr_expect_eq(meta.first->next->prev, meta.first);
	cr_expect_eq(meta.first->next->next->prev, meta.first->next);
	cr_expect_eq(meta.last->prev->next, meta.last);
	cr_expect_eq(meta.len, 2);
}

M src/render.c => src/render.c +1 -1
@@ 98,7 98,7 @@ void		render(const struct s_state state)
			box_option(
				rows[row_index][column_index]->name,
				rows[row_index][column_index]->selected,
				state.cursor->content == rows[row_index][column_index],
				unpack_option(state.cursor) == rows[row_index][column_index],
				column_width[column_index]);
			total_column_width += column_width[column_index]
				+ DERIVE_OPTION_PADDING_LEFT + DERIVE_OPTION_PADDING_RIGHT;

M src/state.c => src/state.c +28 -23
@@ 6,11 6,20 @@
#include "error.h"
#include "state.h"

t_error	state_make_options(t_list2 *dest, int argc, char **argv)
struct s_state_option	*unpack_option(struct s_list2_conn *conn)
{
	if (conn == NULL)
		return (NULL);
	return (struct s_state_option *)(
		(char *)conn - offsetof(struct s_state_option, conn));
}

t_error					state_make_options(
							t_list2_meta *dest, int argc, char **argv)
{
	int						i;
	t_list2_item			*prev;
	struct s_state_option	*curr_option;
	t_list2_conn			*prev;
	struct s_state_option	*new_option;

	if (argc <= 1)
		return (errorf("no options to select"));


@@ 18,17 27,13 @@ t_error	state_make_options(t_list2 *dest, int argc, char **argv)
	prev = NULL;
	while (i < argc)
	{
		curr_option = ft_memalloc(sizeof(*curr_option));
		if (curr_option == NULL)
		new_option = ft_memalloc(sizeof(*new_option));
		if (new_option == NULL)
			return (errorf("failed to allocate option"));
		curr_option->name = argv[i];
		curr_option->selected = false;
		prev = list2_insert(dest, prev, curr_option);
		if (prev == NULL)
		{
			ft_memdel((void **)&curr_option);
			return (errorf("failed to insert option"));
		}
		new_option->name = argv[i];
		new_option->selected = false;
		list2_insert(dest, prev, &new_option->conn);
		prev = &new_option->conn;
		i++;
	}
	return (ERROR_NULL);


@@ 36,22 41,22 @@ t_error	state_make_options(t_list2 *dest, int argc, char **argv)

void	state_debug(const struct s_state state)
{
	t_list2_item	*curr;
	struct s_state_option	*option;

	ft_dprintf(STDERR_FILENO,
		"struct s_state {\n"
		"  .options = {\n");
	curr = state.options.first;
	while (curr != NULL)
	option = unpack_option(state.options.first);
	while (option != NULL)
	{
		ft_dprintf(STDERR_FILENO,
			"    %p {\"%s\", .selected = %s},%s\n",
			curr,
			((struct s_state_option *)curr->content)->name,
			((struct s_state_option *)curr->content)->selected
			"    %p (%+d) {\"%s\", .selected = %s},%s\n",
			option, (char *)&option->conn - (char *)option,
			option->name,
			option->selected
				? "true" : "false",
			state.cursor == curr ? " // focused" : "");
		curr = curr->next;
			state.cursor == &option->conn ? " // focused" : "");
		option = unpack_option(option->conn.next);
	}
	ft_dprintf(STDERR_FILENO,
		"  },\n"


@@ 61,7 66,7 @@ void	state_debug(const struct s_state state)
		"    .columns = %u,\n"
		"  },\n"
		"}\n",
		state.cursor, ((struct s_state_option *)state.cursor->content)->name,
		state.cursor, unpack_option(state.cursor)->name,
		state.terminal.rows,
		state.terminal.columns);
}