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