1use std::fmt::Display;
6
7use term_manager::TermManager;
8
9pub type Result<T> = std::result::Result<T, Error>;
11
12pub type ProcessLineFunc = Box<dyn FnMut(String) -> Result<String>>;
14
15pub type LineCompletionFunc = Box<dyn FnMut(String) -> bool>;
17
18#[derive(Debug)]
20pub enum Error {
21 InitFail(String),
22 IoFlush(String),
23 IoRead(String),
24 IoWrite(String),
25 ProcessLine(String),
26}
27
28impl Display for Error {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 Error::InitFail(s) => write!(f, "initialization failed: {}", s),
32 Error::IoFlush(s) => write!(f, "IO flush error: {}", s),
33 Error::IoRead(s) => write!(f, "IO read error: {}", s),
34 Error::IoWrite(s) => write!(f, "IO write error: {}", s),
35 Error::ProcessLine(s) => write!(f, "Process Line error: {}", s),
36 }
37 }
38}
39
40#[derive(Clone, Debug)]
42pub struct Line {
43 text: String,
44 cursor_pos: usize,
45}
46
47impl Line {
48 pub fn new() -> Self {
50 Self {
51 text: String::new(),
52 cursor_pos: 0,
53 }
54 }
55
56 pub fn insert_char(&mut self, c: char) {
58 self.text.insert(self.cursor_pos, c);
59 self.cursor_pos += 1;
60 }
61
62 pub fn backspace(&mut self) {
64 if self.cursor_pos > 0 {
65 self.cursor_pos -= 1;
66 self.text.remove(self.cursor_pos);
67 }
68 }
69
70 pub fn move_left(&mut self) {
72 if self.cursor_pos > 0 {
73 self.cursor_pos -= 1;
74 }
75 }
76
77 pub fn move_right(&mut self) {
79 if self.cursor_pos < self.text.len() {
80 self.cursor_pos += 1;
81 }
82 }
83
84 pub fn text(&self) -> &str {
86 &self.text
87 }
88}
89
90impl Display for Line {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 write!(f, "{}", self.text)
93 }
94}
95
96#[derive(Copy, Clone, Debug)]
98enum InputType {
99 Normal,
100 Escape,
101 EscapeSequence,
102}
103
104#[derive(Copy, Clone, Debug)]
106enum ReplState {
107 Continue,
108 Break,
109}
110
111pub struct Repl {
113 tmanager: TermManager,
114 lines: Vec<Line>,
115 current_line: usize,
116 escape_buffer: Vec<u8>,
117 input_state: InputType,
118 process_line: ProcessLineFunc,
119 is_line_complete: LineCompletionFunc,
120 prompt: String,
121 banner: String,
122 welcome_msg: String,
123}
124
125impl Repl {
126 pub fn new(
136 prompt: String,
137 banner: String,
138 welcome_msg: String,
139 process_line: ProcessLineFunc,
140 line_is_terminated: LineCompletionFunc,
141 ) -> Result<Self> {
142 let tmanager = TermManager::new().or_else(|e| {
143 let msg = format!("failed to initialized Repl: {}", e);
144 Err(Error::InitFail(msg))
145 })?;
146 let mut lines: Vec<Line> = Vec::new();
147 lines.push(Line::new());
148 let current_line = 0;
149 let escape_buffer = Vec::new();
150 let input_state = InputType::Normal;
151
152 Ok(Repl {
153 tmanager,
154 lines,
155 current_line,
156 escape_buffer,
157 input_state,
158 process_line,
159 is_line_complete: line_is_terminated,
160 prompt,
161 banner,
162 welcome_msg,
163 })
164 }
165
166 pub fn print_welcome(&mut self) {
168 println!("{}\n{}", self.banner, self.welcome_msg);
169 }
170
171 pub fn print_prompt(&mut self) {
173 print!("{}", self.prompt);
174 }
175
176 pub fn get_line(&self, index: usize) -> Option<&Line> {
178 self.lines.get(index)
179 }
180
181 pub fn process_input(&mut self) -> Result<String> {
183 self.tmanager
184 .flush()
185 .map_err(|_| Error::IoFlush("unable to flush stdout".into()))?;
186
187 let mut output: Option<String> = None;
188
189 loop {
190 let mut buf = [0u8; 1];
191 self.tmanager
192 .read(&mut buf)
193 .map_err(|e| Error::IoRead(format!("error reading from stdin: {}", e)))?;
194 let c = buf[0];
195
196 self.input_state = match self.input_state {
197 InputType::Escape => {
198 self.escape_buffer.push(c);
199 if c == b'[' {
200 InputType::EscapeSequence
201 } else {
202 self.escape_buffer.clear();
203 InputType::Normal
204 }
205 }
206 InputType::EscapeSequence => {
207 self.escape_buffer.push(c);
208 if self.escape_buffer.len() == 2 && self.escape_buffer[0] == b'[' {
209 let final_byte = c;
210 self.handle_escape_sequence(final_byte)?;
211 self.escape_buffer.clear();
212 InputType::Normal
213 } else {
214 InputType::EscapeSequence
215 }
216 }
217 InputType::Normal => match self.handle_normal_input(c)? {
218 ReplState::Break => {
219 let finished_line = self
220 .get_line(self.current_line.saturating_sub(1))
221 .map(|l| l.text.clone())
222 .unwrap_or_default();
223 output = Some((self.process_line)(finished_line)?);
224
225 self.lines.push(Line::new());
226 self.current_line = self.lines.len() - 1;
227
228 break;
229 }
230 ReplState::Continue => self.input_state,
231 },
232 };
233 }
234
235 Ok(output.unwrap_or_default())
236 }
237
238 fn handle_escape_sequence(&mut self, c: u8) -> Result<()> {
240 match c {
241 b'A' => {
242 if self.current_line > 0 {
244 self.current_line -= 1;
245 self.redraw_current_line()?;
246 }
247 }
248 b'B' => {
249 if self.current_line + 1 < self.lines.len() {
251 self.current_line += 1;
252 self.redraw_current_line()?;
253 } else {
254 self.lines.push(Line::new());
255 self.current_line = self.lines.len() - 1;
256 self.redraw_current_line()?;
257 }
258 }
259 b'C' => {
260 if let Some(line) = self.lines.get_mut(self.current_line) {
262 line.move_right();
263 self.redraw_current_line()?;
264 }
265 }
266 b'D' => {
267 if let Some(line) = self.lines.get_mut(self.current_line) {
269 line.move_left();
270 self.redraw_current_line()?;
271 }
272 }
273 _ => {}
274 }
275
276 self.escape_buffer.clear();
277 self.input_state = InputType::Normal;
278
279 Ok(())
280 }
281
282 fn handle_normal_input(&mut self, c: u8) -> Result<ReplState> {
284 let current_line = self
285 .lines
286 .get_mut(self.current_line)
287 .ok_or_else(|| Error::ProcessLine("no active line".into()))?;
288
289 match c {
290 b'\n' | b'\r' => {
291 if (self.is_line_complete)(current_line.text.clone()) {
293 println!();
294 Ok(ReplState::Break)
295 } else {
296 current_line.insert_char('\n');
297 Ok(ReplState::Continue)
298 }
299 }
300 0x7F => {
301 current_line.backspace();
303 self.redraw_current_line()?;
304 Ok(ReplState::Continue)
305 }
306 0x01 => {
307 current_line.cursor_pos = 0;
309 self.redraw_current_line()?;
310 Ok(ReplState::Continue)
311 }
312 0x05 => {
313 current_line.cursor_pos = current_line.text.len();
315 self.redraw_current_line()?;
316 Ok(ReplState::Continue)
317 }
318 0x1B => {
319 self.input_state = InputType::Escape;
321 Ok(ReplState::Continue)
322 }
323 c if c.is_ascii_control() => Ok(ReplState::Continue),
324 c => {
325 current_line.insert_char(c as char);
326 self.redraw_current_line()?;
327 Ok(ReplState::Continue)
328 }
329 }
330 }
331
332 fn redraw_current_line(&mut self) -> Result<()> {
334 let line = self
335 .lines
336 .get(self.current_line)
337 .ok_or_else(|| Error::ProcessLine("no active line for redraw".into()))?;
338
339 print!("\r{}{}\x1b[K", self.prompt, line.text);
340 let right_after_prompt = self.prompt.len() + line.cursor_pos;
341 let total_len = self.prompt.len() + line.text.len();
342 if total_len > right_after_prompt {
343 print!("\x1b[{}D", total_len - right_after_prompt);
344 }
345
346 self.tmanager
347 .flush()
348 .map_err(|_| Error::IoFlush("unable to flush stdout".into()))?;
349 Ok(())
350 }
351}