typie | Minimal typing practice tool |
| download: 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.