typetest

Typing speed benchmarker
git clone https://git.sinitax.com/sinitax/typetest
Log | Files | Refs | LICENSE | sfeed.txt

typetest.c (4765B)


      1 #include <unistd.h>
      2 #include <err.h>
      3 #include <termio.h>
      4 #include <stdarg.h>
      5 #include <time.h>
      6 #include <stdio.h>
      7 #include <string.h>
      8 #include <stdbool.h>
      9 #include <stdint.h>
     10 #include <stdlib.h>
     11 
     12 #define LINTERP(a,b,v) ((a) * (v) + (b) * (1 - (v)))
     13 
     14 enum {
     15 	C_EOF = 0x03, /* CTRL+C */
     16 	C_EOT = 0x04, /* CTRL+D */
     17 	C_DEL = 0x7f, /* BKSP */
     18 	C_SKIP = 0x17, /* CTRL+W */
     19 };
     20 
     21 enum {
     22 	STYLE_END = -1,
     23 
     24 	STYLE_RESET = 0,
     25 
     26 	STYLE_BOLD = 1,
     27 	STYLE_FAINT = 2,
     28 	STYLE_ITALIC = 3,
     29 	STYLE_UNDERLINE = 4,
     30 	STYLE_BLINK = 5,
     31 	STYLE_INVERT = 7,
     32 	STYLE_CROSSED_OUT = 9,
     33 
     34 	STYLE_BLACK_FG = 30,
     35 	STYLE_RED_FG = 31,
     36 	STYLE_GREEN_FG = 32,
     37 	STYLE_YELLOW_FG = 33,
     38 	STYLE_BLUE_FG = 34,
     39 	STYLE_MAGENTA_FG = 35,
     40 	STYLE_CYAN_FG = 36,
     41 	STYLE_WHITE_FG = 37,
     42 	STYLE_GREY_FG = 90,
     43 };
     44 
     45 static const int style_wrong[] = { STYLE_RED_FG, STYLE_BOLD, STYLE_END };
     46 static const int style_right[] = { STYLE_GREEN_FG, STYLE_END };
     47 static const int style_reset[] = { STYLE_RESET, STYLE_END };
     48 static const int style_preview[] = { STYLE_GREY_FG, STYLE_END };
     49 
     50 static struct termios old_termios;
     51 
     52 static int typed = 0;
     53 static float gcps;
     54 
     55 void
     56 print_style(const int *props)
     57 {
     58 	const int *iter;
     59 
     60 	if (!props || *props == STYLE_END) return;
     61 
     62 	printf("\x1b[");
     63 	for (iter = props; *iter != STYLE_END; iter++)
     64 		printf("%i%c", *iter, (iter[1] == STYLE_END) ? 'm' : ';');
     65 }
     66 
     67 void
     68 time_sub(struct timespec *new, struct timespec *old)
     69 {
     70 	new->tv_sec -= old->tv_sec;
     71 	new->tv_nsec -= old->tv_nsec;
     72 
     73 	if (new->tv_nsec < 0) {
     74 		new->tv_sec -= 1;
     75 		new->tv_nsec += 1e9;
     76 	}
     77 }
     78 
     79 int
     80 type(const char *text, const char *preview)
     81 {
     82 	int i, c, right, wrong, tlen;
     83 	struct timespec start = { 0 }, end;
     84 	float cps;
     85 
     86 	if (typed > 0) clock_gettime(CLOCK_REALTIME, &start);
     87 
     88 	tlen = strlen(text);
     89 	c = right = wrong = 0;
     90 	while (1) {
     91 		printf("\r\x1b[2K"); /* goto beginning of row and clear */
     92 		printf("\n\x1b[2K\x1b[1A"); /* down a row and clear */
     93 
     94 		switch (c) {
     95 		case C_EOF:
     96 			goto quit;
     97 		case C_EOT:
     98 			goto exit;
     99 		case C_DEL:
    100 			if (wrong) wrong--;
    101 			else if (right) right--;
    102 			break;
    103 		case C_SKIP:
    104 			right += wrong + 1;
    105 			if (right >= tlen)
    106 				goto exit;
    107 			wrong = 0;
    108 			break;
    109 		case u'\r':
    110 		case u'\n':
    111 		case u'\0':
    112 			break;
    113 		case ' ':
    114 			if (!wrong && right == tlen)
    115 				goto exit;
    116 		default:
    117 			if (!start.tv_sec)
    118 				clock_gettime(CLOCK_REALTIME, &start);
    119 			if (right + wrong == tlen) break;
    120 			if (c == text[right + wrong] && !wrong) right++;
    121 			else wrong++;
    122 			break;
    123 		}
    124 
    125 		print_style(style_right);
    126 		printf("%.*s", right, text);
    127 		print_style(style_wrong);
    128 		for (i = 0; i < wrong; i++)
    129 			putchar(text[right + i] == ' ' ? '_' : text[right + i]);
    130 		print_style(style_reset);
    131 		printf("%.*s", tlen - right - wrong, text + right + wrong);
    132 
    133 		print_style(style_preview);
    134 		printf("\n\r%s", preview);
    135 		print_style(style_reset);
    136 
    137 		printf("\x1b[1A\r");
    138 		if (right + wrong)
    139 			printf("\x1b[%iC", right + wrong);
    140 
    141 		if (!*preview && !wrong && right == tlen)
    142 			goto exit;
    143 
    144 		c = getchar();
    145 		if (c < 0) goto quit;
    146 	};
    147 
    148 exit:
    149 	printf("\r\x1b[2K"); /* goto beginning of line and clear */
    150 	printf("\n\x1b[2K\x1b[1A"); /* down a row and clear */
    151 
    152 	if (!start.tv_sec) return 1;
    153 
    154 	clock_gettime(CLOCK_REALTIME, &end);
    155 	time_sub(&end, &start);
    156 
    157 	typed += right;
    158 
    159 	end.tv_nsec /= 1000000; /* reduce precision to millis */
    160 	cps = right / (end.tv_sec + end.tv_nsec / 1e3f);
    161 	if (!typed) gcps = cps;
    162 	else gcps = LINTERP(cps, gcps, right * 1.f / typed);
    163 
    164 	return 0;
    165 
    166 quit:
    167 	return 1;
    168 }
    169 
    170 int
    171 readline(FILE *f, char *buf, size_t size)
    172 {
    173 	do {
    174 		if (!fgets(buf, size, f)) return 1;
    175 
    176 		for (ssize_t i = strlen(buf) - 1; i >= 0; i--) {
    177 			if (buf[i] == '\r' || buf[i] == '\n')
    178 				buf[i] = '\0';
    179 			else
    180 				break;
    181 		}
    182 		if (strlen(buf) == 0) continue;
    183 	} while (!*buf);
    184 
    185 	return 0;
    186 }
    187 
    188 void
    189 reset()
    190 {
    191 	tcsetattr(0, TCSANOW, &old_termios);
    192 }
    193 
    194 int
    195 main(int argc, const char **argv)
    196 {
    197 	const char *path = NULL;
    198 	bool retry = true;
    199 
    200 	for (const char **arg = argv + 1; *arg; arg++) {
    201 		if (!strcmp(*arg, "-R")) {
    202 			retry = false;
    203 		} else if (!path) {
    204 			path = *arg;
    205 		} else {
    206 			break;
    207 		}
    208 	}
    209 	if (!path) errx(1, "missing file argument");
    210 
    211 	FILE *file = fopen(path, "r");
    212 	if (!file) err(1, "fopen");
    213 
    214 	setvbuf(stdin, NULL, _IONBF, 0);
    215 	setvbuf(stdout, NULL, _IONBF, 0);
    216 
    217 	struct termios termios;
    218 	tcgetattr(0, &termios);
    219 	old_termios = termios;
    220 
    221 	cfmakeraw(&termios);
    222 
    223 	if (!tcsetattr(0, TCSANOW, &termios)) atexit(reset);
    224 
    225 	char line[256], next_line[256];
    226 	if (readline(file, next_line, sizeof(next_line)))
    227 		errx(1, "Empty input file");
    228 
    229 	for (bool eof = 0; !eof; ) {
    230 		strcpy(line, next_line);
    231 		eof |= readline(file, next_line, sizeof(next_line));
    232 		if (type(line, eof ? "" : next_line)) break;
    233 	}
    234 
    235 	if (gcps) {
    236 		printf("CPM: %0.2f\n\r", gcps * 60);
    237 		printf("WPM: %0.2f\n\r", gcps * 12);
    238 	}
    239 
    240 	fclose(file);
    241 }