1 | // Copyright 2013-2019 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at |
3 | // http://rust-lang.org/COPYRIGHT. |
4 | // |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
8 | // option. This file may not be copied, modified, or distributed |
9 | // except according to those terms. |
10 | |
11 | //! Terminal formatting library. |
12 | //! |
13 | //! This crate provides the `Terminal` trait, which abstracts over an [ANSI |
14 | //! Terminal][ansi] to provide color printing, among other things. There are two |
15 | //! implementations, the `TerminfoTerminal`, which uses control characters from |
16 | //! a [terminfo][ti] database, and `WinConsole`, which uses the [Win32 Console |
17 | //! API][win]. |
18 | //! |
19 | //! # Usage |
20 | //! |
21 | //! This crate is [on crates.io](https://crates.io/crates/term) and can be |
22 | //! used by adding `term` to the dependencies in your project's `Cargo.toml`. |
23 | //! |
24 | //! ```toml |
25 | //! [dependencies] |
26 | //! |
27 | //! term = "*" |
28 | //! ``` |
29 | //! |
30 | //! and this to your crate root: |
31 | //! |
32 | //! ```rust |
33 | //! extern crate term; |
34 | //! ``` |
35 | //! |
36 | //! # Examples |
37 | //! |
38 | //! ```no_run |
39 | //! extern crate term; |
40 | //! use std::io::prelude::*; |
41 | //! |
42 | //! fn main() { |
43 | //! let mut t = term::stdout().unwrap(); |
44 | //! |
45 | //! t.fg(term::color::GREEN).unwrap(); |
46 | //! write!(t, "hello, " ).unwrap(); |
47 | //! |
48 | //! t.fg(term::color::RED).unwrap(); |
49 | //! writeln!(t, "world!" ).unwrap(); |
50 | //! |
51 | //! t.reset().unwrap(); |
52 | //! } |
53 | //! ``` |
54 | //! |
55 | //! [ansi]: https://en.wikipedia.org/wiki/ANSI_escape_code |
56 | //! [win]: http://msdn.microsoft.com/en-us/library/windows/desktop/ms682010%28v=vs.85%29.aspx |
57 | //! [ti]: https://en.wikipedia.org/wiki/Terminfo |
58 | |
59 | #![doc ( |
60 | html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png" , |
61 | html_favicon_url = "https://doc.rust-lang.org/favicon.ico" , |
62 | html_root_url = "https://stebalien.github.io/doc/term/term/" , |
63 | test(attr(deny(warnings))) |
64 | )] |
65 | #![deny (missing_docs)] |
66 | #![cfg_attr (test, deny(warnings))] |
67 | #![allow (clippy::redundant_field_names)] |
68 | |
69 | use std::io::prelude::*; |
70 | |
71 | pub use crate::terminfo::TerminfoTerminal; |
72 | #[cfg (windows)] |
73 | pub use win::{WinConsole, WinConsoleInfo}; |
74 | |
75 | use std::io::{self, Stderr, Stdout}; |
76 | |
77 | pub mod terminfo; |
78 | |
79 | #[cfg (windows)] |
80 | mod win; |
81 | |
82 | /// Alias for stdout terminals. |
83 | pub type StdoutTerminal = dyn Terminal<Output = Stdout> + Send; |
84 | /// Alias for stderr terminals. |
85 | pub type StderrTerminal = dyn Terminal<Output = Stderr> + Send; |
86 | |
87 | #[cfg (not(windows))] |
88 | /// Return a Terminal wrapping stdout, or None if a terminal couldn't be |
89 | /// opened. |
90 | pub fn stdout() -> Option<Box<StdoutTerminal>> { |
91 | TerminfoTerminal::new(out:io::stdout()).map(|t: TerminfoTerminal| Box::new(t) as Box<StdoutTerminal>) |
92 | } |
93 | |
94 | #[cfg (windows)] |
95 | /// Return a Terminal wrapping stdout, or None if a terminal couldn't be |
96 | /// opened. |
97 | pub fn stdout() -> Option<Box<StdoutTerminal>> { |
98 | TerminfoTerminal::new(io::stdout()) |
99 | .map(|t| Box::new(t) as Box<StdoutTerminal>) |
100 | .or_else(|| { |
101 | WinConsole::new(io::stdout()) |
102 | .ok() |
103 | .map(|t| Box::new(t) as Box<StdoutTerminal>) |
104 | }) |
105 | } |
106 | |
107 | #[cfg (not(windows))] |
108 | /// Return a Terminal wrapping stderr, or None if a terminal couldn't be |
109 | /// opened. |
110 | pub fn stderr() -> Option<Box<StderrTerminal>> { |
111 | TerminfoTerminal::new(out:io::stderr()).map(|t: TerminfoTerminal| Box::new(t) as Box<StderrTerminal>) |
112 | } |
113 | |
114 | #[cfg (windows)] |
115 | /// Return a Terminal wrapping stderr, or None if a terminal couldn't be |
116 | /// opened. |
117 | pub fn stderr() -> Option<Box<StderrTerminal>> { |
118 | TerminfoTerminal::new(io::stderr()) |
119 | .map(|t| Box::new(t) as Box<StderrTerminal>) |
120 | .or_else(|| { |
121 | WinConsole::new(io::stderr()) |
122 | .ok() |
123 | .map(|t| Box::new(t) as Box<StderrTerminal>) |
124 | }) |
125 | } |
126 | |
127 | /// Terminal color definitions |
128 | #[allow (missing_docs)] |
129 | pub mod color { |
130 | /// Number for a terminal color |
131 | pub type Color = u32; |
132 | |
133 | pub const BLACK: Color = 0; |
134 | pub const RED: Color = 1; |
135 | pub const GREEN: Color = 2; |
136 | pub const YELLOW: Color = 3; |
137 | pub const BLUE: Color = 4; |
138 | pub const MAGENTA: Color = 5; |
139 | pub const CYAN: Color = 6; |
140 | pub const WHITE: Color = 7; |
141 | |
142 | pub const BRIGHT_BLACK: Color = 8; |
143 | pub const BRIGHT_RED: Color = 9; |
144 | pub const BRIGHT_GREEN: Color = 10; |
145 | pub const BRIGHT_YELLOW: Color = 11; |
146 | pub const BRIGHT_BLUE: Color = 12; |
147 | pub const BRIGHT_MAGENTA: Color = 13; |
148 | pub const BRIGHT_CYAN: Color = 14; |
149 | pub const BRIGHT_WHITE: Color = 15; |
150 | } |
151 | |
152 | /// Terminal attributes for use with term.attr(). |
153 | /// |
154 | /// Most attributes can only be turned on and must be turned off with term.reset(). |
155 | /// The ones that can be turned off explicitly take a boolean value. |
156 | /// Color is also represented as an attribute for convenience. |
157 | #[derive (Debug, PartialEq, Hash, Eq, Copy, Clone)] |
158 | pub enum Attr { |
159 | /// Bold (or possibly bright) mode |
160 | Bold, |
161 | /// Dim mode, also called faint or half-bright. Often not supported |
162 | Dim, |
163 | /// Italics mode. Often not supported |
164 | Italic(bool), |
165 | /// Underline mode |
166 | Underline(bool), |
167 | /// Blink mode |
168 | Blink, |
169 | /// Standout mode. Often implemented as Reverse, sometimes coupled with Bold |
170 | Standout(bool), |
171 | /// Reverse mode, inverts the foreground and background colors |
172 | Reverse, |
173 | /// Secure mode, also called invis mode. Hides the printed text |
174 | Secure, |
175 | /// Convenience attribute to set the foreground color |
176 | ForegroundColor(color::Color), |
177 | /// Convenience attribute to set the background color |
178 | BackgroundColor(color::Color), |
179 | } |
180 | |
181 | /// An error arising from interacting with the terminal. |
182 | #[derive (Debug)] |
183 | pub enum Error { |
184 | /// Indicates an error from any underlying IO |
185 | Io(io::Error), |
186 | /// Indicates an error during terminfo parsing |
187 | TerminfoParsing(terminfo::Error), |
188 | /// Indicates an error expanding a parameterized string from the terminfo database |
189 | ParameterizedExpansion(terminfo::parm::Error), |
190 | /// Indicates that the terminal does not support the requested operation. |
191 | NotSupported, |
192 | /// Indicates that the `TERM` environment variable was unset, and thus we were unable to detect |
193 | /// which terminal we should be using. |
194 | TermUnset, |
195 | /// Indicates that we were unable to find a terminfo entry for the requested terminal. |
196 | TerminfoEntryNotFound, |
197 | /// Indicates that the cursor could not be moved to the requested position. |
198 | CursorDestinationInvalid, |
199 | /// Indicates that the terminal does not support displaying the requested color. |
200 | /// |
201 | /// This is like `NotSupported`, but more specific. |
202 | ColorOutOfRange, |
203 | #[doc (hidden)] |
204 | /// Please don't match against this - if you do, we can't promise we won't break your crate |
205 | /// with a semver-compliant version bump. |
206 | __Nonexhaustive, |
207 | } |
208 | |
209 | // manually implemented because std::io::Error does not implement Eq/PartialEq |
210 | impl std::cmp::PartialEq for Error { |
211 | fn eq(&self, other: &Error) -> bool { |
212 | use crate::Error::*; |
213 | match *self { |
214 | Io(_) => false, |
215 | TerminfoParsing(ref inner1) => match *other { |
216 | TerminfoParsing(ref inner2) => inner1 == inner2, |
217 | _ => false, |
218 | }, |
219 | ParameterizedExpansion(ref inner1) => match *other { |
220 | ParameterizedExpansion(ref inner2) => inner1 == inner2, |
221 | _ => false, |
222 | }, |
223 | NotSupported => match *other { |
224 | NotSupported => true, |
225 | _ => false, |
226 | }, |
227 | TermUnset => match *other { |
228 | TermUnset => true, |
229 | _ => false, |
230 | }, |
231 | TerminfoEntryNotFound => match *other { |
232 | TerminfoEntryNotFound => true, |
233 | _ => false, |
234 | }, |
235 | CursorDestinationInvalid => match *other { |
236 | CursorDestinationInvalid => true, |
237 | _ => false, |
238 | }, |
239 | ColorOutOfRange => match *other { |
240 | ColorOutOfRange => true, |
241 | _ => false, |
242 | }, |
243 | __Nonexhaustive => match *other { |
244 | __Nonexhaustive => true, |
245 | _ => false, |
246 | }, |
247 | } |
248 | } |
249 | } |
250 | |
251 | /// The canonical `Result` type using this crate's Error type. |
252 | pub type Result<T> = std::result::Result<T, Error>; |
253 | |
254 | impl std::fmt::Display for Error { |
255 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
256 | use crate::Error::*; |
257 | match *self { |
258 | Io(ref io: &Error) => io.fmt(f), |
259 | TerminfoParsing(ref e: &Error) => e.fmt(f), |
260 | ParameterizedExpansion(ref e: &Error) => e.fmt(f), |
261 | NotSupported => f.write_str(data:"operation not supported by the terminal" ), |
262 | TermUnset => { |
263 | f.write_str(data:"TERM environment variable unset, unable to detect a terminal" ) |
264 | } |
265 | TerminfoEntryNotFound => { |
266 | f.write_str(data:"could not find a terminfo entry for this terminal" ) |
267 | } |
268 | CursorDestinationInvalid => f.write_str(data:"could not move cursor to requested position" ), |
269 | ColorOutOfRange => f.write_str(data:"color not supported by the terminal" ), |
270 | __Nonexhaustive => f.write_str(data:"placeholder variant that shouldn't be used" ), |
271 | } |
272 | } |
273 | } |
274 | |
275 | impl std::error::Error for Error { |
276 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
277 | match *self { |
278 | Error::Io(ref io: &Error) => Some(io), |
279 | Error::TerminfoParsing(ref e: &Error) => Some(e), |
280 | Error::ParameterizedExpansion(ref e: &Error) => Some(e), |
281 | _ => None, |
282 | } |
283 | } |
284 | } |
285 | |
286 | impl From<Error> for io::Error { |
287 | fn from(err: Error) -> io::Error { |
288 | let kind: ErrorKind = match err { |
289 | Error::Io(ref e: &Error) => e.kind(), |
290 | _ => io::ErrorKind::Other, |
291 | }; |
292 | io::Error::new(kind, error:err) |
293 | } |
294 | } |
295 | |
296 | impl std::convert::From<io::Error> for Error { |
297 | fn from(val: io::Error) -> Self { |
298 | Error::Io(val) |
299 | } |
300 | } |
301 | |
302 | impl std::convert::From<terminfo::Error> for Error { |
303 | fn from(val: terminfo::Error) -> Self { |
304 | Error::TerminfoParsing(val) |
305 | } |
306 | } |
307 | |
308 | impl std::convert::From<terminfo::parm::Error> for Error { |
309 | fn from(val: terminfo::parm::Error) -> Self { |
310 | Error::ParameterizedExpansion(val) |
311 | } |
312 | } |
313 | |
314 | /// A terminal with similar capabilities to an ANSI Terminal |
315 | /// (foreground/background colors etc). |
316 | pub trait Terminal: Write { |
317 | /// The terminal's output writer type. |
318 | type Output: Write; |
319 | |
320 | /// Sets the foreground color to the given color. |
321 | /// |
322 | /// If the color is a bright color, but the terminal only supports 8 colors, |
323 | /// the corresponding normal color will be used instead. |
324 | /// |
325 | /// Returns `Ok(())` if the color change code was sent to the terminal, or `Err(e)` if there |
326 | /// was an error. |
327 | fn fg(&mut self, color: color::Color) -> Result<()>; |
328 | |
329 | /// Sets the background color to the given color. |
330 | /// |
331 | /// If the color is a bright color, but the terminal only supports 8 colors, |
332 | /// the corresponding normal color will be used instead. |
333 | /// |
334 | /// Returns `Ok(())` if the color change code was sent to the terminal, or `Err(e)` if there |
335 | /// was an error. |
336 | fn bg(&mut self, color: color::Color) -> Result<()>; |
337 | |
338 | /// Sets the given terminal attribute, if supported. Returns `Ok(())` if the attribute is |
339 | /// supported and was sent to the terminal, or `Err(e)` if there was an error or the attribute |
340 | /// wasn't supported. |
341 | fn attr(&mut self, attr: Attr) -> Result<()>; |
342 | |
343 | /// Returns whether the given terminal attribute is supported. |
344 | fn supports_attr(&self, attr: Attr) -> bool; |
345 | |
346 | /// Resets all terminal attributes and colors to their defaults. |
347 | /// |
348 | /// Returns `Ok(())` if the reset code was printed, or `Err(e)` if there was an error. |
349 | /// |
350 | /// *Note: This does not flush.* |
351 | /// |
352 | /// That means the reset command may get buffered so, if you aren't planning on doing anything |
353 | /// else that might flush stdout's buffer (e.g. writing a line of text), you should flush after |
354 | /// calling reset. |
355 | fn reset(&mut self) -> Result<()>; |
356 | |
357 | /// Returns true if reset is supported. |
358 | fn supports_reset(&self) -> bool; |
359 | |
360 | /// Returns true if color is fully supported. |
361 | /// |
362 | /// If this function returns `true`, `bg`, `fg`, and `reset` will never |
363 | /// return `Err(Error::NotSupported)`. |
364 | fn supports_color(&self) -> bool; |
365 | |
366 | /// Moves the cursor up one line. |
367 | /// |
368 | /// Returns `Ok(())` if the cursor movement code was printed, or `Err(e)` if there was an |
369 | /// error. |
370 | fn cursor_up(&mut self) -> Result<()>; |
371 | |
372 | /// Deletes the text from the cursor location to the end of the line. |
373 | /// |
374 | /// Returns `Ok(())` if the deletion code was printed, or `Err(e)` if there was an error. |
375 | fn delete_line(&mut self) -> Result<()>; |
376 | |
377 | /// Moves the cursor to the left edge of the current line. |
378 | /// |
379 | /// Returns `Ok(true)` if the deletion code was printed, or `Err(e)` if there was an error. |
380 | fn carriage_return(&mut self) -> Result<()>; |
381 | |
382 | /// Gets an immutable reference to the stream inside |
383 | fn get_ref(&self) -> &Self::Output; |
384 | |
385 | /// Gets a mutable reference to the stream inside |
386 | fn get_mut(&mut self) -> &mut Self::Output; |
387 | |
388 | /// Returns the contained stream, destroying the `Terminal` |
389 | fn into_inner(self) -> Self::Output |
390 | where |
391 | Self: Sized; |
392 | } |
393 | |