1#![warn(missing_docs)]
2use crossterm::{
8 cursor::MoveToColumn,
9 event::{self, Event, KeyCode, KeyModifiers},
10 execute,
11 terminal::{self, Clear, ClearType},
12};
13use oak_highlight::{AnsiExporter, Exporter, HighlightResult, OakHighlighter};
14use std::io::{self, Write};
15
16use std::{
17 error::Error,
18 fmt::{Display, Formatter},
19};
20
21#[derive(Debug)]
23pub enum ReplError {
24 Io(std::io::Error),
26 Other(String),
28}
29
30impl Display for ReplError {
31 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
32 match self {
33 ReplError::Io(e) => write!(f, "IO error: {}", e),
34 ReplError::Other(s) => write!(f, "{}", s),
35 }
36 }
37}
38
39impl Error for ReplError {}
40
41impl From<std::io::Error> for ReplError {
42 fn from(e: std::io::Error) -> Self {
43 ReplError::Io(e)
44 }
45}
46
47impl From<String> for ReplError {
48 fn from(s: String) -> Self {
49 ReplError::Other(s)
50 }
51}
52
53impl From<&str> for ReplError {
54 fn from(s: &str) -> Self {
55 ReplError::Other(s.to_string())
56 }
57}
58
59pub enum HandleResult {
61 Continue,
63 Exit,
65}
66
67pub trait ReplHandler {
69 fn highlight<'a>(&self, _code: &'a str) -> Option<HighlightResult<'a>> {
71 None
72 }
73
74 fn prompt(&self, is_continuation: bool) -> &str;
76
77 fn is_complete(&self, code: &str) -> bool;
80
81 fn handle_line(&mut self, line: &str) -> Result<HandleResult, ReplError>;
83
84 fn get_indent(&self, _code: &str) -> usize {
86 0
88 }
89}
90
91pub struct LineBuffer {
93 lines: Vec<String>,
95 current_line: usize,
97 cursor_pos: usize,
99}
100
101impl LineBuffer {
102 pub fn new() -> Self {
104 Self { lines: vec![String::new()], current_line: 0, cursor_pos: 0 }
105 }
106
107 pub fn insert(&mut self, ch: char) {
109 self.lines[self.current_line].insert(self.cursor_pos, ch);
110 self.cursor_pos += 1;
111 }
112
113 pub fn backspace(&mut self) -> bool {
115 if self.cursor_pos > 0 {
116 self.cursor_pos -= 1;
117 self.lines[self.current_line].remove(self.cursor_pos);
118 true
119 }
120 else if self.current_line > 0 {
121 let current = self.lines.remove(self.current_line);
123 self.current_line -= 1;
124 self.cursor_pos = self.lines[self.current_line].chars().count();
125 self.lines[self.current_line].push_str(¤t);
126 true
127 }
128 else {
129 false
130 }
131 }
132
133 pub fn full_text(&self) -> String {
135 self.lines.join("\n")
136 }
137
138 pub fn clear(&mut self) {
140 self.lines = vec![String::new()];
141 self.current_line = 0;
142 self.cursor_pos = 0;
143 }
144
145 pub fn is_empty(&self) -> bool {
147 self.lines.len() == 1 && self.lines[0].is_empty()
148 }
149}
150
151pub struct OakRepl<H: ReplHandler> {
153 handler: H,
155}
156
157impl<H: ReplHandler> OakRepl<H> {
158 pub fn new(handler: H) -> Self {
160 Self { handler }
161 }
162
163 pub fn run(&mut self) -> Result<(), ReplError> {
165 let mut stdout = io::stdout();
166 let mut line_buf = LineBuffer::new();
167 let mut is_continuation = false;
168 let _highlighter = OakHighlighter::new();
169 let exporter = AnsiExporter;
170
171 terminal::enable_raw_mode()?;
172
173 loop {
174 execute!(stdout, MoveToColumn(0), Clear(ClearType::CurrentLine))?;
176 let prompt = self.handler.prompt(is_continuation);
177
178 let current_line_text = &line_buf.lines[line_buf.current_line];
179
180 let displayed_text = if let Some(highlighted) = self.handler.highlight(current_line_text) { exporter.export(&highlighted) } else { current_line_text.clone() };
182
183 write!(stdout, "{}{}", prompt, displayed_text)?;
184
185 let cursor_col = (prompt.chars().count() + line_buf.cursor_pos) as u16;
186 execute!(stdout, MoveToColumn(cursor_col))?;
187 stdout.flush()?;
188
189 if let Event::Key(key_event) = event::read()? {
190 match key_event.code {
191 KeyCode::Char('c') if key_event.modifiers.contains(KeyModifiers::CONTROL) => {
192 println!("\nInterrupted");
193 line_buf.clear();
194 is_continuation = false;
195 continue;
196 }
197 KeyCode::Char('d') if key_event.modifiers.contains(KeyModifiers::CONTROL) => {
198 if line_buf.is_empty() {
199 println!("\nEOF");
200 break;
201 }
202 }
203 KeyCode::Char(ch) => {
204 line_buf.insert(ch);
205 }
206 KeyCode::Enter => {
207 let full_code = line_buf.full_text();
208
209 if self.handler.is_complete(&full_code) {
210 terminal::disable_raw_mode()?;
211 println!();
212
213 match self.handler.handle_line(&full_code) {
214 Ok(HandleResult::Exit) => break,
215 Ok(HandleResult::Continue) => {}
216 Err(e) => eprintln!("Error: {}", e),
217 }
218
219 line_buf.clear();
220 is_continuation = false;
221 terminal::enable_raw_mode()?;
222 }
223 else {
224 println!();
226 line_buf.lines.push(String::new());
227 line_buf.current_line += 1;
228 line_buf.cursor_pos = 0;
229 is_continuation = true;
230
231 let indent_size = self.handler.get_indent(&full_code);
233 for _ in 0..indent_size {
234 line_buf.insert(' ');
235 }
236 }
237 }
238 KeyCode::Backspace => {
239 line_buf.backspace();
240 }
241 KeyCode::Left => {
242 if line_buf.cursor_pos > 0 {
243 line_buf.cursor_pos -= 1;
244 }
245 }
246 KeyCode::Right => {
247 if line_buf.cursor_pos < line_buf.lines[line_buf.current_line].chars().count() {
248 line_buf.cursor_pos += 1
249 }
250 }
251 _ => {}
252 }
253 }
254 }
255
256 terminal::disable_raw_mode()?;
257 Ok(())
258 }
259}