rustpython_vm/
readline.rs1use std::{io, path::Path};
7
8type OtherError = Box<dyn core::error::Error>;
9type OtherResult<T> = Result<T, OtherError>;
10
11pub enum ReadlineResult {
12 Line(String),
13 Eof,
14 Interrupt,
15 Io(std::io::Error),
16 #[cfg(unix)]
17 OsError(nix::Error),
18 Other(OtherError),
19}
20
21#[allow(unused)]
22mod basic_readline {
23 use super::*;
24
25 pub trait Helper {}
26 impl<T> Helper for T {}
27
28 pub struct Readline<H: Helper> {
29 helper: H,
30 }
31
32 impl<H: Helper> Readline<H> {
33 pub const fn new(helper: H) -> Self {
34 Self { helper }
35 }
36
37 pub fn load_history(&mut self, _path: &Path) -> OtherResult<()> {
38 Ok(())
39 }
40
41 pub fn save_history(&mut self, _path: &Path) -> OtherResult<()> {
42 Ok(())
43 }
44
45 pub fn add_history_entry(&mut self, _entry: &str) -> OtherResult<()> {
46 Ok(())
47 }
48
49 pub fn readline(&mut self, prompt: &str) -> ReadlineResult {
50 use std::io::prelude::*;
51 print!("{prompt}");
52 if let Err(e) = io::stdout().flush() {
53 return ReadlineResult::Io(e);
54 }
55
56 let next_line = io::stdin().lock().lines().next();
57 match next_line {
58 Some(Ok(line)) => ReadlineResult::Line(line),
59 None => ReadlineResult::Eof,
60 Some(Err(e)) if e.kind() == io::ErrorKind::Interrupted => ReadlineResult::Interrupt,
61 Some(Err(e)) => ReadlineResult::Io(e),
62 }
63 }
64 }
65}
66
67#[cfg(not(target_arch = "wasm32"))]
68mod rustyline_readline {
69 use super::*;
70
71 pub trait Helper: rustyline::Helper {}
72 impl<T: rustyline::Helper> Helper for T {}
73
74 pub struct Readline<H: Helper> {
76 repl: rustyline::Editor<H, rustyline::history::DefaultHistory>,
77 }
78
79 #[cfg(windows)]
80 const EOF_CHAR: &str = "\u{001A}";
81
82 impl<H: Helper> Readline<H> {
83 pub fn new(helper: H) -> Self {
84 use rustyline::*;
85 let mut repl = Editor::with_config(
86 Config::builder()
87 .completion_type(CompletionType::List)
88 .tab_stop(8)
89 .bracketed_paste(false) .build(),
91 )
92 .expect("failed to initialize line editor");
93 repl.set_helper(Some(helper));
94
95 #[cfg(windows)]
97 {
98 repl.bind_sequence(
99 KeyEvent::new('z', Modifiers::CTRL),
100 EventHandler::Simple(Cmd::Insert(1, EOF_CHAR.into())),
101 );
102 }
103
104 Self { repl }
105 }
106
107 pub fn load_history(&mut self, path: &Path) -> OtherResult<()> {
108 self.repl.load_history(path)?;
109 Ok(())
110 }
111
112 pub fn save_history(&mut self, path: &Path) -> OtherResult<()> {
113 if !path.exists()
114 && let Some(parent) = path.parent()
115 {
116 std::fs::create_dir_all(parent)?;
117 }
118 self.repl.save_history(path)?;
119 Ok(())
120 }
121
122 pub fn add_history_entry(&mut self, entry: &str) -> OtherResult<()> {
123 self.repl.add_history_entry(entry)?;
124 Ok(())
125 }
126
127 pub fn readline(&mut self, prompt: &str) -> ReadlineResult {
128 use rustyline::error::ReadlineError;
129 loop {
130 break match self.repl.readline(prompt) {
131 Ok(line) => {
132 #[cfg(windows)]
134 {
135 use std::io::IsTerminal;
136
137 let trimmed = line.trim_end_matches(&['\r', '\n'][..]);
138 if trimmed == EOF_CHAR && io::stdin().is_terminal() {
139 return ReadlineResult::Eof;
140 }
141 }
142 ReadlineResult::Line(line)
143 }
144 Err(ReadlineError::Interrupted) => ReadlineResult::Interrupt,
145 Err(ReadlineError::Eof) => ReadlineResult::Eof,
146 Err(ReadlineError::Io(e)) => ReadlineResult::Io(e),
147 Err(ReadlineError::Signal(_)) => continue,
148 #[cfg(unix)]
149 Err(ReadlineError::Errno(num)) => ReadlineResult::OsError(num),
150 Err(e) => ReadlineResult::Other(e.into()),
151 };
152 }
153 }
154 }
155}
156
157#[cfg(target_arch = "wasm32")]
158use basic_readline as readline_inner;
159#[cfg(not(target_arch = "wasm32"))]
160use rustyline_readline as readline_inner;
161
162pub use readline_inner::Helper;
163
164pub struct Readline<H: Helper>(readline_inner::Readline<H>);
165
166impl<H: Helper> Readline<H> {
167 pub fn new(helper: H) -> Self {
168 Self(readline_inner::Readline::new(helper))
169 }
170
171 pub fn load_history(&mut self, path: &Path) -> OtherResult<()> {
172 self.0.load_history(path)
173 }
174
175 pub fn save_history(&mut self, path: &Path) -> OtherResult<()> {
176 self.0.save_history(path)
177 }
178
179 pub fn add_history_entry(&mut self, entry: &str) -> OtherResult<()> {
180 self.0.add_history_entry(entry)
181 }
182
183 pub fn readline(&mut self, prompt: &str) -> ReadlineResult {
184 self.0.readline(prompt)
185 }
186}