1mod history;
2pub(crate) mod iter;
3
4use history::History;
5
6use crate::lang::{DefaultLangInterface, LangInterface};
7use crossterm::{cursor, event, execute, queue, style, terminal};
8use std::cmp::min;
9use std::io::prelude::*;
10use std::marker::PhantomData;
11use std::path::PathBuf;
12
13pub struct Repl<L: LangInterface = DefaultLangInterface> {
43 history: History,
45 leader: &'static str,
50 leader_len: usize,
53 continued_leader: &'static str,
59 continued_leader_len: usize,
62 exit_keyword: &'static str,
64 clear_keyword: &'static str,
66 _lang_interface: PhantomData<L>,
67}
68
69impl Repl<DefaultLangInterface> {
70 pub fn newd(
72 leader: &'static str,
73 continued_leader: &'static str,
74 path: Option<PathBuf>,
75 ) -> Self {
76 Self::with_capacity(leader, continued_leader, 64, path)
77 }
78
79 pub fn with_capacityd(
81 leader: &'static str,
82 continued_leader: &'static str,
83 capacity: usize,
84 path: Option<PathBuf>,
85 ) -> Self {
86 let should_persist = path.is_some();
87
88 let mut repl = Self {
89 history: History::with_capacity(capacity, path),
90 leader,
91 leader_len: leader.chars().count(),
92 continued_leader,
93 continued_leader_len: leader.chars().count(),
94 exit_keyword: "exit",
95 clear_keyword: "clear",
96 _lang_interface: PhantomData,
97 };
98
99 if should_persist {
100 let _ = repl.history.read_from_file();
101 }
102
103 repl
104 }
105}
106
107impl<L: LangInterface> Repl<L> {
108 pub fn new(
110 leader: &'static str,
111 continued_leader: &'static str,
112 path: Option<PathBuf>,
113 ) -> Self {
114 Self::with_capacity(leader, continued_leader, 64, path)
115 }
116
117 pub fn with_capacity(
119 leader: &'static str,
120 continued_leader: &'static str,
121 capacity: usize,
122 path: Option<PathBuf>,
123 ) -> Self {
124 let should_persist = path.is_some();
125
126 let mut repl = Self {
127 history: History::with_capacity(capacity, path),
128 leader,
129 leader_len: leader.chars().count(),
130 continued_leader,
131 continued_leader_len: leader.chars().count(),
132 exit_keyword: "exit",
133 clear_keyword: "clear",
134 _lang_interface: PhantomData,
135 };
136
137 if should_persist {
138 let _ = repl.history.read_from_file();
139 }
140
141 repl
142 }
143
144 pub fn set_exit_keyword(&mut self, exit_keyword: &'static str) {
146 self.exit_keyword = exit_keyword
147 }
148
149 pub fn set_clear_keyword(&mut self, clear_keyword: &'static str) {
151 self.clear_keyword = clear_keyword
152 }
153
154 fn cur<'a>(&'a self, c: &Cursor, lines: &'a [String]) -> &'a [String] {
156 if c.use_history {
157 self.history.cur().unwrap()
160 } else {
161 lines
162 }
163 }
164
165 fn cur_str<'a>(&'a self, c: &Cursor, lines: &'a [String]) -> &'a str {
167 &self.cur(c, lines)[c.lineno]
168 }
169
170 fn replace_with_history(&self, lines: &mut Vec<String>) {
172 let cur = self.history.cur().unwrap();
173 lines.resize(cur.len(), String::new());
174
175 for (i, string) in cur.iter().enumerate() {
176 lines[i].clear();
177 lines[i] += string;
178 }
179
180 self.history.reset_iter();
181 }
182
183 fn pre_exit(&self) {
184 let _ = terminal::disable_raw_mode();
185 println!();
186 let _ = self.history.write_to_file();
187 }
188
189 fn exit(&self) -> ! {
190 self.pre_exit();
191 std::process::exit(0)
192 }
193
194 fn print_lines(
196 &self,
197 stdout: &mut std::io::Stdout,
198 c: &mut Cursor,
199 lines: &[String],
200 colour: style::Color,
201 ) -> crate::Result<()> {
202 if c.lineno > 0 {
203 queue!(stdout, cursor::MoveUp(c.lineno as u16))?;
204 }
205
206 queue!(
207 stdout,
208 terminal::Clear(terminal::ClearType::CurrentLine),
209 terminal::Clear(terminal::ClearType::FromCursorDown),
210 )?;
211 let mut is_first = true;
212
213 for index in 0..lines.len() {
214 let leader = if is_first {
215 is_first = false;
216 self.leader
217 } else {
218 self.continued_leader
219 };
220
221 queue!(
222 stdout,
223 cursor::MoveToColumn(0),
224 style::SetForegroundColor(colour),
225 style::Print(leader),
226 )?;
227 L::print_line(stdout, lines, index)?;
228 queue!(stdout, style::Print("\n"))?;
229 }
230
231 let leader_len = if c.lineno == 0 {
232 self.leader_len
233 } else {
234 self.continued_leader_len
235 };
236
237 c.charno = min(c.charno, lines[c.lineno].chars().count());
238
239 execute!(
240 stdout,
241 cursor::MoveUp((lines.len() - c.lineno) as u16),
242 cursor::MoveToColumn((leader_len + c.charno) as u16)
243 )
244 }
245
246 pub fn next(&mut self, colour: style::Color) -> crate::Result<String> {
248 let mut stdout = std::io::stdout();
249 let mut lines = Vec::new();
250 lines.push(String::new());
251
252 let mut c = Cursor::default();
253
254 terminal::enable_raw_mode()?;
255
256 execute!(
257 stdout,
258 style::SetForegroundColor(colour),
259 style::Print(self.leader),
260 style::ResetColor
261 )?;
262
263 loop {
264 if let event::Event::Key(e) = event::read()? {
265 match e.code {
266 event::KeyCode::Char('c')
267 if e.modifiers.contains(event::KeyModifiers::CONTROL) =>
268 {
269 self.exit()
270 }
271 event::KeyCode::Char('l')
272 if e.modifiers.contains(event::KeyModifiers::CONTROL) =>
273 {
274 let lineno = c.lineno;
275 c.lineno = 0;
276
277 queue!(
278 stdout,
279 terminal::Clear(terminal::ClearType::All),
280 cursor::MoveTo(0, 0)
281 )?;
282 let lines = self.cur(&c, &lines);
283 self.print_lines(&mut stdout, &mut c, lines, colour)?;
284 c.lineno = lineno;
285 c.charno = min(c.charno, lines[c.lineno].chars().count());
286
287 if c.lineno > 0 {
288 queue!(stdout, cursor::MoveDown(lineno as u16))?;
289 }
290 queue!(
291 stdout,
292 cursor::MoveToColumn((self.continued_leader_len + c.charno) as u16),
293 )?;
294 }
295 event::KeyCode::Char(chr) => {
296 if c.use_history {
297 self.replace_with_history(&mut lines);
298 c.use_history = false;
299 };
300
301 let byte_i = get_byte_i(&lines[c.lineno], c.charno);
302
303 lines[c.lineno].insert(byte_i, chr);
304 c.charno += 1;
305 }
306 event::KeyCode::Tab => {
307 if c.use_history {
308 self.replace_with_history(&mut lines);
309 c.use_history = false;
310 };
311
312 lines[c.lineno].insert_str(c.charno, " ");
313 c.charno += 4;
314 }
315
316 event::KeyCode::Home => {
317 c.charno = 0;
318 }
319 event::KeyCode::End => {
320 c.charno = self.cur_str(&c, &lines).chars().count();
321 }
322 event::KeyCode::Left if c.charno > 0 => {
323 c.charno -= 1;
324 }
325 event::KeyCode::Right => {
326 if c.charno < self.cur_str(&c, &lines).chars().count() {
327 c.charno += 1;
328 };
329 }
330
331 event::KeyCode::PageUp => history_up!(retain self, stdout, c, lines, colour),
332 event::KeyCode::Up if c.lineno == 0 => {
334 history_up!(self, stdout, c, lines, colour)
335 }
336 event::KeyCode::Up => {
338 c.lineno -= 1;
339 queue!(stdout, cursor::MoveUp(1))?;
340 c.charno = min(self.cur_str(&c, &lines).chars().count(), c.charno);
341 }
342
343 event::KeyCode::PageDown => {
344 history_down!(retain self, stdout, c, lines, colour)
345 }
346 event::KeyCode::Down
350 if c.use_history && (c.lineno + 1) == self.history.cur().unwrap().len() =>
351 {
352 history_down!(self, stdout, c, lines, colour)
353 }
354 event::KeyCode::Down if !c.use_history && (c.lineno + 1) == lines.len() => {}
356 event::KeyCode::Down => {
358 c.lineno += 1;
359 queue!(stdout, cursor::MoveDown(1))?;
360 c.charno = min(self.cur_str(&c, &lines).chars().count(), c.charno);
361 }
362
363 event::KeyCode::Backspace if c.charno > 0 => {
365 if c.use_history {
366 self.replace_with_history(&mut lines);
367 c.use_history = false;
368 };
369
370 c.charno -= 1;
371 let byte_i = get_byte_i(&lines[c.lineno], c.charno);
372 lines[c.lineno].remove(byte_i);
373 }
374 event::KeyCode::Backspace if c.lineno > 0 => {
376 if c.use_history {
377 self.replace_with_history(&mut lines);
378 c.use_history = false;
379 };
380
381 c.lineno -= 1;
382 c.charno = lines[c.lineno].chars().count();
383 let line = lines.remove(c.lineno + 1);
384 lines[c.lineno] += &line;
385
386 execute!(stdout, cursor::MoveUp(1))?;
387 self.print_lines(&mut stdout, &mut c, &lines, colour)?;
388 }
389
390 event::KeyCode::Delete
392 if c.charno < self.cur_str(&c, &lines).chars().count() =>
393 {
394 if c.use_history {
395 self.replace_with_history(&mut lines);
396 c.use_history = false;
397 };
398
399 let byte_i = get_byte_i(&lines[c.lineno], c.charno);
400 lines[c.lineno].remove(byte_i);
401 }
402 event::KeyCode::Delete if (c.lineno + 1) < self.cur(&c, &lines).len() => {
403 if c.use_history {
404 self.replace_with_history(&mut lines);
405 c.use_history = false;
406 };
407
408 let line = lines.remove(c.lineno + 1);
409 lines[c.lineno] += &line;
410
411 self.print_lines(&mut stdout, &mut c, &lines, colour)?;
412 }
413
414 event::KeyCode::Enter => {
415 if self.cur(&c, &lines[..])[0].trim().is_empty() {
416 execute!(
417 stdout,
418 cursor::MoveToNextLine(1),
419 style::SetForegroundColor(colour),
420 style::Print(self.leader)
421 )?;
422 continue;
424 }
425
426 if !c.use_history && lines.len() == 1 {
427 if lines[0] == self.exit_keyword {
428 self.exit();
429 } else if lines[0] == self.clear_keyword {
430 c.charno = 0;
431 lines[0].clear();
432
433 execute!(
434 stdout,
435 terminal::Clear(terminal::ClearType::All),
436 cursor::MoveTo(0, 0),
437 style::SetForegroundColor(colour),
438 style::Print(self.leader),
439 style::ResetColor,
440 )?;
441
442 continue;
444 }
445 }
446
447 if c.use_history && (c.lineno + 1) == self.history.cur().unwrap().len() {
448 break;
450 }
451 let indent = L::get_indent(&self.cur(&c, &lines)[0..(c.lineno + 1)]);
452
453 if !c.use_history && (c.lineno + 1) == lines.len() && indent == 0 {
454 break;
456 } else {
457 if c.use_history {
458 self.replace_with_history(&mut lines);
459 c.use_history = false;
460 }
461
462 c.lineno += 1;
463 c.charno = indent;
464 lines.insert(c.lineno, " ".repeat(indent));
465 execute!(stdout, style::Print("\n"))?;
466 self.print_lines(&mut stdout, &mut c, &lines, colour)?;
467 }
468 }
469 _ => {}
470 }
471 };
472
473 queue!(
474 stdout,
475 terminal::Clear(terminal::ClearType::CurrentLine),
476 cursor::MoveToColumn(0),
477 style::SetForegroundColor(colour),
478 )?;
479
480 let (leader, leader_len) = if c.lineno == 0 {
481 (self.leader, self.leader_len)
482 } else {
483 (self.continued_leader, self.continued_leader_len)
484 };
485
486 queue!(stdout, style::Print(leader))?;
487 L::print_line(&mut stdout, self.cur(&c, &lines[..]), c.lineno)?;
488 execute!(
489 stdout,
490 cursor::MoveToColumn((leader_len + c.charno + 1) as u16)
491 )?;
492 }
493
494 terminal::disable_raw_mode()?;
495 println!();
496
497 let src = self.cur(&c, &lines).join("\n");
498
499 if c.use_history {
500 self.history.push(self.history.cur().unwrap().clone());
501 } else {
502 self.history.push(lines);
503 }
504
505 Ok(src)
506 }
507}
508
509impl<L: LangInterface> Drop for Repl<L> {
510 fn drop(&mut self) {
511 self.pre_exit();
512 }
513}
514
515fn get_byte_i(string: &str, i: usize) -> usize {
516 string
517 .char_indices()
518 .nth(i)
519 .map(|c| c.0)
520 .unwrap_or_else(|| string.len())
521}
522
523#[derive(Debug, Default)]
524struct Cursor {
525 use_history: bool,
526 lineno: usize,
527 charno: usize,
528}