rustpython_vm/
readline.rs

1use 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    /// Readline: the REPL
68    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) // multi-line paste
80                    .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}