rustpython_vm/
readline.rs1use std::{io, path::Path};
2
3type OtherError = Box<dyn std::error::Error>;
4type OtherResult<T> = Result<T, OtherError>;
5
6pub enum ReadlineResult {
7 Line(String),
8 Eof,
9 Interrupt,
10 Io(std::io::Error),
11 Other(OtherError),
12}
13
14#[allow(unused)]
15mod basic_readline {
16 use super::*;
17
18 pub trait Helper {}
19 impl<T> Helper for T {}
20
21 pub struct Readline<H: Helper> {
22 helper: H,
23 }
24
25 impl<H: Helper> Readline<H> {
26 pub fn new(helper: H) -> Self {
27 Readline { helper }
28 }
29
30 pub fn load_history(&mut self, _path: &Path) -> OtherResult<()> {
31 Ok(())
32 }
33
34 pub fn save_history(&mut self, _path: &Path) -> OtherResult<()> {
35 Ok(())
36 }
37
38 pub fn add_history_entry(&mut self, _entry: &str) -> OtherResult<()> {
39 Ok(())
40 }
41
42 pub fn readline(&mut self, prompt: &str) -> ReadlineResult {
43 use std::io::prelude::*;
44 print!("{prompt}");
45 if let Err(e) = io::stdout().flush() {
46 return ReadlineResult::Io(e);
47 }
48
49 let next_line = io::stdin().lock().lines().next();
50 match next_line {
51 Some(Ok(line)) => ReadlineResult::Line(line),
52 None => ReadlineResult::Eof,
53 Some(Err(e)) if e.kind() == io::ErrorKind::Interrupted => ReadlineResult::Interrupt,
54 Some(Err(e)) => ReadlineResult::Io(e),
55 }
56 }
57 }
58}
59
60#[cfg(not(target_arch = "wasm32"))]
61mod rustyline_readline {
62 use super::*;
63
64 pub trait Helper: rustyline::Helper {}
65 impl<T: rustyline::Helper> Helper for T {}
66
67 pub struct Readline<H: Helper> {
69 repl: rustyline::Editor<H, rustyline::history::DefaultHistory>,
70 }
71
72 impl<H: Helper> Readline<H> {
73 pub fn new(helper: H) -> Self {
74 use rustyline::*;
75 let mut repl = Editor::with_config(
76 Config::builder()
77 .completion_type(CompletionType::List)
78 .tab_stop(8)
79 .bracketed_paste(false) .build(),
81 )
82 .expect("failed to initialize line editor");
83 repl.set_helper(Some(helper));
84 Readline { repl }
85 }
86
87 pub fn load_history(&mut self, path: &Path) -> OtherResult<()> {
88 self.repl.load_history(path)?;
89 Ok(())
90 }
91
92 pub fn save_history(&mut self, path: &Path) -> OtherResult<()> {
93 if !path.exists() {
94 if let Some(parent) = path.parent() {
95 std::fs::create_dir_all(parent)?;
96 }
97 }
98 self.repl.save_history(path)?;
99 Ok(())
100 }
101
102 pub fn add_history_entry(&mut self, entry: &str) -> OtherResult<()> {
103 self.repl.add_history_entry(entry)?;
104 Ok(())
105 }
106
107 pub fn readline(&mut self, prompt: &str) -> ReadlineResult {
108 use rustyline::error::ReadlineError;
109 loop {
110 break match self.repl.readline(prompt) {
111 Ok(line) => ReadlineResult::Line(line),
112 Err(ReadlineError::Interrupted) => ReadlineResult::Interrupt,
113 Err(ReadlineError::Eof) => ReadlineResult::Eof,
114 Err(ReadlineError::Io(e)) => ReadlineResult::Io(e),
115 Err(ReadlineError::WindowResized) => continue,
116 Err(e) => ReadlineResult::Other(e.into()),
117 };
118 }
119 }
120 }
121}
122
123#[cfg(target_arch = "wasm32")]
124use basic_readline as readline_inner;
125#[cfg(not(target_arch = "wasm32"))]
126use rustyline_readline as readline_inner;
127
128pub use readline_inner::Helper;
129
130pub struct Readline<H: Helper>(readline_inner::Readline<H>);
131
132impl<H: Helper> Readline<H> {
133 pub fn new(helper: H) -> Self {
134 Readline(readline_inner::Readline::new(helper))
135 }
136 pub fn load_history(&mut self, path: &Path) -> OtherResult<()> {
137 self.0.load_history(path)
138 }
139 pub fn save_history(&mut self, path: &Path) -> OtherResult<()> {
140 self.0.save_history(path)
141 }
142 pub fn add_history_entry(&mut self, entry: &str) -> OtherResult<()> {
143 self.0.add_history_entry(entry)
144 }
145 pub fn readline(&mut self, prompt: &str) -> ReadlineResult {
146 self.0.readline(prompt)
147 }
148}