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 }