1use ansi_term::{Color, Style};
2use rustyline::completion::{Completer, FilenameCompleter, Pair};
3use rustyline::error::ReadlineError;
4use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
5use rustyline::hint::{Hinter, HistoryHinter};
6use rustyline::validate::Validator;
7use rustyline::validate::{self, MatchingBracketValidator};
8use rustyline::Editor;
9use rustyline::{CompletionType, Config, Context};
10use rustyline_derive::Helper;
11use std::borrow::Cow::{self, Borrowed, Owned};
12
13use crate::Locker;
14
15use crate::{parse, CallSnapshot, Environment};
16
17#[derive(Helper)]
18struct ReplHelper {
19 highlighter: MatchingBracketHighlighter,
20 validator: MatchingBracketValidator,
21 hinter: HistoryHinter,
22 colored_prompt: String,
23 completer: FilenameCompleter,
24}
25
26impl Completer for ReplHelper {
27 type Candidate = Pair;
28
29 fn complete(
30 &self,
31 line: &str,
32 pos: usize,
33 ctx: &Context<'_>,
34 ) -> Result<(usize, Vec<Pair>), ReadlineError> {
35 self.completer.complete(line, pos, ctx)
36 }
37}
38
39impl Hinter for ReplHelper {
40 fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option<String> {
41 self.hinter.hint(line, pos, ctx)
42 }
43}
44
45impl Highlighter for ReplHelper {
46 fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
47 &'s self,
48 prompt: &'p str,
49 default: bool,
50 ) -> Cow<'b, str> {
51 if default {
52 Borrowed(&self.colored_prompt)
53 } else {
54 Borrowed(prompt)
55 }
56 }
57
58 fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
59 Owned(Style::new().dimmed().paint(hint).to_string())
60 }
61
62 fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
63 self.highlighter.highlight(line, pos)
64 }
65
66 fn highlight_char(&self, line: &str, pos: usize) -> bool {
67 self.highlighter.highlight_char(line, pos)
68 }
69}
70
71impl Validator for ReplHelper {
72 fn validate(
73 &self,
74 ctx: &mut validate::ValidationContext,
75 ) -> rustyline::Result<validate::ValidationResult> {
76 self.validator.validate(ctx)
77 }
78
79 fn validate_while_typing(&self) -> bool {
80 self.validator.validate_while_typing()
81 }
82}
83
84pub fn spawn(env: Locker<Environment>) {
85 let config = Config::builder()
86 .history_ignore_space(true)
87 .completion_type(CompletionType::List)
88 .build();
89 let h = ReplHelper {
90 completer: FilenameCompleter::new(),
91 highlighter: MatchingBracketHighlighter::new(),
92 hinter: HistoryHinter {},
93 colored_prompt: "".to_owned(),
94 validator: MatchingBracketValidator::new(),
95 };
96 let mut rl = Editor::with_config(config);
97 rl.set_helper(Some(h));
98
99 if rl.load_history(".turtle_history.txt").is_err() {
100 println!("It looks like this is your first time running Turtle from this directory; no history was loaded.")
101 }
102
103 loop {
104 let p = "🐢 > ".to_string();
105 rl.helper_mut().expect("No helper").colored_prompt =
106 Color::Green.bold().paint(&p).to_string();
107 let line = rl.readline(&p);
108 match line {
109 Ok(line) => {
110 rl.add_history_entry(line.as_str());
111 match parse(line.as_str(), "<stdin>") {
112 Ok(values) => {
113 for value in values {
114 let snapshot = CallSnapshot::root(&value.clone());
115 match value
116 .eval_async(snapshot, env.clone())
117 .unwrap()
118 .recv()
119 .unwrap()
120 {
121 Ok(result) => println!(
122 " {} {}",
123 Color::Blue.bold().paint("="),
124 Style::default().bold().paint(format!("{}", result))
125 ),
126 Err(error) => eprintln!("{}", error),
127 }
128 }
129 }
130 Err(err) => eprintln!("{:#}", err),
131 }
132 }
133 Err(ReadlineError::Interrupted) => break,
134 Err(ReadlineError::Eof) => break,
135 Err(err) => {
136 eprintln!("Error: {:?}", err);
137 break;
138 }
139 }
140 rl.save_history(".turtle_history.txt").unwrap();
141 }
142}