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