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

commit 9fb7d9a0b6ae910e5b64300451cdc1a2fe4024f8
parent 09da1f9bea7fb12e327de2399a62ab3370f4f629
Author: Emma Weaver <emma@waeaves.com>
Date:   Sat,  2 May 2026 15:26:54 -0400

Added hardwrapping of long prompts

Diffstat:
Mexamples/lesson.sh24++++++++++++------------
Cexamples/words.sh -> examples/words-by-letter.sh0
Mexamples/words.sh36+++---------------------------------
Mtyper.c75++++++++++++++++++++++++++++++++++++---------------------------------------
Mtyper.h42+++++++++++++++++++++++-------------------
5 files changed, 74 insertions(+), 103 deletions(-)

diff --git a/examples/lesson.sh b/examples/lesson.sh @@ -24,21 +24,21 @@ echo "If it gets too hard, don't be afraid to ctrl+C and redo previous lessons." echo "" echo "Introducing the new letters..." -typie -lf /dev/null "$(./words.sh "$NEW_LETTERS" 6 12)" -typie -lf /dev/null "$(./words.sh "$NEW_LETTERS" 6 12)" +typie -lf /dev/null "$(./words-by-letter.sh "$NEW_LETTERS" 6 12)" +typie -lf /dev/null "$(./words-by-letter.sh "$NEW_LETTERS" 6 12)" echo "Integrating with some old letters..." -typie -f /dev/null "$(./words.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)" -typie -f /dev/null "$(./words.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)" -typie -f /dev/null "$(./words.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)" -typie -f /dev/null "$(./words.sh "$(old_letters $MEDIUM)$NEW_LETTERS" 5 8)" -typie -f /dev/null "$(./words.sh "$(old_letters $MEDIUM)$NEW_LETTERS" 5 8)" -typie -f /dev/null "$(./words.sh "$(old_letters $HARD)$NEW_LETTERS" 5 8)" -typie -f /dev/null "$(./words.sh "$(old_letters $HARD)$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words-by-letter.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words-by-letter.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words-by-letter.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words-by-letter.sh "$(old_letters $MEDIUM)$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words-by-letter.sh "$(old_letters $MEDIUM)$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words-by-letter.sh "$(old_letters $HARD)$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words-by-letter.sh "$(old_letters $HARD)$NEW_LETTERS" 5 8)" echo "All together now!" -typie -f /dev/null "$(./words.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)" -typie -f /dev/null "$(./words.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)" -typie -f /dev/null "$(./words.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words-by-letter.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words-by-letter.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)" +typie -f /dev/null "$(./words-by-letter.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)" echo "All done! :3" diff --git a/examples/words.sh b/examples/words-by-letter.sh diff --git a/examples/words.sh b/examples/words.sh @@ -1,35 +1,5 @@ +#!/bin/sh -CHARSET=$1 -MIN_LENGTH=$2 -MAX_LENGTH=$3 -DICTFILE="dicts/20k.txt" - -if [ $CHARSET = "" ]; then - echo "Requires charset argument." - exit 1 -fi - -# filter the dictionary for words only involving the characters, len >= 5. -WORDS="$(cat $DICTFILE | egrep "^[$CHARSET]{5,}$")" - -LENGTH="$(seq $MIN_LENGTH $MAX_LENGTH | shuf -n 1)" -WORD_COUNT="$(echo "$WORDS" | wc -l)" -FEW_WORDS="false" -if [ "$WORD_COUNT" -lt "$LENGTH" ]; then - FEW_WORDS="true" -fi - -get_word() { - ./letters.sh "$CHARSET" 4 6 -} - -if [ $FEW_WORDS = "true" ]; then - PROMPT="$(get_word)" - for i in $(seq $LENGTH); do - PROMPT="$PROMPT $(get_word)" - done - echo "$PROMPT" -else - echo "$WORDS" | shuf -n $LENGTH | xargs echo -fi +cd $(dirname $0) +./words-by-letter.sh "abcdefghijklmnopqrstuvwxyz" $1 $1 diff --git a/typer.c b/typer.c @@ -3,6 +3,8 @@ #include "typer.h" #include "opt.h" +#define PRINT_CONTROL(literal) printf(literal); + void setup_termios(struct termios term_og) { @@ -27,35 +29,29 @@ restore_termios(struct termios term_og) } void -cursor_back(int steps) +cursor_back(State * state, int steps) { + while (state->column < steps) { + steps -= state->column; + state->column = 80; + state->row--; + printf("\r" "\033[1A" "\033[80C"); // cursor up 1 + right 80 + } + state->column -= steps; printf("\033[" "%d" "D", steps); } void -cursor_forward(int steps) -{ - printf("\033[" "%d" "C", steps); -} - -void -cursor_up(int steps) -{ - printf("\033[" "%d" "A", steps); -} - -void -cursor_down(int steps) -{ - printf("\033[" "%d" "B", steps); -} - -void -print_char(char c) +print_char(State * state, char c) { - /* replace spaces with interpuncts. */ + if (state->column >= 80) { + printf("\n\r"); + state->column = 0; + state->row++; + } if (c == ' ') printf("\u00B7"); else printf("%c", c); + state->column++; } void @@ -68,12 +64,13 @@ add_jump(State * state, int size) int remove_jump(State * state) { - /* returns the size of the most recent jump */ + /* returns the size of the removed jump */ state->jumps.length--; return state->jumps.buffer[state->jumps.length]; } +// TODO refactor this to just be a general print_str method? void show_remaining(State * state) { @@ -83,7 +80,7 @@ show_remaining(State * state) int steps = 0; char c; - printf(COLOR_RESET "\033[0K"); /* clear to end of line */ + PRINT_CONTROL(COLOR_RESET CLEAR_TO_LINE_END); if (prompt[index] == '\0') return; @@ -93,11 +90,11 @@ show_remaining(State * state) if (c == '\0' || c == '\n') break; - printf("%c", prompt[index]); + print_char(state, prompt[index]); steps++; index++; } - cursor_back(steps); + cursor_back(state, steps); } void @@ -114,12 +111,13 @@ skip(State * state, char input) if (c == '\0' || IS_BLANK(c) != blankness) break; - printf("%c" COLOR_YELLOW, c); + print_char(state, c); + PRINT_CONTROL(COLOR_YELLOW); steps++; state->index++; } if (c != '\0') { - printf("%c", input); + print_char(state, input); steps++; state->index++; } @@ -129,8 +127,8 @@ skip(State * state, char input) void on_correct(State * state, char c) { - printf(COLOR_GREEN); - printf("%c", c); + PRINT_CONTROL(COLOR_GREEN); + print_char(state, c); add_jump(state, 1); state->index++; @@ -144,16 +142,16 @@ on_incorrect(State * state, char c) char expected_c = prompt[state->index]; int input_blank = IS_BLANK(c), prompt_blank = IS_BLANK(expected_c); - printf(COLOR_RED); + PRINT_CONTROL(COLOR_RED); if (state->flags & FLAG_LEARN_MODE) { - print_char(c); + print_char(state, c); show_remaining(state); - cursor_back(1); + cursor_back(state, 1); return; } if (expected_c == '\0') { - print_char(c); + print_char(state, c); add_jump(state, 0); return; } @@ -163,7 +161,7 @@ on_incorrect(State * state, char c) (!input_blank && !prompt_blank) ) { /* correct character type */ - print_char(c); + print_char(state, c); state->index++; add_jump(state, 1); return; @@ -174,7 +172,7 @@ on_incorrect(State * state, char c) IS_BLANK(prompt[state->index-1]) == input_blank ) { /* extra letters at the end of a chunk */ - print_char(c); + print_char(state, c); add_jump(state, 0); show_remaining(state); return; @@ -200,12 +198,12 @@ on_backspace(State * state) jump = remove_jump(state); if (jump == 0) { - cursor_back(1); + cursor_back(state, 1); show_remaining(state); return; } - cursor_back(jump); + cursor_back(state, jump); state->index -= jump; show_remaining(state); } @@ -220,8 +218,7 @@ void start_typing(State * state) { state->index = 0; - printf(COLOR_BOLD "%s" COLOR_RESET "\r", state->prompt); - // printf("%s" "\n" "\r", state->prompt); + show_remaining(state); } int diff --git a/typer.h b/typer.h @@ -5,32 +5,32 @@ #include <time.h> -#define COLOR_GREEN "\033[1;32m" -#define COLOR_YELLOW "\033[1;33m" -#define COLOR_RED "\033[1;31m" +#define COLOR_GREEN "\033[1;32m" +#define COLOR_YELLOW "\033[1;33m" +#define COLOR_RED "\033[1;31m" #define COLOR_PALE_RED "\033[0;31m" -#define COLOR_BOLD "\033[1;37m" -#define COLOR_RESET "\033[0m" -#define CLEAR_LINE "\033[0K" +#define COLOR_BOLD "\033[1;37m" +#define COLOR_RESET "\033[0m" +#define CLEAR_TO_LINE_END "\033[0K" -#define KEY_CTRL_C 3 -#define KEY_CTRL_D 4 -#define KEY_ENTER 13 -#define KEY_ESCAPE 27 -#define KEY_BACKSPACE 127 -#define KEY_SHIFT_BACKSPACE 8 +#define KEY_CTRL_C 3 +#define KEY_CTRL_D 4 +#define KEY_ENTER 13 +#define KEY_ESCAPE 27 +#define KEY_BACKSPACE 127 +#define KEY_SHIFT_BACKSPACE 8 #define IS_BLANK(X) ((X) == ' ' || (X) == '\t') #define IS_NUMBER(X) ((X) >= '0' && (X) <= '9') #define IS_LETTER(X) (((X) >= 'a' && (X) <= 'z') || ((X) >= 'A' && (X) <= 'Z')) #define IS_ALPHANUMERIC(X) (IS_LETTER(X) || IS_NUMBER(X)) -#define KEYPRESS_CORRECT 0 -#define KEYPRESS_INCORRECT 1 -#define KEYPRESS_BACKSPACE 2 -#define KEYPRESS_EXIT 3 -#define KEYPRESS_ENTER 4 -#define KEYPRESS_NONE 5 +#define KEYPRESS_CORRECT 0 +#define KEYPRESS_INCORRECT 1 +#define KEYPRESS_BACKSPACE 2 +#define KEYPRESS_EXIT 3 +#define KEYPRESS_ENTER 4 +#define KEYPRESS_NONE 5 typedef struct { int * buffer; @@ -45,9 +45,13 @@ typedef struct { /* records how many characters are traversed by each keypress */ Jumps jumps; - /* sum of jumps, current index in prompt */ + /* sum of jumps, cursor's index in prompt */ int index; + /* cursor's position within the terminal */ + int column; + int row; + /* keypress tallies */ int chars_correct; int chars_total;

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