ferristype/
ferristype.rs

1use crate::lang::Language;
2use crate::stats::Timer;
3use crate::terminalui::{Input, Terminal};
4use itertools::peek_nth;
5use std::io::{stdin, stdout, Write};
6use termion::{cursor, event::Key, input::TermRead, raw::IntoRawMode};
7
8#[derive(Clone)]
9pub enum Mode {
10    Words(String, usize),
11}
12
13impl Default for Mode {
14    fn default() -> Self {
15        Self::Words("english".to_string(), 10)
16    }
17}
18
19fn initialize_lines(words: &Vec<String>, words_per_line: u16) -> Vec<String> {
20    let mut resu = vec![String::new()];
21    let mut w = words.clone();
22    let count = words.len() / words_per_line as usize + 1;
23    for _ in 0..count {
24        let mut line = vec![];
25        for _ in 0..words_per_line {
26            if let Some(word) = w.pop() {
27                line.push(word);
28            }
29        }
30        resu.push(line.join(" "));
31    }
32    resu.remove(0);
33    if words.len() % words_per_line as usize == 0 {
34        resu.remove(count - 1);
35    }
36    resu
37}
38
39pub struct FerrisType {
40    mode: Mode,
41    lines: Vec<String>,
42    timer: Timer,
43    terminal: Terminal,
44    line_peek: u16,
45    words_per_line: u16,
46}
47
48impl FerrisType {
49    pub fn new(mode: Mode) -> Self {
50        let words = match &mode {
51            Mode::Words(lang, num) => {
52                let language = Language::new(lang.to_string());
53                language.get_random(num.to_owned())
54            }
55        };
56        let (w, _) = termion::terminal_size().unwrap();
57        let lines = initialize_lines(&words, w / 3 / 5);
58        Self {
59            mode,
60            lines,
61            timer: Timer::default(),
62            terminal: Terminal::new(stdout().into_raw_mode().unwrap()),
63            line_peek: 3,
64            words_per_line: w / 3 / 5,
65        }
66    }
67
68    pub fn words(lang: String, n: usize) -> Self {
69        Self::new(Mode::Words(lang, n))
70    }
71
72    pub fn line_peek(mut self, n: u16) -> Self {
73        self.line_peek = n;
74        self
75    }
76    pub fn words_per_line(mut self, n: u16) -> Self {
77        self.words_per_line = n;
78        self
79    }
80
81    pub fn play(mut self) -> std::io::Result<()> {
82        let mut stdin = stdin().keys();
83        let mut on_last_word = false;
84        self.terminal.initialize_screen()?;
85        let l = self.lines.clone();
86        let line_peek = self.line_peek;
87        let mut lines = peek_nth(l.iter());
88        let mut lastline = None;
89        'lines: while let Some(current_line) = lines.next() {
90            let mut input = Input::new(current_line);
91            self.terminal.draw_current_line(current_line)?;
92            self.terminal.draw_prev_line(lastline)?;
93            self.terminal.clear_current_line()?;
94            write!(self.terminal.stdout, "{}", input)?;
95            for i in 0..line_peek {
96                self.terminal.clear_next_line(i)?;
97                let next_line = lines.peek_nth(i.into());
98                self.terminal.draw_future_line(next_line, i)?;
99                if i == 0 {
100                    on_last_word = true;
101                }
102            }
103            lastline = Some(current_line);
104            loop {
105                write!(
106                    self.terminal.stdout,
107                    "{}",
108                    cursor::Goto(self.terminal.type_width, self.terminal.type_height)
109                )?;
110                self.terminal.stdout.flush()?;
111                let c = stdin.next().unwrap()?;
112                self.timer.record_key(c);
113                match c {
114                    Key::Char('\t') => return FerrisType::new(self.mode.clone()).play(),
115                    Key::Char('\n') => {
116                        break;
117                    }
118                    Key::Char(' ') if input.is_complete() => break,
119                    Key::Char(c) => {
120                        let correct = input.update(c);
121                        if correct && on_last_word && input.is_complete() {
122                            break;
123                        }
124                    }
125                    Key::Backspace => {
126                        self.terminal.clear_current_line()?;
127                        input.delete_one();
128                    }
129                    Key::Ctrl('c') => {
130                        break 'lines;
131                    }
132                    _ => {}
133                }
134                write!(self.terminal.stdout, "{}", input)?;
135                self.terminal.stdout.flush()?;
136            }
137            self.terminal.clear_current_line()?;
138        }
139        self.terminal.finalize(self.timer, self.lines)?;
140        Ok(())
141    }
142}