~jstnas/tt

9da5714dc2ab36ec0de7f04454c7b6e393594d34 — Justinas Grigas 1 year, 19 days ago 27ae122 master
feat(twin): implemented grapheme

also tidied up.
3 files changed, 208 insertions(+), 189 deletions(-)

M README.md
M twin.c
M twin.h
M README.md => README.md +1 -0
@@ 11,6 11,7 @@ on the keyboard.

- C99
- ncurses
- [libgrapheme](https://libs.suckless.org/libgrapheme/)

## References


M twin.c => twin.c +207 -188
@@ 8,23 8,28 @@
#include "result.h"
#include "timer.h"
#include <curses.h>
#include <stdio.h>
#include <stdlib.h>
#include <grapheme.h>

/* checks if pointer is a valid wide character */
static inline int is_wc(char **);
/* returns the attributes to draw the current characters with */
static inline int get_attr(char **, int);
static inline char *get_wc(char **);
static size_t word_length(list_node_t **);
static double twin_timer_diff(Timer **);
static int twin_completed(TWindow *);
static size_t twin_remove_line(TWindow *);
static void twin_add_key(TWindow *, char);
static void twin_backspace(TWindow *);
static void twin_clear(TWindow *);
static void twin_draw_sentences(TWindow *);
static bool twin_completed(TWindow *);
static inline void twin_title_time(TWindow *);
static inline void twin_title_words(TWindow *);
static void twin_space(TWindow *);
static void twin_title(TWindow *);
static double twin_timer_diff(Timer **);
static bool word_incorrect(list_node_t **);
static void twin_title_time(TWindow *);
static void twin_title_words(TWindow *);
static void twin_update_position(TWindow *);

static int _get_attr(char **, int);
static int _get_wc(char **);
static int _is_wc(char **);
static int _word_incorrect(list_node_t **);
static size_t _word_bytes(list_node_t **);

TWindow *
twin_new(Result *result)


@@ 48,15 53,6 @@ twin_new(Result *result)
	return twin;
}

static void
twin_clear(TWindow *twin)
{
	list_destroy(twin->sentences[0]);
	list_destroy(twin->sentences[1]);
	free(twin->timers[0]);
	free(twin->timers[1]);
}

void
twin_free(TWindow *twin)
{


@@ 110,95 106,6 @@ twin_update(TWindow *twin)
	return 0;
}

static void
twin_update_position(TWindow *twin)
{
	twin->position = (Point) {
		0, 0
	};
	list_iterator_t *it[2] = {
		list_iterator_new(twin->sentences[0], LIST_HEAD),
		list_iterator_new(twin->sentences[1], LIST_HEAD),
	};
	list_node_t *word[2] = {
		list_iterator_next(it[0]),
		list_iterator_next(it[1]),
	};
	while (word[0]) {
		size_t len = word_length(word);
		/* move onto next line if word is too long */
		if (twin->position.x + len >= WIDTH) {
			twin->position.x = 0;
			++twin->position.y;
		}
		/* if there are more words, account for space between them */
		if (word[0]->next)
			twin->position.x += len + 1;
		/* otherwise, move the cursor by input characters */
		else
			twin->position.x += strlen(twin->sentences[0]->tail->val);
		word[0] = list_iterator_next(it[0]);
		word[1] = list_iterator_next(it[1]);
	}
	list_iterator_destroy(it[0]);
	list_iterator_destroy(it[1]);
	mvprintw(2, 0, "%u %u\n", twin->position.x, twin->position.y);
}

static void
twin_space(TWindow *twin)
{
	sentence_add_word(twin->sentences[0], "");
	/* TODO: remove chars when the word is typed incorrectly */
	++twin->result->chars[0];
	twin_update_position(twin);
	if (twin->position.y >= 2) {
		twin_remove_line(twin);
		twin_update_position(twin);
	}
	/* TODO: remove line instead of going to 3rd line */
}

static void
twin_add_key(TWindow *twin, char key)
{
	/* prevent wrapping word onto next line */
	if (twin->position.x >= WIDTH - 1)
		return;
	if (sentence_add_key(twin->sentences[0], key))
		return;
	++twin->result->chars[0];
	/* update cursor position */
	/* wrap onto next line if at the end of line */
	if (twin->position.x >= WIDTH - 1) {
		++twin->position.y;
		list_node_t *words[2] = {twin->sentences[0]->tail, twin->sentences[1]->tail};
		twin->position.x = word_length(words);
		return;
	}
	/* otherwise move right */
	++twin->position.x;
}

static inline void
twin_backspace(TWindow *twin)
{
	switch (sentence_remove_key(twin->sentences[0])) {
	case 1: /* skip if nothing to do */
		return;
	case 0:
		--twin->position.x;
		break;
	case 2:
		if (twin->position.x == 0 && twin->position.y == 0)
			return;
		twin_update_position(twin);
		break;
	}
	--twin->result->chars[0];
	wclear(twin->panel->window);
}

int
twin_input(TWindow *twin)
{


@@ 236,45 143,31 @@ twin_input(TWindow *twin)
	return -1;
}

static inline int
is_wc(char **chars)
{
	return ((chars[0] && *chars[0]) || (chars[1] && *chars[1]));
}

static inline int
get_attr(char **chars, int is_last)
{
	/* TODO: figure out optimal order and conditions of the if statements */
	/* bg if haven't reached this point */
	if (!chars[0])
		return COLOR_PAIR(TPAIR_BG);
	if (!*chars[0] && is_last)
		return COLOR_PAIR(TPAIR_BG);
	/* fg if characters match */
	if (chars[1] && *chars[0] == *chars[1])
		return COLOR_PAIR(TPAIR_FG);
	/* err otherwise */
	if (!*chars[0])
		return COLOR_PAIR(TPAIR_ERR);
	return COLOR_PAIR(TPAIR_ERR) | A_BOLD;
}

static inline char *
get_wc(char **chars)
static double
twin_timer_diff(Timer **timers)
{
	if (!chars[1] || !*chars[1])
		return chars[0];
	return chars[1];
	if (!timers[1])
		timers[1] = malloc(sizeof(Timer));
	timer_now(timers[1]);
	return timer_diff(timers[0], timers[1]);
}

static size_t
word_length(list_node_t **word)
static int
twin_completed(TWindow *twin)
{
	size_t a = word[0] ? grapheme_next_word_break_utf8(word[0]->val, SIZE_MAX) : 0,
	       b = word[1] ? grapheme_next_word_break_utf8(word[1]->val, SIZE_MAX) : 0;
	mvprintw(5, 0, "length %lu %lu\n", a, b);
	return a > b ? a : b;
	/* TODO: support completig on last character */
	switch (twin->result->mode) {
	case MODE_TIME:
		if (!twin->timers[1])
			return false;
		return timer_diff(twin->timers[0], twin->timers[1]) >= twin->result->length;
	case MODE_WORDS:
	case MODE_QUOTE:
		return twin->sentences[0]->len > twin->sentences[1]->len;
	case MODE_ZEN:
		return false;
	}
	return false;
}

static size_t


@@ 285,7 178,7 @@ twin_remove_line(TWindow *twin)
	do {
		word[0] = list_lpop(twin->sentences[0]);
		word[1] = list_lpop(twin->sentences[1]);
		line_length += word_length(word);
		line_length += _word_bytes(word);
		if (word[0]) {
			free(word[0]->val);
			free(word[0]);


@@ 294,7 187,7 @@ twin_remove_line(TWindow *twin)
			free(word[1]->val);
			free(word[1]);
		}
		next_word_length = word_length((list_node_t *[2]) {
		next_word_length = _word_bytes((list_node_t *[2]) {
			twin->sentences[0]->head, twin->sentences[1]->head
		});
		if (line_length + next_word_length >= WIDTH - 1)


@@ 306,13 199,63 @@ twin_remove_line(TWindow *twin)
}

static void
twin_add_key(TWindow *twin, char key)
{
	/* prevent wrapping word onto next line */
	if (twin->position.x >= WIDTH - 1)
		return;
	if (sentence_add_key(twin->sentences[0], key))
		return;
	++twin->result->chars[0];
	/* update cursor position */
	list_node_t *words[2] = {twin->sentences[0]->tail, twin->sentences[1]->tail};
	/* wrap onto next line if at the end of line */
	if (twin->position.x >= WIDTH - 1) {
		++twin->position.y;
		twin->position.x = _word_bytes(words);
		return;
	}
	/* otherwise move right */
	twin->position.x += 2;
}

static void
twin_backspace(TWindow *twin)
{
	switch (sentence_remove_key(twin->sentences[0])) {
	case 1: /* skip if nothing to do */
		return;
	case 0:
		--twin->position.x;
		break;
	case 2:
		if (twin->position.x == 0 && twin->position.y == 0)
			return;
		twin_update_position(twin);
		break;
	}
	--twin->result->chars[0];
	wclear(twin->panel->window);
}

static void
twin_clear(TWindow *twin)
{
	list_destroy(twin->sentences[0]);
	list_destroy(twin->sentences[1]);
	free(twin->timers[0]);
	free(twin->timers[1]);
}

static void
twin_draw_sentences(TWindow *twin)
{
	/* TODO: draw wrongly typed letter underneath mistake */
	/* TODO: handle newline characters */
	WINDOW *win = twin->panel->window;
	char *chars[2] = {NULL, NULL}, *c = NULL;
	int attr;
	char *chars[2] = {NULL, NULL};
	int attr, c, last;
	size_t ret[2];
	Point position = {0, TWIN_STATUS_HEIGHT};
	wmove(win, position.y, position.x);
	list_iterator_t *it[2] = {


@@ 328,29 271,29 @@ twin_draw_sentences(TWindow *twin)
		/* draw word */
		chars[0] = word[0] ? word[0]->val : NULL;
		chars[1] = word[1] ? word[1]->val : NULL;
		while (is_wc(chars)) {
		while (_is_wc(chars)) {
			/* add the character */
			c = get_wc(chars);
			bool last = (word[0]) ? (word[0]->next == NULL) : false;
			attr = get_attr(chars, last);
			if (word_incorrect(word))
			c = _get_wc(chars);
			ret[0] = grapheme_next_character_break_utf8(chars[0], SIZE_MAX);
			ret[1] = grapheme_next_character_break_utf8(chars[1], SIZE_MAX);
			last = (word[0]) ? (word[0]->next == NULL) : 0;
			attr = _get_attr(chars, last);
			if (_word_incorrect(word))
				attr |= A_UNDERLINE;
			wattron(win, attr);
			waddnstr(win, c, 1);
			waddnstr(win, chars[c], ret[c]);
			wattroff(win, attr);
			/* advance characters */
			if (chars[0] && *chars[0])
				++chars[0];
			if (chars[1] && *chars[1])
				++chars[1];
			chars[0] += ret[0];
			chars[1] += ret[1];
		}
		position.x += word_length(word);
		position.x += _word_bytes(word);
		/* advance to next word */
		word[0] = list_iterator_next(it[0]);
		word[1] = list_iterator_next(it[1]);
		/* add seperator */
		if (word[0] || word[1]) {
			if (position.x + word_length(word) >= WIDTH - 1) {
			if (position.x + _word_bytes(word) >= WIDTH - 1) {
				/* don't draw past last line */
				if (position.y >= HEIGHT + TWIN_STATUS_HEIGHT)
					break;


@@ 369,21 312,33 @@ twin_draw_sentences(TWindow *twin)
	list_iterator_destroy(it[1]);
}

static bool
twin_completed(TWindow *twin)
static void
twin_space(TWindow *twin)
{
	sentence_add_word(twin->sentences[0], "");
	/* TODO: remove chars when the word is typed incorrectly */
	++twin->result->chars[0];
	twin_update_position(twin);
	if (twin->position.y >= 2) {
		twin_remove_line(twin);
		twin_update_position(twin);
	}
	/* TODO: remove line instead of going to 3rd line */
}

static void
twin_title(TWindow *twin)
{
	switch (twin->result->mode) {
	case MODE_TIME:
		if (!twin->timers[1])
			return false;
		return timer_diff(twin->timers[0], twin->timers[1]) >= twin->result->length;
	case MODE_WORDS:
		twin_title_time(twin);
		break;
	case MODE_QUOTE:
		return twin->sentences[0]->len > twin->sentences[1]->len;
	case MODE_WORDS:
	case MODE_ZEN:
		return false;
		twin_title_words(twin);
		break;
	}
	return false;
}

static inline void


@@ 415,39 370,84 @@ twin_title_words(TWindow *twin)
		sprintf(twin->panel->title, "%zu", len[0]);
}

/* Recalculate cursor position */
static void
twin_title(TWindow *twin)
twin_update_position(TWindow *twin)
{
	switch (twin->result->mode) {
	case MODE_TIME:
		twin_title_time(twin);
		break;
	case MODE_QUOTE:
	case MODE_WORDS:
	case MODE_ZEN:
		twin_title_words(twin);
		break;
	twin->position = (Point) {
		0, 0
	};
	list_iterator_t *it[2] = {
		list_iterator_new(twin->sentences[0], LIST_HEAD),
		list_iterator_new(twin->sentences[1], LIST_HEAD),
	};
	list_node_t *word[2] = {
		list_iterator_next(it[0]),
		list_iterator_next(it[1]),
	};
	size_t len;
	while (word[0]) {
		len = _word_bytes(word);
		/* move onto next line if word is too long */
		if (twin->position.x + len >= WIDTH) {
			twin->position.x = 0;
			++twin->position.y;
		}
		/* if there are more words, account for space between them */
		if (word[0]->next)
			twin->position.x += len + 1;
		/* otherwise, move the cursor by input characters */
		//else
		//	twin->position.x += grapheme_next_word_break_utf8(twin->sentences[0]->tail->val, SIZE_MAX);
		word[0] = list_iterator_next(it[0]);
		word[1] = list_iterator_next(it[1]);
	}
	list_iterator_destroy(it[0]);
	list_iterator_destroy(it[1]);
	mvprintw(2, 0, "%u %u\n", twin->position.x, twin->position.y);
}

static double
twin_timer_diff(Timer **timers)
static inline int
_get_attr(char **chars, int is_last)
{
	if (!timers[1])
		timers[1] = malloc(sizeof(Timer));
	timer_now(timers[1]);
	return timer_diff(timers[0], timers[1]);
	/* TODO: figure out optimal order and conditions of the if statements */
	/* bg if haven't reached this point */
	if (!chars[0])
		return COLOR_PAIR(TPAIR_BG);
	if (!*chars[0] && is_last)
		return COLOR_PAIR(TPAIR_BG);
	/* fg if characters match */
	if (chars[1] && *chars[0] == *chars[1])
		return COLOR_PAIR(TPAIR_FG);
	/* err otherwise */
	if (!*chars[0])
		return COLOR_PAIR(TPAIR_ERR);
	return COLOR_PAIR(TPAIR_ERR) | A_BOLD;
}

static bool
word_incorrect(list_node_t **words)
static inline int
_get_wc(char **chars)
{
	if (!chars[1] || !*chars[1])
		return 0;
	return 1;
}

static inline int
_is_wc(char **chars)
{
	return ((chars[0] && *chars[0]) || (chars[1] && *chars[1]));
}

static int
_word_incorrect(list_node_t **words)
{
	if (!words[0] || !words[1])
		return false;
	if (!strcmp(words[0]->val, words[1]->val))
		return false;
	char *wc[2] = {words[0]->val, words[1]->val};
	while (is_wc(wc)) {
	while (_is_wc(wc)) {
		if (wc[0] && *wc[0] && wc[1] && *wc[1] && *wc[0] != *wc[1])
			return true;
		if (wc[0] && *wc[0])


@@ 457,3 457,22 @@ word_incorrect(list_node_t **words)
	}
	return false;
}

/* Return the length of the longer word in bytes */
static size_t
_word_bytes(list_node_t **word)
{
	size_t length = 0, ret[2];
	char *chars[] = {
		word[0] ? word[0]->val : NULL,
		word[1] ? word[1]->val : NULL,
	};
	while (_is_wc(chars)) {
		ret[0] = grapheme_next_character_break_utf8(chars[0], SIZE_MAX);
		ret[1] = grapheme_next_character_break_utf8(chars[1], SIZE_MAX);
		length += ret[0] > ret[1] ? ret[0] : ret[1];
		chars[0] += ret[0];
		chars[1] += ret[1];
	}
	return length;
}

M twin.h => twin.h +0 -1
@@ 3,7 3,6 @@
#ifndef T_WINDOW_H
#define T_WINDOW_H

#include "config.h"
#include "panel.h"
#include "result.h"
#include "sentence.h"