~rcr/rirc

2170399f9640163f3b8f817765630ec6db0d6e9b — Richard Robbins 3 months ago bb722e0
remove mutable input history
3 files changed, 92 insertions(+), 117 deletions(-)

M src/components/input.c
M src/components/input.h
M test/components/input.c
M src/components/input.c => src/components/input.c +25 -65
@@ 13,6 13,8 @@
#error INPUT_HIST_MAX must be a power of 2
#endif

#define INPUT_HIST_LINE(I, X) ((I)->hist.ptrs[INPUT_MASK((X))])

static char *input_text_copy(struct input*);
static int input_text_isfull(struct input*);
static int input_text_iszero(struct input*);


@@ 30,10 32,8 @@ input_init(struct input *inp)
void
input_free(struct input *inp)
{
	free(inp->hist.save);

	while (inp->hist.tail != inp->hist.head)
		free(inp->hist.ptrs[INPUT_MASK(inp->hist.tail++)]);
		free(INPUT_HIST_LINE(inp, inp->hist.tail++));
}

int


@@ 42,7 42,7 @@ input_cursor_back(struct input *inp)
	if (inp->head == 0)
		return 0;

	inp->text[--inp->tail] = inp->text[--inp->head];
	inp->buf[--inp->tail] = inp->buf[--inp->head];

	return 1;
}


@@ 53,7 53,7 @@ input_cursor_forw(struct input *inp)
	if (inp->tail == INPUT_LEN_MAX)
		return 0;

	inp->text[inp->head++] = inp->text[inp->tail++];
	inp->buf[inp->head++] = inp->buf[inp->tail++];

	return 1;
}


@@ 89,10 89,10 @@ input_insert(struct input *inp, const char *c, size_t count)
	while (!input_text_isfull(inp) && count--) {

		if (iscntrl(*c))
			inp->text[inp->head++] = ' ';
			inp->buf[inp->head++] = ' ';

		if (isprint(*c))
			inp->text[inp->head++] = *c;
			inp->buf[inp->head++] = *c;

		c++;
	}


@@ 106,11 106,7 @@ input_reset(struct input *inp)
	if (input_text_iszero(inp))
		return 0;

	free(inp->hist.save);

	inp->hist.current = inp->hist.head;
	inp->hist.save = NULL;

	inp->head = 0;
	inp->tail = INPUT_LEN_MAX;
	inp->window = 0;


@@ 134,17 130,17 @@ input_complete(struct input *inp, f_completion_cb cb)
	head = inp->head;
	tail = inp->tail;

	while (head && inp->text[head - 1] != ' ')
	while (head && inp->buf[head - 1] != ' ')
		head--;

	if (inp->text[head] == ' ')
	if (inp->buf[head] == ' ')
		return 0;

	while (tail < INPUT_LEN_MAX && inp->text[tail] != ' ')
	while (tail < INPUT_LEN_MAX && inp->buf[tail] != ' ')
		tail++;

	ret = (*cb)(
		(inp->text + head),
		(inp->buf + head),
		(inp->head - head - inp->tail + tail),
		(INPUT_LEN_MAX - input_text_size(inp)),
		(head == 0));


@@ 165,17 161,10 @@ input_hist_back(struct input *inp)
	if (input_hist_size(inp) == 0 || inp->hist.current == inp->hist.tail)
		return 0;

	if (inp->hist.current == inp->hist.head) {
		inp->hist.save = input_text_copy(inp);
	} else {
		free(inp->hist.ptrs[INPUT_MASK(inp->hist.current)]);
		inp->hist.ptrs[INPUT_MASK(inp->hist.current)] = input_text_copy(inp);
	}

	inp->hist.current--;

	len = strlen(inp->hist.ptrs[INPUT_MASK(inp->hist.current)]);
	memcpy(inp->text, inp->hist.ptrs[INPUT_MASK(inp->hist.current)], len);
	len = strlen(INPUT_HIST_LINE(inp, inp->hist.current));
	memcpy(inp->buf, INPUT_HIST_LINE(inp, inp->hist.current), len);

	inp->head = len;
	inp->tail = INPUT_LEN_MAX;


@@ 186,30 175,18 @@ input_hist_back(struct input *inp)
int
input_hist_forw(struct input *inp)
{
	char *str;
	size_t len = 0;
	size_t len;

	if (input_hist_size(inp) == 0 || inp->hist.current == inp->hist.head)
		return 0;

	free(inp->hist.ptrs[INPUT_MASK(inp->hist.current)]);
	inp->hist.ptrs[INPUT_MASK(inp->hist.current)] = input_text_copy(inp);

	inp->hist.current++;

	if (inp->hist.current == inp->hist.head)
		str = inp->hist.save;
	else
		str = inp->hist.ptrs[INPUT_MASK(inp->hist.current)];

	if (str) {
		len = strlen(str);
		memcpy(inp->text, str, len);
	}

	if (inp->hist.current == inp->hist.head) {
		free(inp->hist.save);
		inp->hist.save = NULL;
		len = 0;
	} else {
		len = strlen(INPUT_HIST_LINE(inp, inp->hist.current));
		memcpy(inp->buf, INPUT_HIST_LINE(inp, inp->hist.current), len);
	}

	inp->head = len;


@@ 221,32 198,15 @@ input_hist_forw(struct input *inp)
int
input_hist_push(struct input *inp)
{
	char *save;
	char *hist;

	if ((save = input_text_copy(inp)) == NULL)
	if ((hist = input_text_copy(inp)) == NULL)
		return 0;

	if (inp->hist.current < inp->hist.head) {

		uint16_t i;

		free(inp->hist.ptrs[INPUT_MASK(inp->hist.current)]);

		for (i = inp->hist.current; i < inp->hist.head - 1; i++)
			inp->hist.ptrs[INPUT_MASK(i)] = inp->hist.ptrs[INPUT_MASK(i + 1)];
	if (input_hist_size(inp) == INPUT_HIST_MAX)
		free(INPUT_HIST_LINE(inp, inp->hist.tail++));

		inp->hist.current = i;
		inp->hist.ptrs[INPUT_MASK(inp->hist.current)] = save;

	} else if (input_hist_size(inp) == INPUT_HIST_MAX) {

		free(inp->hist.ptrs[INPUT_MASK(inp->hist.tail++)]);
		inp->hist.ptrs[INPUT_MASK(inp->hist.head++)] = save;

	} else {

		inp->hist.ptrs[INPUT_MASK(inp->hist.head++)] = save;
	}
	INPUT_HIST_LINE(inp, inp->hist.head++) = hist;

	return input_reset(inp);
}


@@ 289,14 249,14 @@ input_write(struct input *inp, char *buf, uint16_t max, uint16_t pos)
	uint16_t buf_len = 0;

	while (max > 1 && pos < inp->head) {
		buf[buf_len++] = inp->text[pos++];
		buf[buf_len++] = inp->buf[pos++];
		max--;
	}

	pos = inp->tail;

	while (max > 1 && pos < INPUT_LEN_MAX) {
		buf[buf_len++] = inp->text[pos++];
		buf[buf_len++] = inp->buf[pos++];
		max--;
	}


M src/components/input.h => src/components/input.h +7 -4
@@ 18,13 18,17 @@

/* 410 max characters for input should be sufficient given
 * rfc2812 maximum length of 50 characters for channel names,
 * plus 50 characters for additional message formatting */
 * plus 50 characters for additional message formatting.
 *
 * Precluded in tests */
#ifndef INPUT_LEN_MAX
#define INPUT_LEN_MAX 410
#endif

/* Number of history lines to keep for input. For proper
 * ring buffer masking this must be a power of 2 */
 * ring buffer masking this must be a power of 2.
 *
 * Precluded in tests */
#ifndef INPUT_HIST_MAX
#define INPUT_HIST_MAX 16
#endif


@@ 39,10 43,9 @@ typedef uint16_t (*f_completion_cb)(

struct input
{
	char text[INPUT_LEN_MAX];
	char buf[INPUT_LEN_MAX];
	struct {
		char *ptrs[INPUT_HIST_MAX];
		char *save;
		uint16_t current; /* Ring buffer current entry */
		uint16_t head;    /* Ring buffer head */
		uint16_t tail;    /* Ring buffer tail */

M test/components/input.c => test/components/input.c +60 -48
@@ 161,7 161,7 @@ test_input_del(void)

	input_init(&inp);

	/* Deleting back/forw on empty input */
	/* Deleting back/forward on empty input */
	CHECK_INPUT_WRITE(&inp, "");
	assert_eq(input_delete_back(&inp), 0);
	assert_eq(input_delete_forw(&inp), 0);


@@ 220,24 220,23 @@ test_input_hist(void)
	assert_eq(input_insert(&inp, "444", 3), 1);
	assert_eq(input_hist_push(&inp), 1);

#define INP_HIST_CURR(I) ((I).hist.ptrs[INPUT_MASK((I).hist.current)])
#define INP_HIST_HEAD(I) ((I).hist.ptrs[INPUT_MASK((I).hist.head - 1)])
#define INP_HIST_TAIL(I) ((I).hist.ptrs[INPUT_MASK((I).hist.tail)])

	assert_strcmp(INP_HIST_HEAD(inp), "444");
	assert_strcmp(INP_HIST_TAIL(inp), "111");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 1), "444");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.tail), "111");

	/* Test pushing after INPUT_HIST_MAX frees the tail */
	assert_eq(input_insert(&inp, "555", 3), 1);
	assert_eq(input_hist_push(&inp), 1);

	assert_strcmp(INP_HIST_HEAD(inp), "555");
	assert_strcmp(INP_HIST_TAIL(inp), "222");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 1), "555");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.tail), "222");

	/* Test scrolling back saves the current working input */
	assert_eq(input_insert(&inp, "000", 3), 1);
	/* Test scrolling backwards */
	assert_eq(input_reset(&inp), 0);
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 4), "222");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 3), "333");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 2), "444");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 1), "555");

	/* Test scrolling back to tail */
	assert_eq(input_hist_back(&inp), 1);
	CHECK_INPUT_WRITE(&inp, "555");
	assert_eq(input_hist_back(&inp), 1);


@@ 247,9 246,9 @@ test_input_hist(void)
	assert_eq(input_hist_back(&inp), 1);
	CHECK_INPUT_WRITE(&inp, "222");
	assert_eq(input_hist_back(&inp), 0);
	assert_strcmp(inp.hist.save, "000");
	CHECK_INPUT_WRITE(&inp, "222");

	/* Test scrolling forw to head */
	/* Test scrolling forwards */
	assert_eq(input_hist_forw(&inp), 1);
	CHECK_INPUT_WRITE(&inp, "333");
	assert_eq(input_hist_forw(&inp), 1);


@@ 257,38 256,51 @@ test_input_hist(void)
	assert_eq(input_hist_forw(&inp), 1);
	CHECK_INPUT_WRITE(&inp, "555");
	assert_eq(input_hist_forw(&inp), 1);
	CHECK_INPUT_WRITE(&inp, "000");
	CHECK_INPUT_WRITE(&inp, "");
	assert_eq(input_hist_forw(&inp), 0);
	CHECK_INPUT_WRITE(&inp, "");

	assert_strcmp(INP_HIST_HEAD(inp), "555");
	assert_strcmp(INP_HIST_TAIL(inp), "222");
	/* Test replaying history */
	assert_eq(input_reset(&inp), 0);
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 4), "222");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 3), "333");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 2), "444");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 1), "555");

	/* Test replaying hist head */
	/* Test replaying history from head */
	assert_eq(input_hist_back(&inp), 1);
	assert_eq(input_hist_push(&inp), 1);
	assert_strcmp(INP_HIST_HEAD(inp), "555");
	assert_strcmp(INP_HIST_TAIL(inp), "222");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 4), "333");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 3), "444");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 2), "555");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 1), "555");

	/* Test replaying hist middle */
	/* Test replaying history from tail */
	assert_eq(input_hist_back(&inp), 1);
	assert_eq(input_hist_back(&inp), 1);
	assert_eq(input_hist_back(&inp), 1);
	assert_eq(input_hist_back(&inp), 1);
	assert_eq(input_hist_back(&inp), 0);
	assert_eq(input_hist_push(&inp), 1);
	assert_strcmp(INP_HIST_HEAD(inp), "444");
	assert_strcmp(INP_HIST_TAIL(inp), "222");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 4), "444");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 3), "555");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 2), "555");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 1), "333");

	/* Test replaying hist tail */
	/* Test replaying history with edit */
	assert_eq(input_hist_back(&inp), 1);
	assert_eq(input_hist_back(&inp), 1);
	assert_eq(input_hist_back(&inp), 1);
	assert_eq(input_hist_back(&inp), 1);
	assert_eq(input_hist_back(&inp), 0);
	assert_eq(input_insert(&inp, "xxx", 3), 1);
	assert_eq(input_hist_push(&inp), 1);
	assert_strcmp(INP_HIST_HEAD(inp), "222");
	assert_strcmp(INP_HIST_TAIL(inp), "333");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 4), "555");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 3), "555");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 2), "333");
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 1), "555xxx");

#undef INP_HIST_CURR
#undef INP_HIST_HEAD
#undef INP_HIST_TAIL
	/* Test pushing resets scrollback */
	assert_eq(input_hist_back(&inp), 1);
	assert_strcmp(INPUT_HIST_LINE(&inp, inp.hist.head - 1), "555xxx");

	input_free(&inp);
}


@@ 361,7 373,7 @@ test_input_frame(void)
	assert_eq(input_cursor_back(&inp), 1);
	CHECK_INPUT_FRAME(&inp, "12345", 6, 0);

	/* Test cursor forw keeps cursor in view */
	/* Test cursor forward keeps cursor in view */
	assert_eq(input_cursor_forw(&inp), 1);
	assert_eq(input_cursor_forw(&inp), 1);
	assert_eq(input_cursor_forw(&inp), 1);


@@ 439,26 451,26 @@ test_input_complete(void)
	/* Test: ` abc `
	 *            ^ */
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(inp.text[inp.head], ' ');
	assert_eq(inp.buf[inp.head], ' ');
	assert_eq(input_complete(&inp, completion_rot1), 1);
	assert_eq(inp.text[inp.head], ' ');
	assert_eq(inp.buf[inp.head], ' ');
	CHECK_INPUT_WRITE(&inp, " bcd ");

	/* Test: ` bcd `
	 *           ^ */
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(inp.text[inp.head], 'd');
	assert_eq(inp.buf[inp.head], 'd');
	assert_eq(input_complete(&inp, completion_rot1), 1);
	assert_eq(inp.text[inp.head], ' ');
	assert_eq(inp.buf[inp.head], ' ');
	CHECK_INPUT_WRITE(&inp, " cde ");

	/* Test: ` cde `
	 *          ^ */
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(inp.text[inp.head], 'd');
	assert_eq(inp.buf[inp.head], 'd');
	assert_eq(input_complete(&inp, completion_rot1), 1);
	assert_eq(inp.text[inp.head], ' ');
	assert_eq(inp.buf[inp.head], ' ');
	CHECK_INPUT_WRITE(&inp, " def ");

	/* Test: ` def `


@@ 466,9 478,9 @@ test_input_complete(void)
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(inp.text[inp.head], 'd');
	assert_eq(inp.buf[inp.head], 'd');
	assert_eq(input_complete(&inp, completion_rot1), 1);
	assert_eq(inp.text[inp.head], ' ');
	assert_eq(inp.buf[inp.head], ' ');
	CHECK_INPUT_WRITE(&inp, " efg ");

	/* Test: ` efg `


@@ 477,9 489,9 @@ test_input_complete(void)
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(inp.text[inp.head], ' ');
	assert_eq(inp.buf[inp.head], ' ');
	assert_eq(input_complete(&inp, completion_rot1), 0);
	assert_eq(inp.text[inp.head], ' ');
	assert_eq(inp.buf[inp.head], ' ');
	CHECK_INPUT_WRITE(&inp, " efg ");
	assert_eq(input_reset(&inp), 1);



@@ 491,9 503,9 @@ test_input_complete(void)
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(inp.text[inp.head], 'x');
	assert_eq(inp.buf[inp.head], 'x');
	assert_eq(input_complete(&inp, completion_rot1), 1);
	assert_eq(inp.text[inp.tail], ' ');
	assert_eq(inp.buf[inp.tail], ' ');
	CHECK_INPUT_WRITE(&inp, "y!! abc ");
	assert_eq(input_reset(&inp), 1);



@@ 503,10 515,10 @@ test_input_complete(void)
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(inp.text[inp.head], 'c');
	assert_eq(inp.buf[inp.head], 'c');
	assert_eq(input_complete(&inp, completion_l), 1);
	CHECK_INPUT_WRITE(&inp, " xyxyxy ab");
	assert_eq(inp.text[inp.tail], ' '); /* points to 'c' */
	assert_eq(inp.buf[inp.tail], ' '); /* points to 'c' */
	assert_eq(input_reset(&inp), 1);

	/* Test replacement word shorter */


@@ 516,10 528,10 @@ test_input_complete(void)
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(input_cursor_back(&inp), 1);
	assert_eq(inp.text[inp.head], 'c');
	assert_eq(inp.buf[inp.head], 'c');
	assert_eq(input_complete(&inp, completion_s), 1);
	CHECK_INPUT_WRITE(&inp, " z ab ");
	assert_eq(inp.text[inp.tail], ' ');
	assert_eq(inp.buf[inp.tail], ' ');
	assert_eq(input_reset(&inp), 1);

	/* Test writing up to max chars */