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 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 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}