| 1 | use std::fmt::{Debug, Display}; | 
| 2 | use std::io::{self, Read, Write}; | 
|---|
| 3 | use std::sync::{Arc, Mutex, RwLock}; | 
|---|
| 4 |  | 
|---|
| 5 | #[ cfg(unix)] | 
|---|
| 6 | use std::os::unix::io::{AsRawFd, RawFd}; | 
|---|
| 7 | #[ cfg(windows)] | 
|---|
| 8 | use std::os::windows::io::{AsRawHandle, RawHandle}; | 
|---|
| 9 |  | 
|---|
| 10 | use crate::{kb::Key, utils::Style}; | 
|---|
| 11 |  | 
|---|
| 12 | #[ cfg(unix)] | 
|---|
| 13 | trait TermWrite: Write + Debug + AsRawFd + Send {} | 
|---|
| 14 | #[ cfg(unix)] | 
|---|
| 15 | impl<T: Write + Debug + AsRawFd + Send> TermWrite for T {} | 
|---|
| 16 |  | 
|---|
| 17 | #[ cfg(unix)] | 
|---|
| 18 | trait TermRead: Read + Debug + AsRawFd + Send {} | 
|---|
| 19 | #[ cfg(unix)] | 
|---|
| 20 | impl<T: Read + Debug + AsRawFd + Send> TermRead for T {} | 
|---|
| 21 |  | 
|---|
| 22 | #[ cfg(unix)] | 
|---|
| 23 | #[ derive(Debug, Clone)] | 
|---|
| 24 | pub struct ReadWritePair { | 
|---|
| 25 | #[ allow(unused)] | 
|---|
| 26 | read: Arc<Mutex<dyn TermRead>>, | 
|---|
| 27 | write: Arc<Mutex<dyn TermWrite>>, | 
|---|
| 28 | style: Style, | 
|---|
| 29 | } | 
|---|
| 30 |  | 
|---|
| 31 | /// Where the term is writing. | 
|---|
| 32 | #[ derive(Debug, Clone)] | 
|---|
| 33 | pub enum TermTarget { | 
|---|
| 34 | Stdout, | 
|---|
| 35 | Stderr, | 
|---|
| 36 | #[ cfg(unix)] | 
|---|
| 37 | ReadWritePair(ReadWritePair), | 
|---|
| 38 | } | 
|---|
| 39 |  | 
|---|
| 40 | #[ derive(Debug)] | 
|---|
| 41 | pub struct TermInner { | 
|---|
| 42 | target: TermTarget, | 
|---|
| 43 | buffer: Option<Mutex<Vec<u8>>>, | 
|---|
| 44 | prompt: RwLock<String>, | 
|---|
| 45 | prompt_guard: Mutex<()>, | 
|---|
| 46 | } | 
|---|
| 47 |  | 
|---|
| 48 | /// The family of the terminal. | 
|---|
| 49 | #[ derive(Debug, Copy, Clone, PartialEq, Eq)] | 
|---|
| 50 | pub enum TermFamily { | 
|---|
| 51 | /// Redirected to a file or file like thing. | 
|---|
| 52 | File, | 
|---|
| 53 | /// A standard unix terminal. | 
|---|
| 54 | UnixTerm, | 
|---|
| 55 | /// A cmd.exe like windows console. | 
|---|
| 56 | WindowsConsole, | 
|---|
| 57 | /// A dummy terminal (for instance on wasm) | 
|---|
| 58 | Dummy, | 
|---|
| 59 | } | 
|---|
| 60 |  | 
|---|
| 61 | /// Gives access to the terminal features. | 
|---|
| 62 | #[ derive(Debug, Clone)] | 
|---|
| 63 | pub struct TermFeatures<'a>(&'a Term); | 
|---|
| 64 |  | 
|---|
| 65 | impl TermFeatures<'_> { | 
|---|
| 66 | /// Check if this is a real user attended terminal (`isatty`) | 
|---|
| 67 | #[ inline] | 
|---|
| 68 | pub fn is_attended(&self) -> bool { | 
|---|
| 69 | is_a_terminal(self.0) | 
|---|
| 70 | } | 
|---|
| 71 |  | 
|---|
| 72 | /// Check if colors are supported by this terminal. | 
|---|
| 73 | /// | 
|---|
| 74 | /// This does not check if colors are enabled.  Currently all terminals | 
|---|
| 75 | /// are considered to support colors | 
|---|
| 76 | #[ inline] | 
|---|
| 77 | pub fn colors_supported(&self) -> bool { | 
|---|
| 78 | is_a_color_terminal(self.0) | 
|---|
| 79 | } | 
|---|
| 80 |  | 
|---|
| 81 | /// Check if this terminal is an msys terminal. | 
|---|
| 82 | /// | 
|---|
| 83 | /// This is sometimes useful to disable features that are known to not | 
|---|
| 84 | /// work on msys terminals or require special handling. | 
|---|
| 85 | #[ inline] | 
|---|
| 86 | pub fn is_msys_tty(&self) -> bool { | 
|---|
| 87 | #[ cfg(windows)] | 
|---|
| 88 | { | 
|---|
| 89 | msys_tty_on(self.0) | 
|---|
| 90 | } | 
|---|
| 91 | #[ cfg(not(windows))] | 
|---|
| 92 | { | 
|---|
| 93 | false | 
|---|
| 94 | } | 
|---|
| 95 | } | 
|---|
| 96 |  | 
|---|
| 97 | /// Check if this terminal wants emojis. | 
|---|
| 98 | #[ inline] | 
|---|
| 99 | pub fn wants_emoji(&self) -> bool { | 
|---|
| 100 | self.is_attended() && wants_emoji() | 
|---|
| 101 | } | 
|---|
| 102 |  | 
|---|
| 103 | /// Return the family of the terminal. | 
|---|
| 104 | #[ inline] | 
|---|
| 105 | pub fn family(&self) -> TermFamily { | 
|---|
| 106 | if !self.is_attended() { | 
|---|
| 107 | return TermFamily::File; | 
|---|
| 108 | } | 
|---|
| 109 | #[ cfg(windows)] | 
|---|
| 110 | { | 
|---|
| 111 | TermFamily::WindowsConsole | 
|---|
| 112 | } | 
|---|
| 113 | #[ cfg(all(unix, not(target_arch = "wasm32")))] | 
|---|
| 114 | { | 
|---|
| 115 | TermFamily::UnixTerm | 
|---|
| 116 | } | 
|---|
| 117 | #[ cfg(target_arch = "wasm32")] | 
|---|
| 118 | { | 
|---|
| 119 | TermFamily::Dummy | 
|---|
| 120 | } | 
|---|
| 121 | } | 
|---|
| 122 | } | 
|---|
| 123 |  | 
|---|
| 124 | /// Abstraction around a terminal. | 
|---|
| 125 | /// | 
|---|
| 126 | /// A terminal can be cloned.  If a buffer is used it's shared across all | 
|---|
| 127 | /// clones which means it largely acts as a handle. | 
|---|
| 128 | #[ derive(Clone, Debug)] | 
|---|
| 129 | pub struct Term { | 
|---|
| 130 | inner: Arc<TermInner>, | 
|---|
| 131 | pub(crate) is_msys_tty: bool, | 
|---|
| 132 | pub(crate) is_tty: bool, | 
|---|
| 133 | } | 
|---|
| 134 |  | 
|---|
| 135 | impl Term { | 
|---|
| 136 | fn with_inner(inner: TermInner) -> Term { | 
|---|
| 137 | let mut term = Term { | 
|---|
| 138 | inner: Arc::new(inner), | 
|---|
| 139 | is_msys_tty: false, | 
|---|
| 140 | is_tty: false, | 
|---|
| 141 | }; | 
|---|
| 142 |  | 
|---|
| 143 | term.is_msys_tty = term.features().is_msys_tty(); | 
|---|
| 144 | term.is_tty = term.features().is_attended(); | 
|---|
| 145 | term | 
|---|
| 146 | } | 
|---|
| 147 |  | 
|---|
| 148 | /// Return a new unbuffered terminal. | 
|---|
| 149 | #[ inline] | 
|---|
| 150 | pub fn stdout() -> Term { | 
|---|
| 151 | Term::with_inner(TermInner { | 
|---|
| 152 | target: TermTarget::Stdout, | 
|---|
| 153 | buffer: None, | 
|---|
| 154 | prompt: RwLock::new(String::new()), | 
|---|
| 155 | prompt_guard: Mutex::new(()), | 
|---|
| 156 | }) | 
|---|
| 157 | } | 
|---|
| 158 |  | 
|---|
| 159 | /// Return a new unbuffered terminal to stderr. | 
|---|
| 160 | #[ inline] | 
|---|
| 161 | pub fn stderr() -> Term { | 
|---|
| 162 | Term::with_inner(TermInner { | 
|---|
| 163 | target: TermTarget::Stderr, | 
|---|
| 164 | buffer: None, | 
|---|
| 165 | prompt: RwLock::new(String::new()), | 
|---|
| 166 | prompt_guard: Mutex::new(()), | 
|---|
| 167 | }) | 
|---|
| 168 | } | 
|---|
| 169 |  | 
|---|
| 170 | /// Return a new buffered terminal. | 
|---|
| 171 | pub fn buffered_stdout() -> Term { | 
|---|
| 172 | Term::with_inner(TermInner { | 
|---|
| 173 | target: TermTarget::Stdout, | 
|---|
| 174 | buffer: Some(Mutex::new(vec![])), | 
|---|
| 175 | prompt: RwLock::new(String::new()), | 
|---|
| 176 | prompt_guard: Mutex::new(()), | 
|---|
| 177 | }) | 
|---|
| 178 | } | 
|---|
| 179 |  | 
|---|
| 180 | /// Return a new buffered terminal to stderr. | 
|---|
| 181 | pub fn buffered_stderr() -> Term { | 
|---|
| 182 | Term::with_inner(TermInner { | 
|---|
| 183 | target: TermTarget::Stderr, | 
|---|
| 184 | buffer: Some(Mutex::new(vec![])), | 
|---|
| 185 | prompt: RwLock::new(String::new()), | 
|---|
| 186 | prompt_guard: Mutex::new(()), | 
|---|
| 187 | }) | 
|---|
| 188 | } | 
|---|
| 189 |  | 
|---|
| 190 | /// Return a terminal for the given Read/Write pair styled like stderr. | 
|---|
| 191 | #[ cfg(unix)] | 
|---|
| 192 | pub fn read_write_pair<R, W>(read: R, write: W) -> Term | 
|---|
| 193 | where | 
|---|
| 194 | R: Read + Debug + AsRawFd + Send + 'static, | 
|---|
| 195 | W: Write + Debug + AsRawFd + Send + 'static, | 
|---|
| 196 | { | 
|---|
| 197 | Self::read_write_pair_with_style(read, write, Style::new().for_stderr()) | 
|---|
| 198 | } | 
|---|
| 199 |  | 
|---|
| 200 | /// Return a terminal for the given Read/Write pair. | 
|---|
| 201 | #[ cfg(unix)] | 
|---|
| 202 | pub fn read_write_pair_with_style<R, W>(read: R, write: W, style: Style) -> Term | 
|---|
| 203 | where | 
|---|
| 204 | R: Read + Debug + AsRawFd + Send + 'static, | 
|---|
| 205 | W: Write + Debug + AsRawFd + Send + 'static, | 
|---|
| 206 | { | 
|---|
| 207 | Term::with_inner(TermInner { | 
|---|
| 208 | target: TermTarget::ReadWritePair(ReadWritePair { | 
|---|
| 209 | read: Arc::new(Mutex::new(read)), | 
|---|
| 210 | write: Arc::new(Mutex::new(write)), | 
|---|
| 211 | style, | 
|---|
| 212 | }), | 
|---|
| 213 | buffer: None, | 
|---|
| 214 | prompt: RwLock::new(String::new()), | 
|---|
| 215 | prompt_guard: Mutex::new(()), | 
|---|
| 216 | }) | 
|---|
| 217 | } | 
|---|
| 218 |  | 
|---|
| 219 | /// Return the style for this terminal. | 
|---|
| 220 | #[ inline] | 
|---|
| 221 | pub fn style(&self) -> Style { | 
|---|
| 222 | match self.inner.target { | 
|---|
| 223 | TermTarget::Stderr => Style::new().for_stderr(), | 
|---|
| 224 | TermTarget::Stdout => Style::new().for_stdout(), | 
|---|
| 225 | #[ cfg(unix)] | 
|---|
| 226 | TermTarget::ReadWritePair(ReadWritePair { ref style, .. }) => style.clone(), | 
|---|
| 227 | } | 
|---|
| 228 | } | 
|---|
| 229 |  | 
|---|
| 230 | /// Return the target of this terminal. | 
|---|
| 231 | #[ inline] | 
|---|
| 232 | pub fn target(&self) -> TermTarget { | 
|---|
| 233 | self.inner.target.clone() | 
|---|
| 234 | } | 
|---|
| 235 |  | 
|---|
| 236 | #[ doc(hidden)] | 
|---|
| 237 | pub fn write_str(&self, s: &str) -> io::Result<()> { | 
|---|
| 238 | match self.inner.buffer { | 
|---|
| 239 | Some(ref buffer) => buffer.lock().unwrap().write_all(s.as_bytes()), | 
|---|
| 240 | None => self.write_through(s.as_bytes()), | 
|---|
| 241 | } | 
|---|
| 242 | } | 
|---|
| 243 |  | 
|---|
| 244 | /// Write a string to the terminal and add a newline. | 
|---|
| 245 | pub fn write_line(&self, s: &str) -> io::Result<()> { | 
|---|
| 246 | let prompt = self.inner.prompt.read().unwrap(); | 
|---|
| 247 | if !prompt.is_empty() { | 
|---|
| 248 | self.clear_line()?; | 
|---|
| 249 | } | 
|---|
| 250 | match self.inner.buffer { | 
|---|
| 251 | Some(ref mutex) => { | 
|---|
| 252 | let mut buffer = mutex.lock().unwrap(); | 
|---|
| 253 | buffer.extend_from_slice(s.as_bytes()); | 
|---|
| 254 | buffer.push( b'\n '); | 
|---|
| 255 | buffer.extend_from_slice(prompt.as_bytes()); | 
|---|
| 256 | Ok(()) | 
|---|
| 257 | } | 
|---|
| 258 | None => self.write_through(format!( "{}\n{} ", s, prompt.as_str()).as_bytes()), | 
|---|
| 259 | } | 
|---|
| 260 | } | 
|---|
| 261 |  | 
|---|
| 262 | /// Read a single character from the terminal. | 
|---|
| 263 | /// | 
|---|
| 264 | /// This does not echo the character and blocks until a single character | 
|---|
| 265 | /// or complete key chord is entered.  If the terminal is not user attended | 
|---|
| 266 | /// the return value will be an error. | 
|---|
| 267 | pub fn read_char(&self) -> io::Result<char> { | 
|---|
| 268 | if !self.is_tty { | 
|---|
| 269 | return Err(io::Error::new( | 
|---|
| 270 | io::ErrorKind::NotConnected, | 
|---|
| 271 | "Not a terminal", | 
|---|
| 272 | )); | 
|---|
| 273 | } | 
|---|
| 274 | loop { | 
|---|
| 275 | match self.read_key()? { | 
|---|
| 276 | Key::Char(c) => { | 
|---|
| 277 | return Ok(c); | 
|---|
| 278 | } | 
|---|
| 279 | Key::Enter => { | 
|---|
| 280 | return Ok( '\n '); | 
|---|
| 281 | } | 
|---|
| 282 | _ => {} | 
|---|
| 283 | } | 
|---|
| 284 | } | 
|---|
| 285 | } | 
|---|
| 286 |  | 
|---|
| 287 | /// Read a single key form the terminal. | 
|---|
| 288 | /// | 
|---|
| 289 | /// This does not echo anything.  If the terminal is not user attended | 
|---|
| 290 | /// the return value will always be the unknown key. | 
|---|
| 291 | pub fn read_key(&self) -> io::Result<Key> { | 
|---|
| 292 | if !self.is_tty { | 
|---|
| 293 | Ok(Key::Unknown) | 
|---|
| 294 | } else { | 
|---|
| 295 | read_single_key(false) | 
|---|
| 296 | } | 
|---|
| 297 | } | 
|---|
| 298 |  | 
|---|
| 299 | pub fn read_key_raw(&self) -> io::Result<Key> { | 
|---|
| 300 | if !self.is_tty { | 
|---|
| 301 | Ok(Key::Unknown) | 
|---|
| 302 | } else { | 
|---|
| 303 | read_single_key(true) | 
|---|
| 304 | } | 
|---|
| 305 | } | 
|---|
| 306 |  | 
|---|
| 307 | /// Read one line of input. | 
|---|
| 308 | /// | 
|---|
| 309 | /// This does not include the trailing newline.  If the terminal is not | 
|---|
| 310 | /// user attended the return value will always be an empty string. | 
|---|
| 311 | pub fn read_line(&self) -> io::Result<String> { | 
|---|
| 312 | self.read_line_initial_text( "") | 
|---|
| 313 | } | 
|---|
| 314 |  | 
|---|
| 315 | /// Read one line of input with initial text. | 
|---|
| 316 | /// | 
|---|
| 317 | /// This method blocks until no other thread is waiting for this read_line | 
|---|
| 318 | /// before reading a line from the terminal. | 
|---|
| 319 | /// This does not include the trailing newline.  If the terminal is not | 
|---|
| 320 | /// user attended the return value will always be an empty string. | 
|---|
| 321 | pub fn read_line_initial_text(&self, initial: &str) -> io::Result<String> { | 
|---|
| 322 | if !self.is_tty { | 
|---|
| 323 | return Ok( "".into()); | 
|---|
| 324 | } | 
|---|
| 325 | *self.inner.prompt.write().unwrap() = initial.to_string(); | 
|---|
| 326 | // use a guard in order to prevent races with other calls to read_line_initial_text | 
|---|
| 327 | let _guard = self.inner.prompt_guard.lock().unwrap(); | 
|---|
| 328 |  | 
|---|
| 329 | self.write_str(initial)?; | 
|---|
| 330 |  | 
|---|
| 331 | fn read_line_internal(slf: &Term, initial: &str) -> io::Result<String> { | 
|---|
| 332 | let prefix_len = initial.len(); | 
|---|
| 333 |  | 
|---|
| 334 | let mut chars: Vec<char> = initial.chars().collect(); | 
|---|
| 335 |  | 
|---|
| 336 | loop { | 
|---|
| 337 | match slf.read_key()? { | 
|---|
| 338 | Key::Backspace => { | 
|---|
| 339 | if prefix_len < chars.len() && chars.pop().is_some() { | 
|---|
| 340 | slf.clear_chars(1)?; | 
|---|
| 341 | } | 
|---|
| 342 | slf.flush()?; | 
|---|
| 343 | } | 
|---|
| 344 | Key::Char(chr) => { | 
|---|
| 345 | chars.push(chr); | 
|---|
| 346 | let mut bytes_char = [0; 4]; | 
|---|
| 347 | chr.encode_utf8(&mut bytes_char); | 
|---|
| 348 | slf.write_str(chr.encode_utf8(&mut bytes_char))?; | 
|---|
| 349 | slf.flush()?; | 
|---|
| 350 | } | 
|---|
| 351 | Key::Enter => { | 
|---|
| 352 | slf.write_through(format!( "\n{} ", initial).as_bytes())?; | 
|---|
| 353 | break; | 
|---|
| 354 | } | 
|---|
| 355 | _ => (), | 
|---|
| 356 | } | 
|---|
| 357 | } | 
|---|
| 358 | Ok(chars.iter().skip(prefix_len).collect::<String>()) | 
|---|
| 359 | } | 
|---|
| 360 | let ret = read_line_internal(self, initial); | 
|---|
| 361 |  | 
|---|
| 362 | *self.inner.prompt.write().unwrap() = String::new(); | 
|---|
| 363 | ret | 
|---|
| 364 | } | 
|---|
| 365 |  | 
|---|
| 366 | /// Read a line of input securely. | 
|---|
| 367 | /// | 
|---|
| 368 | /// This is similar to `read_line` but will not echo the output.  This | 
|---|
| 369 | /// also switches the terminal into a different mode where not all | 
|---|
| 370 | /// characters might be accepted. | 
|---|
| 371 | pub fn read_secure_line(&self) -> io::Result<String> { | 
|---|
| 372 | if !self.is_tty { | 
|---|
| 373 | return Ok( "".into()); | 
|---|
| 374 | } | 
|---|
| 375 | match read_secure() { | 
|---|
| 376 | Ok(rv) => { | 
|---|
| 377 | self.write_line( "")?; | 
|---|
| 378 | Ok(rv) | 
|---|
| 379 | } | 
|---|
| 380 | Err(err) => Err(err), | 
|---|
| 381 | } | 
|---|
| 382 | } | 
|---|
| 383 |  | 
|---|
| 384 | /// Flush internal buffers. | 
|---|
| 385 | /// | 
|---|
| 386 | /// This forces the contents of the internal buffer to be written to | 
|---|
| 387 | /// the terminal.  This is unnecessary for unbuffered terminals which | 
|---|
| 388 | /// will automatically flush. | 
|---|
| 389 | pub fn flush(&self) -> io::Result<()> { | 
|---|
| 390 | if let Some(ref buffer) = self.inner.buffer { | 
|---|
| 391 | let mut buffer = buffer.lock().unwrap(); | 
|---|
| 392 | if !buffer.is_empty() { | 
|---|
| 393 | self.write_through(&buffer[..])?; | 
|---|
| 394 | buffer.clear(); | 
|---|
| 395 | } | 
|---|
| 396 | } | 
|---|
| 397 | Ok(()) | 
|---|
| 398 | } | 
|---|
| 399 |  | 
|---|
| 400 | /// Check if the terminal is indeed a terminal. | 
|---|
| 401 | #[ inline] | 
|---|
| 402 | pub fn is_term(&self) -> bool { | 
|---|
| 403 | self.is_tty | 
|---|
| 404 | } | 
|---|
| 405 |  | 
|---|
| 406 | /// Check for common terminal features. | 
|---|
| 407 | #[ inline] | 
|---|
| 408 | pub fn features(&self) -> TermFeatures<'_> { | 
|---|
| 409 | TermFeatures(self) | 
|---|
| 410 | } | 
|---|
| 411 |  | 
|---|
| 412 | /// Return the terminal size in rows and columns or gets sensible defaults. | 
|---|
| 413 | #[ inline] | 
|---|
| 414 | pub fn size(&self) -> (u16, u16) { | 
|---|
| 415 | self.size_checked().unwrap_or((24, DEFAULT_WIDTH)) | 
|---|
| 416 | } | 
|---|
| 417 |  | 
|---|
| 418 | /// Return the terminal size in rows and columns. | 
|---|
| 419 | /// | 
|---|
| 420 | /// If the size cannot be reliably determined `None` is returned. | 
|---|
| 421 | #[ inline] | 
|---|
| 422 | pub fn size_checked(&self) -> Option<(u16, u16)> { | 
|---|
| 423 | terminal_size(self) | 
|---|
| 424 | } | 
|---|
| 425 |  | 
|---|
| 426 | /// Move the cursor to row `x` and column `y`. Values are 0-based. | 
|---|
| 427 | #[ inline] | 
|---|
| 428 | pub fn move_cursor_to(&self, x: usize, y: usize) -> io::Result<()> { | 
|---|
| 429 | move_cursor_to(self, x, y) | 
|---|
| 430 | } | 
|---|
| 431 |  | 
|---|
| 432 | /// Move the cursor up by `n` lines, if possible. | 
|---|
| 433 | /// | 
|---|
| 434 | /// If there are less than `n` lines above the current cursor position, | 
|---|
| 435 | /// the cursor is moved to the top line of the terminal (i.e., as far up as possible). | 
|---|
| 436 | #[ inline] | 
|---|
| 437 | pub fn move_cursor_up(&self, n: usize) -> io::Result<()> { | 
|---|
| 438 | move_cursor_up(self, n) | 
|---|
| 439 | } | 
|---|
| 440 |  | 
|---|
| 441 | /// Move the cursor down by `n` lines, if possible. | 
|---|
| 442 | /// | 
|---|
| 443 | /// If there are less than `n` lines below the current cursor position, | 
|---|
| 444 | /// the cursor is moved to the bottom line of the terminal (i.e., as far down as possible). | 
|---|
| 445 | #[ inline] | 
|---|
| 446 | pub fn move_cursor_down(&self, n: usize) -> io::Result<()> { | 
|---|
| 447 | move_cursor_down(self, n) | 
|---|
| 448 | } | 
|---|
| 449 |  | 
|---|
| 450 | /// Move the cursor `n` characters to the left, if possible. | 
|---|
| 451 | /// | 
|---|
| 452 | /// If there are fewer than `n` characters to the left of the current cursor position, | 
|---|
| 453 | /// the cursor is moved to the beginning of the line (i.e., as far to the left as possible). | 
|---|
| 454 | #[ inline] | 
|---|
| 455 | pub fn move_cursor_left(&self, n: usize) -> io::Result<()> { | 
|---|
| 456 | move_cursor_left(self, n) | 
|---|
| 457 | } | 
|---|
| 458 |  | 
|---|
| 459 | /// Move the cursor `n` characters to the right. | 
|---|
| 460 | /// | 
|---|
| 461 | /// If there are fewer than `n` characters to the right of the current cursor position, | 
|---|
| 462 | /// the cursor is moved to the end of the current line (i.e., as far to the right as possible). | 
|---|
| 463 | #[ inline] | 
|---|
| 464 | pub fn move_cursor_right(&self, n: usize) -> io::Result<()> { | 
|---|
| 465 | move_cursor_right(self, n) | 
|---|
| 466 | } | 
|---|
| 467 |  | 
|---|
| 468 | /// Clear the current line. | 
|---|
| 469 | /// | 
|---|
| 470 | /// Position the cursor at the beginning of the current line. | 
|---|
| 471 | #[ inline] | 
|---|
| 472 | pub fn clear_line(&self) -> io::Result<()> { | 
|---|
| 473 | clear_line(self) | 
|---|
| 474 | } | 
|---|
| 475 |  | 
|---|
| 476 | /// Clear the last `n` lines before the current line. | 
|---|
| 477 | /// | 
|---|
| 478 | /// Position the cursor at the beginning of the first line that was cleared. | 
|---|
| 479 | pub fn clear_last_lines(&self, n: usize) -> io::Result<()> { | 
|---|
| 480 | self.move_cursor_up(n)?; | 
|---|
| 481 | for _ in 0..n { | 
|---|
| 482 | self.clear_line()?; | 
|---|
| 483 | self.move_cursor_down(1)?; | 
|---|
| 484 | } | 
|---|
| 485 | self.move_cursor_up(n)?; | 
|---|
| 486 | Ok(()) | 
|---|
| 487 | } | 
|---|
| 488 |  | 
|---|
| 489 | /// Clear the entire screen. | 
|---|
| 490 | /// | 
|---|
| 491 | /// Move the cursor to the upper left corner of the screen. | 
|---|
| 492 | #[ inline] | 
|---|
| 493 | pub fn clear_screen(&self) -> io::Result<()> { | 
|---|
| 494 | clear_screen(self) | 
|---|
| 495 | } | 
|---|
| 496 |  | 
|---|
| 497 | /// Clear everything from the current cursor position to the end of the screen. | 
|---|
| 498 | /// The cursor stays in its position. | 
|---|
| 499 | #[ inline] | 
|---|
| 500 | pub fn clear_to_end_of_screen(&self) -> io::Result<()> { | 
|---|
| 501 | clear_to_end_of_screen(self) | 
|---|
| 502 | } | 
|---|
| 503 |  | 
|---|
| 504 | /// Clear the last `n` characters of the current line. | 
|---|
| 505 | #[ inline] | 
|---|
| 506 | pub fn clear_chars(&self, n: usize) -> io::Result<()> { | 
|---|
| 507 | clear_chars(self, n) | 
|---|
| 508 | } | 
|---|
| 509 |  | 
|---|
| 510 | /// Set the terminal title. | 
|---|
| 511 | pub fn set_title<T: Display>(&self, title: T) { | 
|---|
| 512 | if !self.is_tty { | 
|---|
| 513 | return; | 
|---|
| 514 | } | 
|---|
| 515 | set_title(title); | 
|---|
| 516 | } | 
|---|
| 517 |  | 
|---|
| 518 | /// Make the cursor visible again. | 
|---|
| 519 | #[ inline] | 
|---|
| 520 | pub fn show_cursor(&self) -> io::Result<()> { | 
|---|
| 521 | show_cursor(self) | 
|---|
| 522 | } | 
|---|
| 523 |  | 
|---|
| 524 | /// Hide the cursor. | 
|---|
| 525 | #[ inline] | 
|---|
| 526 | pub fn hide_cursor(&self) -> io::Result<()> { | 
|---|
| 527 | hide_cursor(self) | 
|---|
| 528 | } | 
|---|
| 529 |  | 
|---|
| 530 | // helpers | 
|---|
| 531 |  | 
|---|
| 532 | #[ cfg(all(windows, feature = "windows-console-colors"))] | 
|---|
| 533 | fn write_through(&self, bytes: &[u8]) -> io::Result<()> { | 
|---|
| 534 | if self.is_msys_tty || !self.is_tty { | 
|---|
| 535 | self.write_through_common(bytes) | 
|---|
| 536 | } else { | 
|---|
| 537 | match self.inner.target { | 
|---|
| 538 | TermTarget::Stdout => console_colors(self, Console::stdout()?, bytes), | 
|---|
| 539 | TermTarget::Stderr => console_colors(self, Console::stderr()?, bytes), | 
|---|
| 540 | } | 
|---|
| 541 | } | 
|---|
| 542 | } | 
|---|
| 543 |  | 
|---|
| 544 | #[ cfg(not(all(windows, feature = "windows-console-colors")))] | 
|---|
| 545 | fn write_through(&self, bytes: &[u8]) -> io::Result<()> { | 
|---|
| 546 | self.write_through_common(bytes) | 
|---|
| 547 | } | 
|---|
| 548 |  | 
|---|
| 549 | pub(crate) fn write_through_common(&self, bytes: &[u8]) -> io::Result<()> { | 
|---|
| 550 | match self.inner.target { | 
|---|
| 551 | TermTarget::Stdout => { | 
|---|
| 552 | io::stdout().write_all(bytes)?; | 
|---|
| 553 | io::stdout().flush()?; | 
|---|
| 554 | } | 
|---|
| 555 | TermTarget::Stderr => { | 
|---|
| 556 | io::stderr().write_all(bytes)?; | 
|---|
| 557 | io::stderr().flush()?; | 
|---|
| 558 | } | 
|---|
| 559 | #[ cfg(unix)] | 
|---|
| 560 | TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => { | 
|---|
| 561 | let mut write = write.lock().unwrap(); | 
|---|
| 562 | write.write_all(bytes)?; | 
|---|
| 563 | write.flush()?; | 
|---|
| 564 | } | 
|---|
| 565 | } | 
|---|
| 566 | Ok(()) | 
|---|
| 567 | } | 
|---|
| 568 | } | 
|---|
| 569 |  | 
|---|
| 570 | /// A fast way to check if the application has a user attended for stdout. | 
|---|
| 571 | /// | 
|---|
| 572 | /// This means that stdout is connected to a terminal instead of a | 
|---|
| 573 | /// file or redirected by other means. This is a shortcut for | 
|---|
| 574 | /// checking the `is_attended` feature on the stdout terminal. | 
|---|
| 575 | #[ inline] | 
|---|
| 576 | pub fn user_attended() -> bool { | 
|---|
| 577 | Term::stdout().features().is_attended() | 
|---|
| 578 | } | 
|---|
| 579 |  | 
|---|
| 580 | /// A fast way to check if the application has a user attended for stderr. | 
|---|
| 581 | /// | 
|---|
| 582 | /// This means that stderr is connected to a terminal instead of a | 
|---|
| 583 | /// file or redirected by other means. This is a shortcut for | 
|---|
| 584 | /// checking the `is_attended` feature on the stderr terminal. | 
|---|
| 585 | #[ inline] | 
|---|
| 586 | pub fn user_attended_stderr() -> bool { | 
|---|
| 587 | Term::stderr().features().is_attended() | 
|---|
| 588 | } | 
|---|
| 589 |  | 
|---|
| 590 | #[ cfg(unix)] | 
|---|
| 591 | impl AsRawFd for Term { | 
|---|
| 592 | fn as_raw_fd(&self) -> RawFd { | 
|---|
| 593 | match self.inner.target { | 
|---|
| 594 | TermTarget::Stdout => libc::STDOUT_FILENO, | 
|---|
| 595 | TermTarget::Stderr => libc::STDERR_FILENO, | 
|---|
| 596 | TermTarget::ReadWritePair(ReadWritePair { ref write: &Arc>, .. }) => { | 
|---|
| 597 | write.lock().unwrap().as_raw_fd() | 
|---|
| 598 | } | 
|---|
| 599 | } | 
|---|
| 600 | } | 
|---|
| 601 | } | 
|---|
| 602 |  | 
|---|
| 603 | #[ cfg(windows)] | 
|---|
| 604 | impl AsRawHandle for Term { | 
|---|
| 605 | fn as_raw_handle(&self) -> RawHandle { | 
|---|
| 606 | use windows_sys::Win32::System::Console::{ | 
|---|
| 607 | GetStdHandle, STD_ERROR_HANDLE, STD_OUTPUT_HANDLE, | 
|---|
| 608 | }; | 
|---|
| 609 |  | 
|---|
| 610 | unsafe { | 
|---|
| 611 | GetStdHandle(match self.inner.target { | 
|---|
| 612 | TermTarget::Stdout => STD_OUTPUT_HANDLE, | 
|---|
| 613 | TermTarget::Stderr => STD_ERROR_HANDLE, | 
|---|
| 614 | }) as RawHandle | 
|---|
| 615 | } | 
|---|
| 616 | } | 
|---|
| 617 | } | 
|---|
| 618 |  | 
|---|
| 619 | impl Write for Term { | 
|---|
| 620 | fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | 
|---|
| 621 | match self.inner.buffer { | 
|---|
| 622 | Some(ref buffer: &Mutex>) => buffer.lock().unwrap().write_all(buf), | 
|---|
| 623 | None => self.write_through(bytes:buf), | 
|---|
| 624 | }?; | 
|---|
| 625 | Ok(buf.len()) | 
|---|
| 626 | } | 
|---|
| 627 |  | 
|---|
| 628 | fn flush(&mut self) -> io::Result<()> { | 
|---|
| 629 | Term::flush(self) | 
|---|
| 630 | } | 
|---|
| 631 | } | 
|---|
| 632 |  | 
|---|
| 633 | impl Write for &Term { | 
|---|
| 634 | fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | 
|---|
| 635 | match self.inner.buffer { | 
|---|
| 636 | Some(ref buffer: &Mutex>) => buffer.lock().unwrap().write_all(buf), | 
|---|
| 637 | None => self.write_through(bytes:buf), | 
|---|
| 638 | }?; | 
|---|
| 639 | Ok(buf.len()) | 
|---|
| 640 | } | 
|---|
| 641 |  | 
|---|
| 642 | fn flush(&mut self) -> io::Result<()> { | 
|---|
| 643 | Term::flush(self) | 
|---|
| 644 | } | 
|---|
| 645 | } | 
|---|
| 646 |  | 
|---|
| 647 | impl Read for Term { | 
|---|
| 648 | fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | 
|---|
| 649 | io::stdin().read(buf) | 
|---|
| 650 | } | 
|---|
| 651 | } | 
|---|
| 652 |  | 
|---|
| 653 | impl Read for &Term { | 
|---|
| 654 | fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | 
|---|
| 655 | io::stdin().read(buf) | 
|---|
| 656 | } | 
|---|
| 657 | } | 
|---|
| 658 |  | 
|---|
| 659 | #[ cfg(all(unix, not(target_arch = "wasm32")))] | 
|---|
| 660 | pub use crate::unix_term::*; | 
|---|
| 661 | #[ cfg(target_arch = "wasm32")] | 
|---|
| 662 | pub use crate::wasm_term::*; | 
|---|
| 663 | #[ cfg(windows)] | 
|---|
| 664 | pub use crate::windows_term::*; | 
|---|
| 665 |  | 
|---|