git home / emma home
logo

typie

Minimal typing practice tool.
git clone https://git.y1.nz/archives/typie.tar.gz
README | Files | Log | Refs | LICENSE

typer.c


      1 #include <stdio.h>
      2 
      3 #include "typer.h"
      4 #include "opt.h"
      5 
      6 #define PRINT_CONTROL(literal)  printf(literal); /* for hygeine. */
      7 #define WRAP_LENGTH  79 /* one less than the desired max line length */
      8 
      9 
     10 void
     11 setup_termios(struct termios term_og)
     12 {
     13 	struct termios term_raw;
     14 	term_raw = term_og;
     15 	term_raw.c_iflag &= ~(
     16 		IGNBRK | BRKINT | PARMRK | ISTRIP |
     17 		INLCR | IGNCR | ICRNL | IXON
     18 	);
     19 	term_raw.c_oflag &= ~OPOST;
     20 	term_raw.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
     21 	term_raw.c_cflag &= ~(CSIZE | PARENB);
     22 	term_raw.c_cflag |= CS8; 
     23 	tcsetattr(0, 0, &term_raw);
     24 }
     25 
     26 void
     27 restore_termios(struct termios term_og)
     28 {
     29 	tcsetattr(0, 0, &term_og);
     30 	printf(COLOR_RESET "\n");
     31 }
     32 
     33 void
     34 cursor_back(State * state, int steps)
     35 {
     36 	state->chars_printed_index -= steps;
     37 	while (state->column < steps) {
     38 		steps -= state->column;
     39 
     40 		state->column = state->line_lengths[state->row-1];
     41 
     42 		state->row--;
     43 
     44 		/* carriage return, cursor up 1, right (until the end of line) */
     45 		printf("\r" "\033[1A" "\033[%dC", state->column);
     46 	}
     47 
     48 	state->column -= steps;
     49 	printf("\033[" "%d" "D", steps);
     50 }
     51 
     52 void
     53 wrap(State * state)
     54 {
     55 	int line_length = state->line_lengths[state->row];
     56 	int extras;
     57 	int hard_wrap = 0;
     58 
     59 	/* hard wrap very long lines */
     60 	if (line_length < WRAP_LENGTH/4) {
     61 		line_length = WRAP_LENGTH;
     62 		state->line_lengths[state->row] = WRAP_LENGTH;
     63 		hard_wrap = 1;
     64 	}
     65 
     66 	extras = WRAP_LENGTH - line_length;
     67 
     68 	/* erase wrapped word from old line */
     69 	printf("\r" "\033[%dC", line_length);
     70 	if (hard_wrap) printf("-");
     71 	printf(CLEAR_TO_LINE_END);
     72 
     73 	/* cursor to beginning of next line */
     74 	printf("\n\r");
     75 	
     76 	/* replace the wrapped word on the new line */
     77 	for (int i = 0; i < extras; i++)
     78 		printf("%c", state->chars_printed[state->chars_printed_index - extras + i]);
     79 
     80 	state->column = extras;
     81 	state->row++;
     82 	if(state->row > state->max_row)
     83 		state->max_row = state->row;
     84 	state->line_lengths[state->row] = 0;
     85 }
     86 
     87 void
     88 print_char(State * state, char c, int show_whitespace)
     89 {
     90 	if (state->column >= WRAP_LENGTH)
     91 		wrap(state);
     92 
     93 	if (show_whitespace && c == ' ') printf("\u00B7");
     94 	else printf("%c", c);
     95 	
     96 	state->chars_printed[state->chars_printed_index] = c;
     97 	state->chars_printed_index++;
     98 
     99 	if (IS_BLANK(c))
    100 		state->line_lengths[state->row] = state->column + 1;
    101 
    102 	state->column++;
    103 }
    104 
    105 /* PAST THIS POINT, NO PRINTING OTHER THAN ESCAPE CODES. */
    106 /* USE PRINT_CONTROL AND NO PRINTF PAST HERE */
    107 
    108 void
    109 add_jump(State * state, int size)
    110 {
    111 	state->jumps.buffer[state->jumps.length] = size;
    112 	state->jumps.length++;
    113 }
    114 
    115 int
    116 remove_jump(State * state)
    117 {
    118 	/* returns the size of the removed jump */
    119 
    120 	state->jumps.length--;
    121 	return state->jumps.buffer[state->jumps.length];
    122 }
    123 
    124 void
    125 show_remaining(State * state, int reset_color)
    126 {
    127 	/* print the rest of the string after index, at the cursor. */
    128 	const char * prompt = state->prompt;
    129 	int index = state->index;
    130 	int steps = 0;
    131 	char c;
    132 
    133 	if (reset_color)
    134 		PRINT_CONTROL(COLOR_RESET);
    135 	PRINT_CONTROL(CLEAR_TO_LINE_END);
    136 
    137 	if (prompt[index] == '\0')
    138 		return;
    139 
    140 	for (;;) {
    141 		c = prompt[index];
    142 		if (c == '\0' || c == '\n')
    143 			break;
    144 
    145 		print_char(state, prompt[index], 0);
    146 		steps++;
    147 		index++;
    148 	}
    149 	printf(CLEAR_TO_FILE_END);
    150 	cursor_back(state, steps);
    151 }
    152 
    153 void
    154 skip(State * state, char input)
    155 {
    156 	/* jumps to the end of the current whitespace or non-whitespace chunk */
    157 
    158 	int blankness = IS_BLANK(state->prompt[state->index]);
    159 	char c;
    160 	int steps = 0;
    161 	
    162 	for (;;) {
    163 		c = state->prompt[state->index];
    164 		if (c == '\0' || IS_BLANK(c) != blankness)
    165 			break;
    166 
    167 		print_char(state, c, 0);
    168 		PRINT_CONTROL(COLOR_YELLOW);
    169 		steps++;
    170 		state->index++;
    171 	}
    172 	if (c != '\0') {
    173 		print_char(state, input, 0);
    174 		steps++;
    175 		state->index++;
    176 	}
    177 	add_jump(state, steps);
    178 }
    179 
    180 void
    181 on_correct(State * state, char c)
    182 {
    183 	PRINT_CONTROL(COLOR_GREEN);
    184 	print_char(state, c, 0);
    185 	add_jump(state, 1);
    186 	state->index++;
    187 
    188 	show_remaining(state, 1);
    189 }
    190 
    191 void
    192 on_incorrect(State * state, char c)
    193 {
    194 	const char * prompt = state->prompt;
    195 	char expected_c = prompt[state->index];
    196 	int input_blank = IS_BLANK(c), prompt_blank = IS_BLANK(expected_c);
    197 
    198 	PRINT_CONTROL(COLOR_RED);
    199 	if (state->flags & FLAG_LEARN_MODE) {
    200 		print_char(state, c, 1);
    201 		show_remaining(state, 1);
    202 		cursor_back(state, 1);
    203 		return;
    204 	}
    205 
    206 	if (expected_c == '\0') {
    207 		print_char(state, c, 1);
    208 		add_jump(state, 0);
    209 		return;
    210 	}
    211 
    212 	if (
    213 		(input_blank && prompt_blank) ||
    214 		(!input_blank && !prompt_blank)
    215 	) {
    216 		/* correct character type */
    217 		print_char(state, c, 1);
    218 		state->index++;
    219 		add_jump(state, 1);
    220 		return;
    221 	}
    222 
    223 	if (
    224 		state->index != 0 &&
    225 		IS_BLANK(prompt[state->index-1]) == input_blank
    226 	) {
    227 		/* extra letters at the end of a chunk */
    228 		print_char(state, c, 1);
    229 		add_jump(state, 0);
    230 		show_remaining(state, 1);
    231 		return;
    232 	}
    233 
    234 	skip(state, c);
    235 	return;
    236 
    237 }
    238 
    239 void
    240 on_backspace(State * state)
    241 {
    242 	int jump;
    243 
    244 	if (state->flags & FLAG_LEARN_MODE) {
    245 		show_remaining(state, 1);
    246 		return;
    247 	}
    248 
    249 	if (state->jumps.length == 0)
    250 		return;
    251 
    252 	jump = remove_jump(state);
    253 	if (jump == 0) {
    254 		cursor_back(state, 1);
    255 		show_remaining(state, 1);
    256 		return;
    257 	}
    258 
    259 	cursor_back(state, jump);
    260 	state->index -= jump;
    261 	show_remaining(state, 1);
    262 }
    263 
    264 int
    265 on_enter(State * state)
    266 {
    267 	return KEYPRESS_ENTER;
    268 }
    269 
    270 void
    271 start_typing(State * state)
    272 {
    273 	printf(COLOR_BOLD);
    274 	show_remaining(state, 0);
    275 }
    276 
    277 int
    278 handle_keypress(State * state, char c)
    279 {
    280 	char expected = state->prompt[state->index];
    281 	
    282 	if (c == KEY_CTRL_C || c == KEY_CTRL_D || c == KEY_ESCAPE)
    283 		return KEYPRESS_EXIT;
    284 
    285 	if (c == KEY_ENTER)
    286 		return on_enter(state);
    287 
    288 	if (c == KEY_BACKSPACE || c == KEY_SHIFT_BACKSPACE) {
    289 		on_backspace(state);
    290 		return KEYPRESS_BACKSPACE;
    291 	}
    292 	
    293 	if (c == expected) {
    294 		on_correct(state, c);
    295 		return KEYPRESS_CORRECT;
    296 	}
    297 
    298 	on_incorrect(state, c);
    299 	return KEYPRESS_INCORRECT;
    300 }

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.