summaryrefslogtreecommitdiffstats
path: root/vendor/fwdansi/src
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/fwdansi/src')
-rw-r--r--vendor/fwdansi/src/lib.rs234
1 files changed, 234 insertions, 0 deletions
diff --git a/vendor/fwdansi/src/lib.rs b/vendor/fwdansi/src/lib.rs
new file mode 100644
index 0000000..45638c9
--- /dev/null
+++ b/vendor/fwdansi/src/lib.rs
@@ -0,0 +1,234 @@
+//! Write colored strings with ANSI escape code into a `termcolor` terminal.
+//!
+//! This package provides a single function, [`write_ansi`], which parses ANSI
+//! escape codes in the provided byte string and transforms them into the
+//! corresponding `termcolor` commands. The colors will be supported even on a
+//! Windows console.
+//!
+//! The main purpose of this package is to forward colored output from a child
+//! process.
+//!
+//! ```rust
+// #![doc(include = "../examples/rustc.rs")] // still unstable, see issue 44732
+//! extern crate termcolor;
+//! extern crate fwdansi;
+//!
+//! use termcolor::*;
+//! use std::io;
+//! use std::process::Command;
+//! use fwdansi::write_ansi;
+//!
+//! fn main() -> io::Result<()> {
+//! let output = Command::new("rustc").args(&["--color", "always"]).output()?;
+//!
+//! let mut stderr = StandardStream::stderr(ColorChoice::Always);
+//! write_ansi(&mut stderr, &output.stderr)?;
+//! //^ should print "error: no input filename given" with appropriate color everywhere.
+//!
+//! Ok(())
+//! }
+//! ```
+
+extern crate memchr;
+extern crate termcolor;
+
+use memchr::memchr;
+use termcolor::{Color, ColorSpec, WriteColor};
+
+use std::io;
+
+/// Writes a string with ANSI escape code into the colored output stream.
+///
+/// Only SGR (`\x1b[…m`) is supported. Other input will be printed as-is.
+pub fn write_ansi<W: WriteColor>(mut writer: W, mut ansi: &[u8]) -> io::Result<()> {
+ while let Some(index) = memchr(0x1b, ansi) {
+ let (left, right) = ansi.split_at(index);
+ writer.write_all(left)?;
+ if right.is_empty() {
+ return Ok(());
+ }
+
+ let mut parser = ColorSpecParser::new(right);
+ parser.parse();
+ if parser.ansi.as_ptr() == right.as_ptr() {
+ writer.write_all(&right[..1])?;
+ ansi = &right[1..];
+ } else {
+ if parser.reset {
+ writer.reset()?;
+ } else {
+ writer.set_color(&parser.spec)?;
+ }
+ ansi = parser.ansi;
+ }
+ }
+ writer.write_all(ansi)
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+enum State {
+ Normal,
+ PrepareCustomColor,
+ Ansi256,
+ Rgb,
+}
+#[derive(Debug)]
+struct ColorSpecParser<'a> {
+ spec: ColorSpec,
+ ansi: &'a [u8],
+ reset: bool,
+ state: State,
+ is_bg: bool,
+ red: Option<u8>,
+ green: Option<u8>,
+}
+impl<'a> ColorSpecParser<'a> {
+ fn new(ansi: &'a [u8]) -> Self {
+ Self {
+ spec: ColorSpec::new(),
+ ansi,
+ reset: false,
+ state: State::Normal,
+ is_bg: false,
+ red: None,
+ green: None,
+ }
+ }
+
+ fn parse(&mut self) {
+ #[derive(PartialEq, Eq, Debug)]
+ enum Expected {
+ Escape,
+ OpenBracket,
+ Number(u8),
+ }
+
+ while !self.ansi.is_empty() {
+ let mut expected = Expected::Escape;
+ let mut it = self.ansi.iter();
+ for b in &mut it {
+ match (*b, expected) {
+ (0x1b, Expected::Escape) => {
+ expected = Expected::OpenBracket;
+ continue;
+ }
+ (b'[', Expected::OpenBracket) => {
+ expected = Expected::Number(0);
+ continue;
+ }
+ (b'0'..=b'9', Expected::Number(number)) => {
+ if let Some(n) = number.checked_mul(10).and_then(|n| n.checked_add(b - b'0')) {
+ expected = Expected::Number(n);
+ continue;
+ }
+ }
+ (b':', Expected::Number(number))
+ | (b';', Expected::Number(number))
+ | (b'm', Expected::Number(number)) => {
+ self.apply_number(number);
+ if *b == b'm' {
+ expected = Expected::Escape;
+ break;
+ } else {
+ expected = Expected::Number(0);
+ continue;
+ }
+ }
+ _ => {}
+ }
+ return;
+ }
+ if let Expected::Escape = expected {
+ self.ansi = it.as_slice();
+ } else {
+ break;
+ }
+ }
+ }
+
+ fn set_color(&mut self, color: Color) {
+ if self.is_bg {
+ self.spec.set_bg(Some(color));
+ } else {
+ self.spec.set_fg(Some(color));
+ }
+ }
+
+ fn apply_number(&mut self, number: u8) {
+ self.reset = false;
+ match (number, self.state) {
+ (0, State::Normal) => {
+ self.reset = true;
+ }
+ (1, State::Normal) => {
+ self.spec.set_bold(true);
+ }
+ (4, State::Normal) => {
+ self.spec.set_underline(true);
+ }
+ (21, State::Normal) => {
+ self.spec.set_bold(false);
+ }
+ (24, State::Normal) => {
+ self.spec.set_underline(false);
+ }
+ (38, State::Normal) | (48, State::Normal) => {
+ self.is_bg = number == 48;
+ self.state = State::PrepareCustomColor;
+ }
+ (30..=39, State::Normal) => {
+ self.spec.set_fg(parse_color(number - 30));
+ }
+ (40..=49, State::Normal) => {
+ self.spec.set_bg(parse_color(number - 40));
+ }
+ (90..=97, State::Normal) => {
+ self.spec.set_intense(true).set_fg(parse_color(number - 90));
+ }
+ (100..=107, State::Normal) => {
+ self.spec.set_intense(true).set_bg(parse_color(number - 100));
+ }
+ (5, State::PrepareCustomColor) => {
+ self.state = State::Ansi256;
+ }
+ (2, State::PrepareCustomColor) => {
+ self.state = State::Rgb;
+ self.red = None;
+ self.green = None;
+ }
+ (n, State::Ansi256) => {
+ self.set_color(Color::Ansi256(n));
+ self.state = State::Normal;
+ }
+ (b, State::Rgb) => match (self.red, self.green) {
+ (None, _) => {
+ self.red = Some(b);
+ }
+ (Some(_), None) => {
+ self.green = Some(b);
+ }
+ (Some(r), Some(g)) => {
+ self.set_color(Color::Rgb(r, g, b));
+ self.state = State::Normal;
+ }
+ },
+ _ => {
+ self.state = State::Normal;
+ }
+ }
+ }
+}
+
+fn parse_color(digit: u8) -> Option<Color> {
+ match digit {
+ 0 => Some(Color::Black),
+ 1 => Some(Color::Red),
+ 2 => Some(Color::Green),
+ 3 => Some(Color::Yellow),
+ 4 => Some(Color::Blue),
+ 5 => Some(Color::Magenta),
+ 6 => Some(Color::Cyan),
+ 7 => Some(Color::White),
+ _ => None,
+ }
+}