1use std::collections::VecDeque;
5use std::io::{self, Read, Write};
6
7use super::keys::{self, Key};
8use super::{Frame, Terminal};
9
10pub struct RawTerminal {
11 pending: VecDeque<Key>,
12 color: bool,
13 saved: sys::Saved,
14}
15
16impl RawTerminal {
17 pub fn open() -> io::Result<RawTerminal> {
20 let saved = sys::enter_raw()?;
21 let mut out = io::stdout();
22 out.write_all(b"\x1b[?1049h\x1b[?25l")?;
23 out.flush()?;
24 Ok(RawTerminal {
25 pending: VecDeque::new(),
26 color: std::env::var_os("NO_COLOR").is_none_or(|v| v.is_empty()),
27 saved,
28 })
29 }
30}
31
32impl Drop for RawTerminal {
33 fn drop(&mut self) {
34 let mut out = io::stdout();
35 let _ = out.write_all(b"\x1b[0m\x1b[?25h\x1b[?1049l");
36 let _ = out.flush();
37 sys::restore(&self.saved);
38 }
39}
40
41impl Terminal for RawTerminal {
42 fn size(&self) -> (usize, usize) {
43 sys::size().unwrap_or((80, 24))
44 }
45
46 fn read_key(&mut self) -> io::Result<Option<Key>> {
47 loop {
48 if let Some(key) = self.pending.pop_front() {
49 return Ok(Some(key));
50 }
51 let mut buf = [0u8; 256];
52 let n = io::stdin().lock().read(&mut buf)?;
53 if n == 0 {
54 return Ok(None);
55 }
56 self.pending.extend(keys::decode(&buf[..n]));
57 }
58 }
59
60 fn draw(&mut self, frame: &Frame) -> io::Result<()> {
61 let mut out = String::new();
62 out.push_str("\x1b[?25l");
63 for (row, line) in frame.lines.iter().enumerate() {
64 out.push_str(&format!("\x1b[{};1H{line}\x1b[0m\x1b[K", row + 1));
65 }
66 out.push_str(&format!("\x1b[{};1H\x1b[J", frame.lines.len() + 1));
67 if let Some((row, col)) = frame.cursor {
68 out.push_str(&format!("\x1b[{};{}H\x1b[?25h", row + 1, col + 1));
69 }
70 let mut stdout = io::stdout();
71 stdout.write_all(out.as_bytes())?;
72 stdout.flush()
73 }
74
75 fn color(&self) -> bool {
76 self.color
77 }
78}
79
80#[cfg(unix)]
81mod sys {
82 use std::io;
83 use std::process::{Command, Stdio};
84
85 pub struct Saved(String);
86
87 fn stty(args: &[&str]) -> io::Result<String> {
88 let out = Command::new("stty")
89 .args(args)
90 .stdin(Stdio::inherit())
91 .stderr(Stdio::null())
92 .output()?;
93 if !out.status.success() {
94 return Err(io::Error::other("stty failed (is the input a terminal?)"));
95 }
96 Ok(String::from_utf8_lossy(&out.stdout).trim().to_owned())
97 }
98
99 pub fn enter_raw() -> io::Result<Saved> {
100 let saved = stty(&["-g"])?;
101 stty(&["raw", "-echo"])?;
102 Ok(Saved(saved))
103 }
104
105 pub fn restore(saved: &Saved) {
106 let _ = stty(&[&saved.0]);
107 }
108
109 pub fn size() -> Option<(usize, usize)> {
110 let text = stty(&["size"]).ok()?;
111 let mut parts = text.split_whitespace();
112 let rows: usize = parts.next()?.parse().ok()?;
113 let cols: usize = parts.next()?.parse().ok()?;
114 Some((cols, rows))
115 }
116}
117
118#[cfg(windows)]
119mod sys {
120 use std::io;
121
122 type Handle = isize;
123
124 #[repr(C)]
125 #[derive(Default, Clone, Copy)]
126 struct Coord {
127 x: i16,
128 y: i16,
129 }
130
131 #[repr(C)]
132 #[derive(Default, Clone, Copy)]
133 struct SmallRect {
134 left: i16,
135 top: i16,
136 right: i16,
137 bottom: i16,
138 }
139
140 #[repr(C)]
141 #[derive(Default, Clone, Copy)]
142 struct ScreenBufferInfo {
143 size: Coord,
144 cursor: Coord,
145 attributes: u16,
146 window: SmallRect,
147 maximum: Coord,
148 }
149
150 #[link(name = "kernel32")]
151 extern "system" {
152 fn GetStdHandle(which: u32) -> Handle;
153 fn GetConsoleMode(handle: Handle, mode: *mut u32) -> i32;
154 fn SetConsoleMode(handle: Handle, mode: u32) -> i32;
155 fn GetConsoleScreenBufferInfo(handle: Handle, info: *mut ScreenBufferInfo) -> i32;
156 fn GetConsoleOutputCP() -> u32;
157 fn SetConsoleOutputCP(code_page: u32) -> i32;
158 }
159
160 const STD_INPUT: u32 = -10i32 as u32;
161 const STD_OUTPUT: u32 = -11i32 as u32;
162 const ENABLE_PROCESSED_INPUT: u32 = 0x0001;
163 const ENABLE_LINE_INPUT: u32 = 0x0002;
164 const ENABLE_ECHO_INPUT: u32 = 0x0004;
165 const ENABLE_VIRTUAL_TERMINAL_INPUT: u32 = 0x0200;
166 const ENABLE_PROCESSED_OUTPUT: u32 = 0x0001;
167 const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
168 const UTF8: u32 = 65001;
169
170 pub struct Saved {
171 input: u32,
172 output: u32,
173 code_page: u32,
174 }
175
176 fn mode(handle: Handle) -> io::Result<u32> {
177 let mut mode = 0;
178 if unsafe { GetConsoleMode(handle, &mut mode) } == 0 {
180 return Err(io::Error::last_os_error());
181 }
182 Ok(mode)
183 }
184
185 fn set_mode(handle: Handle, mode: u32) -> io::Result<()> {
186 if unsafe { SetConsoleMode(handle, mode) } == 0 {
188 return Err(io::Error::last_os_error());
189 }
190 Ok(())
191 }
192
193 pub fn enter_raw() -> io::Result<Saved> {
194 let (input, output, code_page) = unsafe {
196 (
197 GetStdHandle(STD_INPUT),
198 GetStdHandle(STD_OUTPUT),
199 GetConsoleOutputCP(),
200 )
201 };
202 let saved = Saved {
203 input: mode(input)?,
204 output: mode(output)?,
205 code_page,
206 };
207 set_mode(
208 input,
209 (saved.input & !(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT))
210 | ENABLE_VIRTUAL_TERMINAL_INPUT,
211 )?;
212 if let Err(e) = set_mode(
213 output,
214 saved.output | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING,
215 ) {
216 let _ = set_mode(input, saved.input);
217 return Err(e);
218 }
219 unsafe { SetConsoleOutputCP(UTF8) };
221 Ok(saved)
222 }
223
224 pub fn restore(saved: &Saved) {
225 unsafe {
227 let _ = SetConsoleMode(GetStdHandle(STD_INPUT), saved.input);
228 let _ = SetConsoleMode(GetStdHandle(STD_OUTPUT), saved.output);
229 SetConsoleOutputCP(saved.code_page);
230 }
231 }
232
233 pub fn size() -> Option<(usize, usize)> {
234 let mut info = ScreenBufferInfo::default();
235 let ok = unsafe { GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT), &mut info) };
237 if ok == 0 {
238 return None;
239 }
240 let w = (info.window.right - info.window.left + 1) as usize;
241 let h = (info.window.bottom - info.window.top + 1) as usize;
242 Some((w, h))
243 }
244}
245
246#[cfg(not(any(unix, windows)))]
247mod sys {
248 use std::io;
249
250 pub struct Saved;
251
252 pub fn enter_raw() -> io::Result<Saved> {
253 Err(io::Error::new(
254 io::ErrorKind::Unsupported,
255 "this system does not have the interactive screen",
256 ))
257 }
258
259 pub fn restore(_: &Saved) {}
260
261 pub fn size() -> Option<(usize, usize)> {
262 None
263 }
264}