1use rustyline::error::ReadlineError;
16use rustyline::{Cmd, CompletionType, Config, EditMode, Editor, KeyEvent};
17
18use super::NarjuIo;
19use crate::error::NarjuError;
20use crate::repl::highlight::NarjuHelperLite;
21
22const PURPLE_HISTORY_FILE: &str = ".narju_purple_history";
25
26pub struct TerminalIo {
29 editor: Editor<NarjuHelperLite, rustyline::history::DefaultHistory>,
31 history_path: Option<std::path::PathBuf>,
33}
34
35fn home_dir() -> Option<std::path::PathBuf> {
38 std::env::var_os("HOME")
39 .or_else(|| std::env::var_os("USERPROFILE"))
40 .map(std::path::PathBuf::from)
41}
42
43impl TerminalIo {
44 pub fn new() -> Result<Self, NarjuError> {
49 let config = Config::builder()
50 .history_ignore_space(true)
51 .completion_type(CompletionType::List)
52 .edit_mode(EditMode::Emacs)
53 .max_history_size(1000)
54 .map_err(|e| {
55 NarjuError::Io(std::io::Error::other(format!("rustyline config: {e}")))
56 })?
57 .build();
58
59 let mut editor: Editor<NarjuHelperLite, _> = Editor::with_config(config).map_err(|e| {
60 NarjuError::Io(std::io::Error::other(format!("readline init: {e}")))
61 })?;
62 editor.set_helper(Some(NarjuHelperLite));
63
64 editor.bind_sequence(KeyEvent::alt('p'), Cmd::HistorySearchBackward);
65 editor.bind_sequence(KeyEvent::alt('n'), Cmd::HistorySearchForward);
66
67 let history_path = home_dir().map(|h| h.join(PURPLE_HISTORY_FILE));
68 if let Some(ref p) = history_path {
69 let _ = editor.load_history(p);
70 }
71
72 Ok(Self {
73 editor,
74 history_path,
75 })
76 }
77
78 pub fn save_history(&mut self) {
81 if let Some(ref p) = self.history_path {
82 let _ = self.editor.save_history(p);
83 }
84 }
85}
86
87impl NarjuIo for TerminalIo {
88 fn read_line(&mut self, prompt: &str) -> Result<Option<String>, NarjuError> {
89 let result = self.editor.readline(prompt);
90 match &result {
93 Ok(line) => {
94 if !line.trim().is_empty() {
95 self.editor.add_history_entry(line).ok();
96 }
97 self.save_history();
98 }
99 Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
100 self.save_history();
101 }
102 _ => {}
103 }
104 match result {
105 Ok(line) => Ok(Some(line)),
106 Err(ReadlineError::Interrupted) => Ok(None),
107 Err(ReadlineError::Eof) => Ok(None),
108 Err(e) => Err(NarjuError::Io(std::io::Error::other(format!(
109 "readline: {e}"
110 )))),
111 }
112 }
113
114 fn print(&mut self, s: &str) -> Result<(), NarjuError> {
115 println!("{s}");
116 Ok(())
117 }
118}