1use std::sync::OnceLock;
2
3#[cfg(not(feature = "full-repl"))]
4use std::io::{stdin, BufRead, BufReader};
5
6#[cfg(feature = "full-repl")]
7use crossterm::{
8 cursor::MoveToColumn,
9 event::{read, Event, KeyCode, KeyEvent, KeyModifiers},
10 execute,
11 style::Print,
12 terminal::{disable_raw_mode, enable_raw_mode},
13 terminal::{Clear, ClearType},
14};
15#[cfg(feature = "full-repl")]
16use std::process::Command;
17#[cfg(feature = "full-repl")]
18use std::process::Output;
19
20use crate::shared::Shared;
21
22#[derive(Debug)]
34pub struct StdinReader {
35 block_begin: usize,
36 lineno: usize,
37 buf: Vec<String>,
38 #[cfg(feature = "full-repl")]
39 history_input_position: usize,
40 indent: u16,
41}
42
43impl StdinReader {
44 #[cfg(all(feature = "full-repl", target_os = "linux"))]
45 fn access_clipboard() -> Option<Output> {
46 if let Ok(str) = std::fs::read("/proc/sys/kernel/osrelease") {
47 if let Ok(str) = std::str::from_utf8(&str) {
48 if str.to_ascii_lowercase().contains("microsoft") {
49 return Some(
50 Command::new("powershell")
51 .args(["get-clipboard"])
52 .output()
53 .expect("failed to get clipboard"),
54 );
55 }
56 }
57 }
58 match Command::new("xsel")
59 .args(["--output", "--clipboard"])
60 .output()
61 {
62 Ok(output) => Some(output),
63 Err(_) => {
64 execute!(
65 std::io::stdout(),
66 Print("You need to install `xsel` to use the paste feature on Linux desktop"),
67 )
68 .unwrap();
69 None
70 }
71 }
72 }
73 #[cfg(all(feature = "full-repl", target_os = "macos"))]
74 fn access_clipboard() -> Option<Output> {
75 Some(
76 Command::new("pbpast")
77 .output()
78 .expect("failed to get clipboard"),
79 )
80 }
81
82 #[cfg(all(feature = "full-repl", target_os = "windows"))]
83 fn access_clipboard() -> Option<Output> {
84 Some(
85 Command::new("powershell")
86 .args(["get-clipboard"])
87 .output()
88 .expect("failed to get clipboard"),
89 )
90 }
91
92 #[cfg(not(feature = "full-repl"))]
93 pub fn read(&mut self) -> String {
94 let mut line = "".to_string();
95 let stdin = stdin();
96 let mut reader = BufReader::new(stdin.lock());
97 reader.read_line(&mut line).unwrap();
98 self.lineno += 1;
99 self.buf.push(line.trim_end().to_string());
100 self.buf.last().cloned().unwrap_or_default()
101 }
102
103 #[cfg(feature = "full-repl")]
104 pub fn read(&mut self) -> String {
105 enable_raw_mode().unwrap();
106 let mut output = std::io::stdout();
107 let mut line = String::new();
108 self.input(&mut line).unwrap();
109 disable_raw_mode().unwrap();
110 execute!(output, MoveToColumn(0)).unwrap();
111 self.lineno += 1;
112 self.buf.push(line);
113 self.buf.last().cloned().unwrap_or_default()
114 }
115
116 #[cfg(feature = "full-repl")]
117 fn input(&mut self, line: &mut String) -> std::io::Result<()> {
118 let mut position = 0;
119 let mut consult_history = false;
120 let mut stdout = std::io::stdout();
121 while let Event::Key(KeyEvent {
122 code, modifiers, ..
123 }) = read()?
124 {
125 consult_history = false;
126 match (code, modifiers) {
127 (KeyCode::Char('z'), KeyModifiers::CONTROL)
128 | (KeyCode::Char('d'), KeyModifiers::CONTROL) => {
129 println!();
130 line.clear();
131 line.push_str(":exit");
132 return Ok(());
133 }
134 (KeyCode::Char('v'), KeyModifiers::CONTROL) => {
135 let output = match Self::access_clipboard() {
136 None => {
137 continue;
138 }
139 Some(output) => output,
140 };
141 let clipboard = {
142 let this = String::from_utf8_lossy(&output.stdout).to_string();
143 this.trim_matches(|c: char| c.is_whitespace())
144 .to_string()
145 .replace(['\n', '\r'], "")
146 .replace(|c: char| c.len_utf8() >= 2, "")
147 };
148 line.insert_str(position, &clipboard);
149 position += clipboard.len();
150 }
151 (_, KeyModifiers::CONTROL) => continue,
152 (KeyCode::Tab, _) => {
153 line.insert_str(position, " ");
154 position += 4;
155 }
156 (KeyCode::Home, _) => {
157 position = 0;
158 }
159 (KeyCode::End, _) => {
160 position = line.len();
161 }
162 (KeyCode::Backspace, _) => {
163 if position == 0 {
164 continue;
165 }
166 line.remove(position - 1);
167 position -= 1;
168 }
169 (KeyCode::Delete, _) => {
170 if position == line.len() {
171 continue;
172 }
173 line.remove(position);
174 }
175 (KeyCode::Up, _) => {
176 consult_history = true;
177 if self.history_input_position == 0 {
178 continue;
179 }
180 self.history_input_position -= 1;
181 execute!(stdout, MoveToColumn(4), Clear(ClearType::UntilNewLine))?;
182 if let Some(l) = self.buf.get(self.history_input_position) {
183 position = l.len();
184 line.clear();
185 line.push_str(l);
186 }
187 }
188 (KeyCode::Down, _) => {
189 if self.history_input_position == self.buf.len() {
190 continue;
191 }
192 if self.history_input_position == self.buf.len() - 1 {
193 *line = "".to_string();
194 position = 0;
195 self.history_input_position += 1;
196 execute!(
197 stdout,
198 MoveToColumn(4),
199 Clear(ClearType::UntilNewLine),
200 MoveToColumn(self.indent * 4),
201 Print(line.to_owned()),
202 MoveToColumn(self.indent * 4 + position as u16)
203 )?;
204 continue;
205 }
206 self.history_input_position += 1;
207 execute!(stdout, MoveToColumn(4), Clear(ClearType::UntilNewLine))?;
208 if let Some(l) = self.buf.get(self.history_input_position) {
209 position = l.len();
210 line.clear();
211 line.push_str(l);
212 }
213 }
214 (KeyCode::Left, _) => {
215 if position == 0 {
216 continue;
217 }
218 position -= 1;
219 }
220 (KeyCode::Right, _) => {
221 if position == line.len() {
222 continue;
223 }
224 position += 1;
225 }
226 (KeyCode::Enter, _) => {
227 println!();
228 break;
229 }
230 (KeyCode::Char(c), _) if c.len_utf8() < 2 => {
232 line.insert(position, c);
233 position += 1;
234 }
235 _ => {}
236 }
237 execute!(
238 stdout,
239 MoveToColumn(4),
240 Clear(ClearType::UntilNewLine),
241 MoveToColumn(self.indent * 4),
242 Print(line.to_owned()),
243 MoveToColumn(self.indent * 4 + position as u16)
244 )?;
245 }
246 if !consult_history {
247 self.history_input_position = self.buf.len() + 1;
248 }
249 Ok(())
250 }
251
252 pub fn reread(&self) -> String {
253 self.buf.last().cloned().unwrap_or_default()
254 }
255
256 pub fn reread_lines(&self, ln_begin: usize, ln_end: usize) -> Vec<String> {
257 if let Some(lines) = self.buf.get(ln_begin - 1..=ln_end - 1) {
258 lines.to_vec()
259 } else {
260 self.buf.clone()
261 }
262 }
263
264 pub fn last_line(&mut self) -> Option<&mut String> {
265 self.buf.last_mut()
266 }
267}
268
269#[derive(Debug)]
270pub struct GlobalStdin(OnceLock<Shared<StdinReader>>);
271
272pub static GLOBAL_STDIN: GlobalStdin = GlobalStdin(OnceLock::new());
273
274impl GlobalStdin {
275 fn get(&'static self) -> &'static Shared<StdinReader> {
276 self.0.get_or_init(|| {
277 Shared::new(StdinReader {
278 block_begin: 1,
279 lineno: 1,
280 buf: vec![],
281 #[cfg(feature = "full-repl")]
282 history_input_position: 1,
283 indent: 1,
284 })
285 })
286 }
287
288 pub fn read(&'static self) -> String {
289 self.get().borrow_mut().read()
290 }
291
292 pub fn reread(&'static self) -> String {
293 self.get().borrow_mut().reread()
294 }
295
296 pub fn reread_lines(&'static self, ln_begin: usize, ln_end: usize) -> Vec<String> {
297 self.get().borrow_mut().reread_lines(ln_begin, ln_end)
298 }
299
300 pub fn lineno(&'static self) -> usize {
301 self.get().borrow_mut().lineno
302 }
303
304 pub fn block_begin(&'static self) -> usize {
305 self.get().borrow_mut().block_begin
306 }
307
308 pub fn set_block_begin(&'static self, n: usize) {
309 self.get().borrow_mut().block_begin = n;
310 }
311
312 pub fn set_indent(&'static self, n: usize) {
313 self.get().borrow_mut().indent = n as u16;
314 }
315
316 pub fn insert_whitespace(&'static self, whitespace: &str) {
317 if let Some(line) = self.get().borrow_mut().last_line() {
318 line.insert_str(0, whitespace);
319 }
320 }
321}