arbor_cli/util/
repl.rs

1use crossterm::{
2    cursor,
3    event::{self, Event, KeyCode, KeyModifiers},
4    execute,
5    style::{Color, Print, ResetColor, SetForegroundColor},
6    terminal::{self, Clear, ClearType},
7};
8use std::time::Duration;
9use std::{
10    error::Error,
11    io::{self, Write},
12};
13
14use crate::common::app_builder::Arbor;
15
16pub struct Repl {
17    arbor: Arbor,
18    input: String,
19    input_section: usize,
20    selected_suggestion: usize,
21}
22
23impl Repl {
24    pub async fn new() -> Result<Self, Box<dyn Error>> {
25        Ok(Self {
26            arbor: Arbor::build().await?,
27            input: "".to_string(),
28            input_section: 0,
29            selected_suggestion: 0,
30        })
31    }
32
33    pub async fn run(&mut self) -> Result<(), Box<dyn Error>> {
34        let mut stdout = io::stdout();
35
36        terminal::enable_raw_mode()?;
37        execute!(stdout, terminal::EnterAlternateScreen)?;
38
39        loop {
40            if event::poll(Duration::from_millis(100))? {
41                if let Event::Key(event) = event::read()? {
42                    match event.code {
43                        KeyCode::Char(' ') => {
44                            if self.input.is_empty() {
45                                continue;
46                            }
47
48                            self.input.push(' ');
49                            self.input_section += 1;
50
51                            if self
52                                .input
53                                .chars()
54                                .nth(self.input.len().saturating_sub(2))
55                                .unwrap()
56                                == ' '
57                            {
58                                self.input.pop();
59                                self.input_section -= 1;
60                            }
61                        }
62                        KeyCode::Char('c') if event.modifiers.contains(KeyModifiers::CONTROL) => {
63                            break
64                        }
65                        KeyCode::Char(c) => {
66                            self.input.push(c);
67                            self.selected_suggestion = 0;
68                        }
69                        KeyCode::Backspace => {
70                            if self.input.is_empty() {
71                                continue;
72                            }
73
74                            let curr = self
75                                .input
76                                .chars()
77                                .nth(self.input.len().saturating_sub(1))
78                                .unwrap();
79
80                            if curr == ' ' {
81                                self.input_section -= 1;
82                            }
83
84                            self.input.pop();
85                            self.selected_suggestion = 0;
86                        }
87                        KeyCode::Enter => {
88                            let words = self.input.split(' ').collect::<Vec<&str>>();
89
90                            for word in words {
91                                // word length must be bigger than 1 character
92                                if word.len() < 2 {
93                                    continue;
94                                }
95
96                                self.arbor
97                                    .autocomplete
98                                    .insert_word(word.to_string())
99                                    .await?;
100                            }
101
102                            self.input = "".to_string();
103                            self.selected_suggestion = 0;
104                            self.input_section = 0;
105                        }
106                        KeyCode::Up => {
107                            if self.selected_suggestion > 0 {
108                                self.selected_suggestion -= 1;
109                            }
110                        }
111                        KeyCode::Down => {
112                            self.selected_suggestion += 1;
113                        }
114                        KeyCode::Tab => {
115                            if self.input.is_empty() {
116                                continue;
117                            }
118
119                            if let Some(suggestion) = self
120                                .arbor
121                                .autocomplete
122                                .suggest_word(
123                                    self.input
124                                        .split(' ')
125                                        .collect::<Vec<&str>>()
126                                        .get(self.input_section)
127                                        .unwrap(),
128                                )
129                                .await?
130                                .get(self.selected_suggestion)
131                            {
132                                let mut words = self.input.split(' ').collect::<Vec<&str>>();
133
134                                words[self.input_section] = suggestion.as_str();
135
136                                self.input = words.join(" ");
137                            }
138
139                            self.selected_suggestion = 0;
140
141                            self.input.push(' ');
142                            self.input_section += 1;
143                        }
144                        KeyCode::Esc => break,
145                        _ => panic!("Unknown keystroke!"),
146                    }
147                }
148            }
149
150            let suggestions = self
151                .arbor
152                .autocomplete
153                .suggest_word(
154                    self.input
155                        .split(' ')
156                        .collect::<Vec<&str>>()
157                        .get(self.input_section)
158                        .unwrap(),
159                )
160                .await?;
161
162            // NOTE: this is to prevent selection overflow
163            let max_index = suggestions.len().saturating_sub(1);
164            if self.selected_suggestion > max_index {
165                self.selected_suggestion = max_index;
166            }
167
168            execute!(stdout, Clear(ClearType::All))?;
169
170            execute!(
171                stdout,
172                cursor::MoveTo(0, 0),
173                Print(format!("> {}", self.input))
174            )?;
175
176            for (i, suggestion) in suggestions.iter().enumerate() {
177                if i == self.selected_suggestion {
178                    execute!(
179                        stdout,
180                        cursor::MoveTo(2, (i + 1) as u16),
181                        SetForegroundColor(Color::Green),
182                        Print(suggestion),
183                        ResetColor
184                    )?;
185                } else {
186                    execute!(
187                        stdout,
188                        cursor::MoveTo(2, (i + 1) as u16),
189                        SetForegroundColor(Color::DarkGrey),
190                        Print(suggestion),
191                        ResetColor
192                    )?;
193                }
194            }
195
196            execute!(stdout, cursor::MoveTo(2 + self.input.len() as u16, 0))?;
197
198            stdout.flush()?;
199        }
200
201        execute!(stdout, terminal::LeaveAlternateScreen)?;
202        terminal::disable_raw_mode()?;
203
204        Ok(())
205    }
206}