ansitok-0.3.0/.cargo_vcs_info.json0000644000000001360000000000100125040ustar { "git": { "sha1": "76593a9df05c602060541c4ff820d516105c7483" }, "path_in_vcs": "" }ansitok-0.3.0/.gitignore000064400000000000000000000000241046102023000132600ustar 00000000000000/target /Cargo.lock ansitok-0.3.0/.gitlab-ci.yml000064400000000000000000000014001046102023000137230ustar 00000000000000stages: - build - lint - test check: stage: build parallel: matrix: - image: [rust:latest, rustlang/rust:nightly] image: ${image} script: - cargo check --verbose clippy: stage: lint image: "rust:latest" before_script: - rustup component add clippy script: - cargo clippy --all fmt: stage: lint image: "rust:latest" before_script: - rustup component add rustfmt script: - cargo fmt --all -- --check test: stage: test parallel: matrix: - image: [rust:latest, rustlang/rust:nightly] feature: ["", "std"] image: ${image} script: - cargo test --no-default-features --features=${feature} test_ignored: stage: test image: "rust:latest" script: - cargo test -- --ignored ansitok-0.3.0/Cargo.lock0000644000000023230000000000100104570ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "ansitok" version = "0.3.0" dependencies = [ "nom", "vte", ] [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "vte" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" dependencies = [ "arrayvec", "memchr", ] ansitok-0.3.0/Cargo.toml0000644000000026760000000000100105150ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "ansitok" version = "0.3.0" authors = ["Maxim Zhiburt "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A library for parsing ANSI Escape Codes" homepage = "https://gitlab.com/zhiburt/ansitok" readme = "README.md" keywords = [ "ansi", "escape", "terminal", ] categories = [ "parser-implementations", "no-std", ] license = "MIT" repository = "https://gitlab.com/zhiburt/ansitok" [lib] name = "ansitok" path = "src/lib.rs" [[example]] name = "parse_ansi" path = "examples/parse_ansi.rs" [[example]] name = "parse_sgr" path = "examples/parse_sgr.rs" [[test]] name = "ansi_sequence" path = "tests/ansi_sequence.rs" [[test]] name = "parse_ansi" path = "tests/parse_ansi.rs" [[test]] name = "parse_sgr" path = "tests/parse_sgr.rs" [dependencies.nom] version = "7.1.1" [dependencies.vte] version = "0.14.1" [features] default = ["std"] std = ["nom/std"] ansitok-0.3.0/Cargo.toml.orig000064400000000000000000000007211046102023000141630ustar 00000000000000[package] name = "ansitok" description = "A library for parsing ANSI Escape Codes" homepage = "https://gitlab.com/zhiburt/ansitok" repository = "https://gitlab.com/zhiburt/ansitok" authors = ["Maxim Zhiburt "] keywords = ["ansi", "escape", "terminal"] categories = ["parser-implementations", "no-std"] version = "0.3.0" license = "MIT" edition = "2021" [dependencies] vte = "0.14.1" nom = "7.1.1" [features] default = ["std"] std = ["nom/std"] ansitok-0.3.0/LICENSE000064400000000000000000000020561046102023000123040ustar 00000000000000MIT License Copyright (c) 2022 Maxim Zhiburt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ansitok-0.3.0/README.md000064400000000000000000000031501046102023000125520ustar 00000000000000ansi escape codes tokenization ============================== [gitlab](https://gitlab.com/zhiburt/ansitok/) [crates.io](https://crates.io/crates/ansitok) [docs.rs](https://docs.rs/ansitok) [build status](https://gitlab.com/zhiburt/ansitok/-/pipelines?ref=master) This is a library for parsing ANSI escape sequences. The list of covered sequences. * Cursor Position * Cursor {Up, Down, Forward, Backward} * Cursor {Save, Restore} * Erase Display * Erase Line * Set Graphics mode * Set/Reset Text Mode # Usage ```rust let text = "\x1b[31;1;4mHello World\x1b[0m"; for e in parse_ansi(text) { match e.kind() { ElementKind::Text => { println!("Got a text: {:?}", &text[e.range()],); } _ => { println!( "Got an escape sequence: {:?} from {:#?} to {:#?}", e.kind(), e.start(), e.end() ); } } } ``` # `no_std` support `no_std` is supported via disabling the `std` feature in your `Cargo.toml`. # Notes The project got an insiration from https://gitlab.com/davidbittner/ansi-parser. ansitok-0.3.0/examples/parse_ansi.rs000064400000000000000000000010231046102023000156000ustar 00000000000000use ansitok::{parse_ansi, ElementKind}; fn main() { let text = "\x1b[31;1;4mHello World\x1b[0m"; for e in parse_ansi(text) { match e.kind() { ElementKind::Text => { println!("Got a text: {:?}", &text[e.range()],); } _ => { println!( "Got an escape sequence: {:?} from {:#?} to {:#?}", e.kind(), e.start(), e.end() ); } } } } ansitok-0.3.0/examples/parse_sgr.rs000064400000000000000000000007261046102023000154520ustar 00000000000000use ansitok::{parse_ansi, parse_ansi_sgr, ElementKind}; fn main() { let text = "\x1b[31;1;4mHello World\x1b[0m \x1b[38;2;255;255;0m!!!\x1b[0m"; for element in parse_ansi(text) { if element.kind() != ElementKind::Sgr { continue; } let text = &text[element.start()..element.end()]; println!("text={:?}", text); for style in parse_ansi_sgr(text) { println!("style={:?}", style); } } } ansitok-0.3.0/src/element.rs000064400000000000000000000041761046102023000140720ustar 00000000000000use core::ops::Range; /// Element indicates a slice position in the string and its type. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Element { pos: (usize, usize), kind: ElementKind, } impl Element { /// Creates new [Element] object. pub fn new(start: usize, end: usize, kind: ElementKind) -> Self { Self { pos: (start, end), kind, } } /// Creates [Element] with [ElementKind::Sgr] type. pub fn sgr(start: usize, end: usize) -> Element { Element::new(start, end, ElementKind::Sgr) } /// Creates [Element] with [ElementKind::Csi] type. pub fn csi(start: usize, end: usize) -> Element { Element::new(start, end, ElementKind::Csi) } /// Creates [Element] with [ElementKind::Osc] type. pub fn osc(start: usize, end: usize) -> Element { Element::new(start, end, ElementKind::Osc) } /// Creates [Element] with [ElementKind::Esc] type. pub fn esc(start: usize, end: usize) -> Element { Element::new(start, end, ElementKind::Esc) } /// Creates [Element] with [ElementKind::Text] type. pub fn text(start: usize, end: usize) -> Element { Element::new(start, end, ElementKind::Text) } /// Returns an element type. pub fn kind(&self) -> ElementKind { self.kind } /// Returns a start position of a slice. pub fn start(&self) -> usize { self.pos.0 } /// Returns an end position of a slice. pub fn end(&self) -> usize { self.pos.1 } /// Returns the range of a slice. pub fn range(&self) -> Range { Range { start: self.pos.0, end: self.pos.1, } } } /// A type of a section in a text with ANSI sequences. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum ElementKind { /// ESC starts all the escape sequences <^[> '0x1B'. Esc, /// SGR (Select Graphic Rendition) parameters. Sgr, /// CSI (Control Sequence Introducer) sequences. Csi, /// OSC (Operating System Command) sequences. Osc, /// Text section. Text, } ansitok-0.3.0/src/lib.rs000064400000000000000000000026701046102023000132040ustar 00000000000000#![recursion_limit = "256"] #![cfg_attr(not(any(feature = "std", test)), no_std)] #![warn(missing_docs)] //! This is a crate is made for parsing ANSI escape sequences. //! //! The list of covered sequences. //! //! * Cursor Position //! * Cursor {Up, Down, Forward, Backward} //! * Cursor {Save, Restore} //! * Erase Display //! * Erase Line //! * Set Graphics mode //! * Set/Reset Text Mode //! //! # Usage //! //! ``` //! use ansitok::parse_ansi; //! //! let text = "\x1b[31;1;4mHello World\x1b[0m"; //! for token in parse_ansi(text) { //! let kind = token.kind(); //! let token_text = &text[token.start()..token.end()]; //! //! println!("text={:?} kind={:?}", token_text, kind); //! } //! ``` //! //! Parse SGR. //! //! ``` //! use ansitok::{parse_ansi, parse_ansi_sgr, Output, ElementKind}; //! //! let text = "\x1b[31;1;4mHello World\x1b[0m \x1b[38;2;255;255;0m!!!\x1b[0m"; //! for token in parse_ansi(text) { //! if token.kind() != ElementKind::Sgr { //! continue; //! } //! //! let sgr = &text[token.start()..token.end()]; //! for style in parse_ansi_sgr(sgr) { //! println!("style={:?}", style); //! let style = style.as_escape().unwrap(); //! println!("style={:?}", style); //! } //! } //! ``` mod element; mod parse; pub use element::{Element, ElementKind}; pub use parse::{ parse_ansi, parse_ansi_sgr, AnsiColor, AnsiIterator, EscapeCode, Output, SGRParser, VisualAttribute, }; ansitok-0.3.0/src/parse/ansi_parser.rs000064400000000000000000000222721046102023000160560ustar 00000000000000use core::str::Bytes; use vte::Params; use crate::Element; /// Creates a parser for ANSI escape sequences. pub fn parse_ansi(text: &str) -> AnsiIterator<'_> { AnsiIterator::new(text) } /// An ANSI escape sequence parser. pub struct AnsiIterator<'a> { // The input bytes bytes: Bytes<'a>, // The state machine machine: vte::Parser, // Becomes non-None when the parser finishes parsing an ANSI sequence. // This is never Element::Text. element: Option, // Number of text bytes seen since the last element was emitted. text_length: usize, // Byte offset of start of current element. start: usize, // Byte offset of most rightward byte processed so far pos: usize, // A marker that the previous byte was ESC is_prev_esc: bool, } #[derive(Default)] struct Performer { // Becomes non-None when the parser finishes parsing an ANSI sequence. // This is never Element::Text. element: Option, // Number of text bytes seen since the last element was emitted. text_length: usize, } impl AnsiIterator<'_> { fn new(s: &str) -> AnsiIterator<'_> { AnsiIterator { machine: vte::Parser::new(), bytes: s.bytes(), element: None, text_length: 0, start: 0, pos: 0, is_prev_esc: false, } } fn advance_vte(&mut self, bytes: &[u8]) { let mut performer = Performer::default(); self.machine.advance(&mut performer, bytes); self.element = performer.element; self.text_length += performer.text_length; self.pos += 1; } } impl Iterator for AnsiIterator<'_> { type Item = Element; fn next(&mut self) -> Option { // we need to checked whether ESC was closed let mut esc_started = false; // If the last element emitted was text, then there may be a non-text element waiting // to be emitted. In that case we do not consume a new byte. while self.element.is_none() { match self.bytes.next() { Some(b) => { self.advance_vte(&[b]); // in such case we want to return a ESC let is_esc = b == 27; if is_esc { if self.is_prev_esc { let start = self.start; self.start += 1; let element = Element::esc(start, self.start); return Some(element); } esc_started = true; self.is_prev_esc = true; // if given text starts with no ESC // we wanna return it first until the ESC if self.text_length > 0 { let start = self.start; self.start += self.text_length; self.text_length = 0; return Some(Element::text(start, self.start)); } } else { self.is_prev_esc = false; } } None => break, } } if let Some(element) = self.element.take() { // There is a non-text element waiting to be emitted, but it may have preceding // text, which must be emitted first. if self.text_length > 0 { let start = self.start; self.start += self.text_length; self.text_length = 0; self.element = Some(element); return Some(Element::text(start, self.start)); } let start = self.start; self.start = self.pos; let element = Element::new(start, self.pos, element.kind()); return Some(element); } if self.text_length > 0 { self.text_length = 0; return Some(Element::text(self.start, self.pos)); } if self.is_prev_esc { let start = self.start; self.start += 1; self.is_prev_esc = false; let element = Element::esc(start, self.start); return Some(element); } // ESC can be left opened with text after that // we check here that it's the case and return it in such cases if self.text_length == 0 && esc_started { let start = self.start; self.start = self.start + 1; self.text_length = self.pos - self.start; let element = Element::esc(start, self.start); return Some(element); } None } } // Based on https://github.com/alacritty/vte/blob/v0.9.0/examples/parselog.rs impl vte::Perform for Performer { fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { if ignore || intermediates.len() > 1 { return; } let is_sgr = c == 'm' && intermediates.first().is_none(); let element = if is_sgr { if params.is_empty() { // Attr::Reset // Probably doesn't need to be handled: https://github.com/dandavison/delta/pull/431#discussion_r536883568 None } else { Some(Element::sgr(0, 0)) } } else { Some(Element::csi(0, 0)) }; self.element = element; } fn print(&mut self, c: char) { self.text_length += c.len_utf8(); } fn execute(&mut self, byte: u8) { // E.g. '\n' if byte < 128 { self.text_length += 1; } } fn hook(&mut self, _params: &Params, _intermediates: &[u8], _ignore: bool, _c: char) {} fn put(&mut self, _byte: u8) {} fn unhook(&mut self) {} fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) { self.element = Some(Element::osc(0, 0)); } fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) { self.element = Some(Element::esc(0, 0)); } } #[cfg(test)] mod tests { use super::*; #[test] fn test_iterator_1() { let text = "\x1b[31m0123\x1b[m\n"; let elements: Vec<_> = AnsiIterator::new(text).collect(); assert_eq!( elements, vec![ Element::sgr(0, 5), Element::text(5, 9), Element::sgr(9, 12), Element::text(12, 13), ] ); } #[test] fn test_iterator_2() { let text = "\x1b[31m0123\x1b[m456\n"; let elements: Vec = AnsiIterator::new(text).collect(); assert_eq!( elements, vec![ Element::sgr(0, 5), Element::text(5, 9), Element::sgr(9, 12), Element::text(12, 16), ] ); assert_eq!("0123", &text[5..9]); assert_eq!("456\n", &text[12..16]); } #[test] fn test_iterator_styled_non_ascii() { let text = "\x1b[31mバー\x1b[0m"; let elements: Vec = AnsiIterator::new(text).collect(); assert_eq!( elements, vec![ Element::sgr(0, 5), Element::text(5, 11), Element::sgr(11, 15), ] ); assert_eq!("バー", &text[5..11]); } #[test] fn test_iterator_erase_in_line() { let text = "\x1b[0Kあ.\x1b[m"; let elements: Vec<_> = AnsiIterator::new(text).collect(); assert_eq!( elements, vec![Element::csi(0, 4), Element::text(4, 8), Element::sgr(8, 11),] ); assert_eq!("あ.", &text[4..8]); } #[test] fn test_iterator_erase_in_line_without_n() { let text = "\x1b[Kあ.\x1b[m"; let actual_elements: Vec = AnsiIterator::new(text).collect(); assert_eq!( actual_elements, vec![Element::csi(0, 3), Element::text(3, 7), Element::sgr(7, 10),] ); assert_eq!("あ.", &text[3..7]); } #[test] fn test_iterator_osc_hyperlinks_styled_non_ascii() { let text = "\x1b[38;5;4m\x1b]8;;file:///Users/dan/src/delta/src/ansi/mod.rs\x1b\\src/ansi/modバー.rs\x1b]8;;\x1b\\\x1b[0m\n"; let elements: Vec = AnsiIterator::new(text).collect(); assert_eq!( elements, vec![ Element::sgr(0, 9), Element::osc(9, 58), Element::esc(58, 59), Element::text(59, 80), Element::osc(80, 86), Element::esc(86, 87), Element::sgr(87, 91), Element::text(91, 92), ] ); assert_eq!(&text[0..9], "\x1b[38;5;4m"); assert_eq!( &text[9..58], "\x1b]8;;file:///Users/dan/src/delta/src/ansi/mod.rs\x1b" ); assert_eq!(&text[58..59], "\\"); assert_eq!(&text[59..80], "src/ansi/modバー.rs"); assert_eq!(&text[80..86], "\x1b]8;;\x1b"); assert_eq!(&text[86..87], "\\"); assert_eq!(&text[87..91], "\x1b[0m"); assert_eq!(&text[91..92], "\n"); } } ansitok-0.3.0/src/parse/escape_sequence.rs000064400000000000000000000155341046102023000167030ustar 00000000000000use core::fmt::{self, Display, Formatter}; use super::parsers::parse_escape_sequence; /// An ANSI Escape Sequence. /// /// You can find some specification on /// /// - [wiki](https://en.wikipedia.org/wiki/ANSI_escape_code) /// - [VT51](https://web.archive.org/web/20090227051140/http://ascii-table.com/ansi-escape-sequences-vt-100.php) #[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Clone, Copy, Hash)] #[non_exhaustive] pub enum EscapeCode<'a> { /// A move cursor backward. /// /// Moves the cursor n (default 1) cells backwards. CursorBackward(u32), /// A cursor down. /// /// Moves the cursor n (default 1) cells down. CursorDown(u32), /// A move cursor forward. /// /// Moves the cursor n (default 1) cells forward. CursorForward(u32), /// A cursor position. /// /// The values are 1-based, and default to 1 (top left corner) if omitted. CursorPos(u32, u32), /// A restore of current cursor position/state. CursorRestore, /// A save of current cursor position/state. CursorSave, /// Set cursor key to application CursorToApp, /// A cursor up. /// /// Moves the cursor n (default 1) cells up. CursorUp(u32), /// Erase in Display. EraseDisplay, /// Erase in Display. EraseLine, /// A ESC sequence. Escape, /// Hide the cursor. HideCursor, /// Reset auto repeat. ResetAutoRepeat, /// Reset auto wrap. ResetAutoWrap, /// Reset interlacin. ResetInterlacing, /// Erase in Display. ResetMode(u8), /// Select Graphic Rendition (SGR), sets display attributes. SelectGraphicRendition(&'a str), /// Set alternate keypad. SetAlternateKeypad, /// Set auto repeat. SetAutoRepeat, /// Set auto wrap. SetAutoWrap, /// Set number of columns to 132 SetCol132, /// Set number of columns to 80 SetCol80, /// Set cursor key to cursor. SetCursorKeyToCursor, /// Set G0 alt char ROM and spec. graphics. SetG0AltAndSpecialGraph, /// Set G0 alternate character ROM. SetG0AlternateChar, /// Set G0 special chars. & line set. SetG0SpecialChars, /// Set G1 alt char ROM and spec. graphics. SetG1AltAndSpecialGraph, /// Set G1 alternate character ROM. SetG1AlternateChar, /// Set G1 special chars. & line set. SetG1SpecialChars, /// Set interlacing. SetInterlacing, /// Set jump scrolling. SetJumpScrolling, /// Set line feed mode. SetLineFeedMode, /// Erase in Display. SetMode(u8), /// Set new line mode. SetNewLineMode, /// Set normal video. SetNormalVideo, /// Set numeric keypad. SetNumericKeypad, /// Set origin absolute. SetOriginAbsolute, /// Set origin relative. SetOriginRelative, /// Set reverse video. SetReverseVideo, /// Set single shift 2. SetSingleShift2, /// Set single shift 3. SetSingleShift3, /// Set smooth scroll. SetSmoothScroll, /// Set top and bottom lines of a window. SetTopAndBottom(u32, u32), /// Set United Kingdom G0 character set. SetUKG0, /// Set United Kingdom G1 character set. SetUKG1, /// Set United States G0 character set. SetUSG0, /// Set United States G1 character set. SetUSG1, /// Set VT52. SetVT52, /// Show the cursor. ShowCursor, } impl EscapeCode<'_> { /// Parse an escape code. /// returns None if the sequence is not supported or it can't be parsed. pub fn parse(text: &str) -> Option> { let (_, seq) = parse_escape_sequence(text).ok()?; Some(seq) } } impl Display for EscapeCode<'_> { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { write!(formatter, "\u{1b}")?; use EscapeCode::*; match self { Escape => write!(formatter, "\u{1b}"), CursorPos(line, col) => write!(formatter, "[{};{}H", line, col), CursorUp(amt) => write!(formatter, "[{}A", amt), CursorDown(amt) => write!(formatter, "[{}B", amt), CursorForward(amt) => write!(formatter, "[{}C", amt), CursorBackward(amt) => write!(formatter, "[{}D", amt), CursorSave => write!(formatter, "[s"), CursorRestore => write!(formatter, "[u"), EraseDisplay => write!(formatter, "[2J"), EraseLine => write!(formatter, "[K"), SelectGraphicRendition(mode) => write!(formatter, "[{}m", mode), SetMode(mode) => write!(formatter, "[={}h", mode), ResetMode(mode) => write!(formatter, "[={}l", mode), ShowCursor => write!(formatter, "[?25h"), HideCursor => write!(formatter, "[?25l"), CursorToApp => write!(formatter, "[?1h"), SetNewLineMode => write!(formatter, "[20h"), SetCol132 => write!(formatter, "[?3h"), SetSmoothScroll => write!(formatter, "[?4h"), SetReverseVideo => write!(formatter, "[?5h"), SetOriginRelative => write!(formatter, "[?6h"), SetAutoWrap => write!(formatter, "[?7h"), SetAutoRepeat => write!(formatter, "[?8h"), SetInterlacing => write!(formatter, "[?9h"), SetLineFeedMode => write!(formatter, "[20l"), SetCursorKeyToCursor => write!(formatter, "[?1l"), SetVT52 => write!(formatter, "[?2l"), SetCol80 => write!(formatter, "[?3l"), SetJumpScrolling => write!(formatter, "[?4l"), SetNormalVideo => write!(formatter, "[?5l"), SetOriginAbsolute => write!(formatter, "[?6l"), ResetAutoWrap => write!(formatter, "[?7l"), ResetAutoRepeat => write!(formatter, "[?8l"), ResetInterlacing => write!(formatter, "[?9l"), SetAlternateKeypad => write!(formatter, "="), SetNumericKeypad => write!(formatter, ">"), SetUKG0 => write!(formatter, "(A"), SetUKG1 => write!(formatter, ")A"), SetUSG0 => write!(formatter, "(B"), SetUSG1 => write!(formatter, ")B"), SetG0SpecialChars => write!(formatter, "(0"), SetG1SpecialChars => write!(formatter, ")0"), SetG0AlternateChar => write!(formatter, "(1"), SetG1AlternateChar => write!(formatter, ")1"), SetG0AltAndSpecialGraph => write!(formatter, "(2"), SetG1AltAndSpecialGraph => write!(formatter, ")2"), SetSingleShift2 => write!(formatter, "N"), SetSingleShift3 => write!(formatter, "O"), SetTopAndBottom(x, y) => write!(formatter, "{};{}r", x, y), } } } #[cfg(test)] mod tests { use super::*; use std::fmt::Write; #[test] fn test_cursor_pos() { let pos = EscapeCode::CursorPos(5, 20); let mut buff = String::new(); write!(&mut buff, "{}", pos).expect("failed to write"); assert_eq!(buff, "\x1b[5;20H"); } } ansitok-0.3.0/src/parse/mod.rs000064400000000000000000000004751046102023000143300ustar 00000000000000mod ansi_parser; mod escape_sequence; mod output; mod parsers; mod sgr_parser; mod visual_attribute; pub use ansi_parser::{parse_ansi, AnsiIterator}; pub use escape_sequence::EscapeCode; pub use output::Output; pub use sgr_parser::{parse_ansi_sgr, SGRParser}; pub use visual_attribute::{AnsiColor, VisualAttribute}; ansitok-0.3.0/src/parse/output.rs000064400000000000000000000017711046102023000151110ustar 00000000000000use core::fmt::{Display, Formatter, Result as DisplayResult}; /// The type which represents a result of parsing. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Output<'a, S> { /// A string output. Text(&'a str), /// An escape output. Escape(S), } impl<'a, S> Output<'a, S> { /// Returns an escape sequence. pub fn as_escape(self) -> Option { match self { Output::Text(_) => None, Output::Escape(esc) => Some(esc), } } /// Returns a text. pub fn as_text(self) -> Option<&'a str> { match self { Output::Text(text) => Some(text), Output::Escape(_) => None, } } } impl<'a, S> Display for Output<'a, S> where S: Display, { fn fmt(&self, formatter: &mut Formatter) -> DisplayResult { use Output::*; match self { Text(txt) => write!(formatter, "{}", txt), Escape(seq) => write!(formatter, "{}", seq), } } } ansitok-0.3.0/src/parse/parsers/mod.rs000064400000000000000000000003041046102023000157760ustar 00000000000000mod parse_escape_sequence; mod parse_util; mod parse_visual_attribute; pub(crate) use parse_escape_sequence::parse_escape_sequence; pub(crate) use parse_visual_attribute::parse_visual_attribute; ansitok-0.3.0/src/parse/parsers/parse_escape_sequence.rs000064400000000000000000000272771046102023000215630ustar 00000000000000use nom::{ branch::alt, bytes::{complete::take_until, streaming::tag}, combinator::opt, IResult, }; use crate::parse::escape_sequence::EscapeCode; use super::parse_util::{parse_u32_default, parse_u8}; pub(crate) fn parse_escape_sequence(input: &str) -> IResult<&str, EscapeCode<'_>> { let (input, _) = tag("\u{1b}")(input)?; if input.is_empty() { return Ok((input, EscapeCode::Escape)); } parse::peak_parser(input) } mod parse { use super::*; use parsers::*; pub fn peak_parser(input: &str) -> IResult<&str, EscapeCode<'_>> { alt(( alt(( escape, cursor_pos, cursor_up, cursor_down, cursor_forward, cursor_backward, cursor_save, cursor_restore, erase_display, erase_line, set_mode, )), alt(( reset_mode, hide_cursor, show_cursor, cursor_to_app, set_new_line_mode, set_col_132, set_smooth_scroll, set_reverse_video, set_origin_rel, set_auto_wrap, set_auto_repeat, set_interlacing, set_linefeed, )), alt(( set_cursorkey, set_vt52, set_col80, set_jump_scroll, set_normal_video, set_origin_abs, reset_auto_wrap, reset_auto_repeat, reset_interlacing, set_top_and_bottom, set_alternate_keypad, set_numeric_keypad, )), alt(( set_uk_g0, set_uk_g1, set_us_g0, set_us_g1, set_g0_special, set_g1_special, set_g0_alternate, set_g1_alternate, set_g0_graph, set_g1_graph, set_single_shift2, set_single_shift3, graphics_mode, // greedy so must be at the end )), ))(input) } #[rustfmt::skip] mod parsers { use super::*; macro_rules! tag_parser { ($sig:ident, $val:expr, $ret:expr) => { pub fn $sig(input: &str) -> IResult<&str, EscapeCode<'_>> { let (input, _) = nom::bytes::streaming::tag($val)(input)?; Ok((input, $ret)) } }; } macro_rules! int_parser_1_u32 { ($sig:ident, $prefix:expr, $suffix:expr, $obj:expr) => { pub fn $sig(input: &str) -> IResult<&str, EscapeCode<'_>> { let (input, _) = tag($prefix)(input)?; let (input, am) = parse_u32_default(input, 1)?; let (input, _) = tag($suffix)(input)?; Ok((input, $obj(am))) } }; } macro_rules! int_parser_2_u32 { ($sig:ident, $prefix:expr, $suffix:expr, $obj:expr) => { pub fn $sig(input: &str) -> IResult<&str, EscapeCode<'_>> { let (input, _) = $prefix(input)?; let (input, x) = parse_u32_default(input, 1)?; let (input, _) = opt(tag(";"))(input)?; let (input, y) = parse_u32_default(input, 1)?; let (input, _) = $suffix(input)?; Ok((input, $obj(x, y))) } }; } macro_rules! int_parser_1_u8 { ($sig:ident, $prefix:expr, $suffix:expr, $obj:expr) => { pub fn $sig(input: &str) -> IResult<&str, EscapeCode<'_>> { let (input, _) = tag($prefix)(input)?; let (input, am) = parse_u8(input)?; let (input, _) = tag($suffix)(input)?; Ok((input, $obj(am))) } }; } int_parser_2_u32!(cursor_pos , tag("["), alt((tag("H"), tag("f"))), EscapeCode::CursorPos); int_parser_2_u32!(set_top_and_bottom, tag("["), tag("r"), EscapeCode::SetTopAndBottom); int_parser_1_u32!(cursor_up, "[", "A", EscapeCode::CursorUp); int_parser_1_u32!(cursor_down, "[", "B", EscapeCode::CursorDown); int_parser_1_u32!(cursor_forward, "[", "C", EscapeCode::CursorForward); int_parser_1_u32!(cursor_backward, "[", "D", EscapeCode::CursorBackward); int_parser_1_u8!(set_mode, "[=", "h", EscapeCode::SetMode); int_parser_1_u8!(reset_mode, "[=", "l", EscapeCode::ResetMode); tag_parser!(escape, "\u{1b}", EscapeCode::Escape); tag_parser!(cursor_save, "[s", EscapeCode::CursorSave); tag_parser!(cursor_restore, "[u", EscapeCode::CursorRestore); tag_parser!(erase_display, "[2J", EscapeCode::EraseDisplay); tag_parser!(erase_line, "[K", EscapeCode::EraseLine); tag_parser!(hide_cursor, "[?25l", EscapeCode::HideCursor); tag_parser!(show_cursor, "[?25h", EscapeCode::ShowCursor); tag_parser!(cursor_to_app, "[?1h", EscapeCode::CursorToApp); tag_parser!(set_new_line_mode, "[20h", EscapeCode::SetNewLineMode); tag_parser!(set_col_132, "[?3h", EscapeCode::SetCol132); tag_parser!(set_smooth_scroll, "[?4h", EscapeCode::SetSmoothScroll); tag_parser!(set_reverse_video, "[?5h", EscapeCode::SetReverseVideo); tag_parser!(set_origin_rel, "[?6h", EscapeCode::SetOriginRelative); tag_parser!(set_auto_wrap, "[?7h", EscapeCode::SetAutoWrap); tag_parser!(set_auto_repeat, "[?8h", EscapeCode::SetAutoRepeat); tag_parser!(set_interlacing, "[?9h", EscapeCode::SetInterlacing); tag_parser!(set_linefeed, "[20l", EscapeCode::SetLineFeedMode); tag_parser!(set_cursorkey, "[?1l", EscapeCode::SetCursorKeyToCursor); tag_parser!(set_vt52, "[?2l", EscapeCode::SetVT52); tag_parser!(set_col80, "[?3l", EscapeCode::SetCol80); tag_parser!(set_jump_scroll, "[?4l", EscapeCode::SetJumpScrolling); tag_parser!(set_normal_video, "[?5l", EscapeCode::SetNormalVideo); tag_parser!(set_origin_abs, "[?6l", EscapeCode::SetOriginAbsolute); tag_parser!(reset_auto_wrap, "[?7l", EscapeCode::ResetAutoWrap); tag_parser!(reset_auto_repeat, "[?8l", EscapeCode::ResetAutoRepeat); tag_parser!(reset_interlacing, "[?9l", EscapeCode::ResetInterlacing); tag_parser!(set_alternate_keypad, "=", EscapeCode::SetAlternateKeypad); tag_parser!(set_numeric_keypad, ">", EscapeCode::SetNumericKeypad); tag_parser!(set_uk_g0, "(A", EscapeCode::SetUKG0); tag_parser!(set_uk_g1, ")A", EscapeCode::SetUKG1); tag_parser!(set_us_g0, "(B", EscapeCode::SetUSG0); tag_parser!(set_us_g1, ")B", EscapeCode::SetUSG1); tag_parser!(set_g0_special, "(0", EscapeCode::SetG0SpecialChars); tag_parser!(set_g1_special, ")0", EscapeCode::SetG1SpecialChars); tag_parser!(set_g0_alternate, "(1", EscapeCode::SetG0AlternateChar); tag_parser!(set_g1_alternate, ")1", EscapeCode::SetG1AlternateChar); tag_parser!(set_g0_graph, "(2", EscapeCode::SetG0AltAndSpecialGraph); tag_parser!(set_g1_graph, ")2", EscapeCode::SetG1AltAndSpecialGraph); tag_parser!(set_single_shift2, "N", EscapeCode::SetSingleShift2); tag_parser!(set_single_shift3, "O", EscapeCode::SetSingleShift3); pub fn graphics_mode(input: &str) -> IResult<&str, EscapeCode> { let (input, _) = tag("[")(input)?; let (input, mode) = take_until("m")(input)?; let (input, _) = tag("m")(input)?; Ok((input, EscapeCode::SelectGraphicRendition(mode))) } } } #[cfg(test)] mod tests { use super::*; macro_rules! test_parse { ($name:ident, $string:expr) => { #[test] fn $name() { let (_, ret) = parse_escape_sequence($string).unwrap(); use std::fmt::Write; let mut buff = String::new(); write!(&mut buff, "{}", ret).unwrap(); assert_eq!(buff, $string); let (_, ret2) = parse_escape_sequence(&buff).unwrap(); assert_eq!(ret, ret2); } }; } test_parse!(cursor_pos, "\u{1b}[10;5H"); test_parse!(cursor_up, "\u{1b}[5A"); test_parse!(cursor_down, "\u{1b}[5B"); test_parse!(cursor_forward, "\u{1b}[5C"); test_parse!(cursor_backward, "\u{1b}[5D"); test_parse!(cursor_save, "\u{1b}[s"); test_parse!(cursor_restore, "\u{1b}[u"); test_parse!(erase_display, "\u{1b}[2J"); test_parse!(erase_line, "\u{1b}[K"); test_parse!(set_video_mode_a, "\u{1b}[4m"); test_parse!(set_video_mode_b, "\u{1b}[4;42m"); test_parse!(set_video_mode_c, "\u{1b}[4;31;42m"); test_parse!(set_video_mode_d, "\u{1b}[4;31;42;42;42m"); test_parse!(reset_mode, "\u{1b}[=13l"); test_parse!(set_mode, "\u{1b}[=7h"); test_parse!(show_cursor, "\u{1b}[?25h"); test_parse!(hide_cursor, "\u{1b}[?25l"); test_parse!(cursor_to_app, "\u{1b}[?1h"); test_parse!(set_newline_mode, "\u{1b}[20h"); test_parse!(set_column_132, "\u{1b}[?3h"); test_parse!(set_smooth_scroll, "\u{1b}[?4h"); test_parse!(set_reverse_video, "\u{1b}[?5h"); test_parse!(set_origin_rel, "\u{1b}[?6h"); test_parse!(set_auto_wrap, "\u{1b}[?7h"); test_parse!(set_auto_repeat, "\u{1b}[?8h"); test_parse!(set_interlacing, "\u{1b}[?9h"); test_parse!(set_cursor_key_to_cursor, "\u{1b}[?1l"); test_parse!(set_linefeed, "\u{1b}[20l"); test_parse!(set_vt52, "\u{1b}[?2l"); test_parse!(set_col80, "\u{1b}[?3l"); test_parse!(set_jump_scroll, "\u{1b}[?4l"); test_parse!(set_normal_video, "\u{1b}[?5l"); test_parse!(set_origin_abs, "\u{1b}[?6l"); test_parse!(reset_auto_wrap, "\u{1b}[?7l"); test_parse!(reset_auto_repeat, "\u{1b}[?8l"); test_parse!(reset_interlacing, "\u{1b}[?9l"); test_parse!(set_alternate_keypad, "\u{1b}="); test_parse!(set_numeric_keypad, "\u{1b}>"); test_parse!(set_uk_g0, "\u{1b}(A"); test_parse!(set_uk_g1, "\u{1b})A"); test_parse!(set_us_g0, "\u{1b}(B"); test_parse!(set_us_g1, "\u{1b})B"); test_parse!(set_g0_special, "\u{1b}(0"); test_parse!(set_g1_special, "\u{1b})0"); test_parse!(set_g0_alternate, "\u{1b}(1"); test_parse!(set_g1_alternate, "\u{1b})1"); test_parse!(set_g0_graph, "\u{1b}(2"); test_parse!(set_g1_graph, "\u{1b})2"); test_parse!(set_single_shift2, "\u{1b}N"); test_parse!(set_single_shift3, "\u{1b}O"); macro_rules! test_parse_default { ($name:ident, $string:expr) => { #[test] fn $name() { let mut buff = String::new(); let (_, ret) = parse_escape_sequence($string).unwrap(); use std::fmt::Write; write!(&mut buff, "{}", ret).unwrap(); let ret2 = parse_escape_sequence(&buff); assert!(ret2.is_ok()); let ret2 = ret2.unwrap().1; assert_eq!(ret, ret2); } }; } test_parse_default!(cursor_pos_default, "\u{1b}[H"); test_parse_default!(cursor_up_default, "\u{1b}[A"); } ansitok-0.3.0/src/parse/parsers/parse_util.rs000064400000000000000000000012521046102023000173710ustar 00000000000000use nom::{ character::complete::digit1, combinator::{map_res, opt}, IResult, }; pub(crate) fn parse_u8(input: &str) -> IResult<&str, u8> { map_res(digit1, decimal_u8)(input) } pub(crate) fn decimal_u8(input: &str) -> Result { input.parse::() } pub(crate) fn parse_u32_default(input: &str, default: u32) -> IResult<&str, u32> { parse_u32(input).map(|(input, n)| (input, n.unwrap_or(default))) } pub(crate) fn parse_u32(input: &str) -> IResult<&str, Option> { opt(map_res(digit1, decimal_u32))(input) } pub(crate) fn decimal_u32(input: &str) -> Result { input.parse::() } ansitok-0.3.0/src/parse/parsers/parse_visual_attribute.rs000064400000000000000000000207361046102023000220120ustar 00000000000000use nom::{ branch::alt, bytes::complete::tag, character::complete::digit0, combinator::map, IResult, }; use crate::{AnsiColor, VisualAttribute}; pub(crate) fn parse_visual_attribute(input: &str) -> IResult<&str, VisualAttribute> { peak_parser(input) } fn peak_parser(input: &str) -> IResult<&str, VisualAttribute> { use parsers::*; alt(( alt((gm_fg_color, gm_bg_color, gm_undr_color)), alt(( gm_bg_b_0, gm_bg_b_1, gm_bg_b_2, gm_bg_b_3, gm_bg_b_4, gm_bg_b_5, gm_bg_b_6, gm_bg_b_7, )), alt(( gm_font_0, gm_font_1, gm_font_2, gm_font_3, gm_font_4, gm_font_5, gm_font_6, gm_font_7, gm_font_8, )), alt(( gm_reset_0, gm_reset_22, gm_reset_23, gm_reset_24, gm_reset_25, gm_reset_27, gm_reset_28, gm_reset_29, gm_reset_39, gm_reset_49, gm_reset_50, gm_reset_54, gm_reset_55, gm_reset_59, gm_reset_65, gm_reset_75, )), alt(( gm_underline_double, gm_proportional_spacing, gm_framed, gm_encircled, gm_overlined, gm_reset_font, gm_fraktur, )), alt(( gm_igrm_underline, gm_igrm_underline_double, gm_igrm_overline, gm_igrm_overline_double, gm_igrm_stress_marking, gm_superscript, gm_subscript, )), alt(( gm_fg_0, gm_fg_1, gm_fg_2, gm_fg_3, gm_fg_4, gm_fg_5, gm_fg_6, gm_fg_7, )), alt(( gm_bg_0, gm_bg_1, gm_bg_2, gm_bg_3, gm_bg_4, gm_bg_5, gm_bg_6, gm_bg_7, )), alt(( gm_fg_b_0, gm_fg_b_1, gm_fg_b_2, gm_fg_b_3, gm_fg_b_4, gm_fg_b_5, gm_fg_b_6, gm_fg_b_7, )), alt(( gm_bold, gm_faint, gm_italic, gm_underline, gm_blink_slow, gm_blink_rapid, gm_inverse, gm_hide, gm_crossedout, )), ))(input) } mod parsers { use super::*; use VisualAttribute::*; macro_rules! gm_parse { ($sig:ident, $val:expr, $ret:expr) => { pub fn $sig(input: &str) -> IResult<&str, VisualAttribute> { let (input, _) = nom::bytes::complete::tag($val)(input)?; Ok((input, $ret)) } }; } macro_rules! gm_parse_color { ($sig:ident, $val:expr, $ret:expr) => { pub fn $sig(input: &str) -> IResult<&str, VisualAttribute> { let (input, _) = nom::bytes::complete::tag($val)(input)?; let (input, _) = nom::bytes::complete::tag(";")(input)?; let (input, color) = parse_bit_color(input)?; let result = $ret(color); Ok((input, result)) } }; } fn parse_bit_color(input: &str) -> IResult<&str, AnsiColor> { alt(( map(parse_8_bit_color, AnsiColor::Bit8), map(parse_24_bit_color, |[r, g, b]| AnsiColor::Bit24 { r, g, b }), ))(input) } fn parse_8_bit_color(input: &str) -> IResult<&str, u8> { let (input, _) = tag("5")(input)?; let (input, _) = tag(";")(input)?; let (input, index) = opt_u8(input, 0)?; Ok((input, index)) } fn parse_24_bit_color(input: &str) -> IResult<&str, [u8; 3]> { let (input, _) = tag("2")(input)?; let (input, _) = tag(";")(input)?; let (input, r) = opt_u8(input, 0)?; let (input, _) = tag(";")(input)?; let (input, g) = opt_u8(input, 0)?; let (input, _) = tag(";")(input)?; let (input, b) = opt_u8(input, 0)?; Ok((input, [r, g, b])) } fn opt_u8(input: &str, default: u8) -> IResult<&str, u8> { let (input, nums) = digit0(input)?; if nums.is_empty() { return Ok((input, default)); } let num = u8_from_dec(nums).unwrap_or(default); Ok((input, num)) } fn u8_from_dec(input: &str) -> Result { input.parse::() } // gm_parse!(gm_reset_0, "", Reset(0)); gm_parse!(gm_reset_0, "0", Reset(0)); gm_parse!(gm_bold, "1", Bold); gm_parse!(gm_faint, "2", Faint); gm_parse!(gm_italic, "3", Italic); gm_parse!(gm_underline, "4", Underline); gm_parse!(gm_blink_slow, "5", SlowBlink); gm_parse!(gm_blink_rapid, "6", RapidBlink); gm_parse!(gm_inverse, "7", Inverse); gm_parse!(gm_hide, "8", Hide); gm_parse!(gm_crossedout, "9", Crossedout); gm_parse!(gm_reset_font, "10", Reset(10)); gm_parse!(gm_font_0, "11", Font(11)); gm_parse!(gm_font_1, "12", Font(12)); gm_parse!(gm_font_2, "13", Font(13)); gm_parse!(gm_font_3, "14", Font(14)); gm_parse!(gm_font_4, "15", Font(15)); gm_parse!(gm_font_5, "16", Font(16)); gm_parse!(gm_font_6, "17", Font(17)); gm_parse!(gm_font_7, "18", Font(18)); gm_parse!(gm_font_8, "19", Font(19)); gm_parse!(gm_fraktur, "20", Fraktur); gm_parse!(gm_underline_double, "21", DoubleUnderline); gm_parse!(gm_reset_22, "22", Reset(22)); gm_parse!(gm_reset_23, "23", Reset(23)); gm_parse!(gm_reset_24, "24", Reset(24)); gm_parse!(gm_reset_25, "25", Reset(25)); gm_parse!(gm_proportional_spacing, "26", ProportionalSpacing); gm_parse!(gm_reset_27, "27", Reset(27)); gm_parse!(gm_reset_28, "28", Reset(28)); gm_parse!(gm_reset_29, "29", Reset(29)); gm_parse!(gm_fg_0, "30", FgColor(AnsiColor::Bit4(30))); gm_parse!(gm_fg_1, "31", FgColor(AnsiColor::Bit4(31))); gm_parse!(gm_fg_2, "32", FgColor(AnsiColor::Bit4(32))); gm_parse!(gm_fg_3, "33", FgColor(AnsiColor::Bit4(33))); gm_parse!(gm_fg_4, "34", FgColor(AnsiColor::Bit4(34))); gm_parse!(gm_fg_5, "35", FgColor(AnsiColor::Bit4(35))); gm_parse!(gm_fg_6, "36", FgColor(AnsiColor::Bit4(36))); gm_parse!(gm_fg_7, "37", FgColor(AnsiColor::Bit4(37))); gm_parse!(gm_reset_39, "39", Reset(39)); gm_parse!(gm_bg_0, "40", BgColor(AnsiColor::Bit4(40))); gm_parse!(gm_bg_1, "41", BgColor(AnsiColor::Bit4(41))); gm_parse!(gm_bg_2, "42", BgColor(AnsiColor::Bit4(42))); gm_parse!(gm_bg_3, "43", BgColor(AnsiColor::Bit4(43))); gm_parse!(gm_bg_4, "44", BgColor(AnsiColor::Bit4(44))); gm_parse!(gm_bg_5, "45", BgColor(AnsiColor::Bit4(45))); gm_parse!(gm_bg_6, "46", BgColor(AnsiColor::Bit4(46))); gm_parse!(gm_bg_7, "47", BgColor(AnsiColor::Bit4(47))); gm_parse!(gm_reset_49, "49", Reset(49)); gm_parse!(gm_reset_50, "50", Reset(50)); gm_parse!(gm_framed, "51", Framed); gm_parse!(gm_encircled, "52", Encircled); gm_parse!(gm_overlined, "53", Overlined); gm_parse!(gm_reset_54, "54", Reset(54)); gm_parse!(gm_reset_55, "55", Reset(55)); gm_parse!(gm_reset_59, "59", Reset(59)); gm_parse!(gm_igrm_underline, "60", IgrmUnderline); gm_parse!(gm_igrm_underline_double, "61", IgrmDoubleUnderline); gm_parse!(gm_igrm_overline, "62", IgrmOverline); gm_parse!(gm_igrm_overline_double, "63", IgrmdDoubleOverline); gm_parse!(gm_igrm_stress_marking, "64", IgrmStressMarking); gm_parse!(gm_reset_65, "65", Reset(65)); gm_parse!(gm_superscript, "73", Superscript); gm_parse!(gm_subscript, "74", Subscript); gm_parse!(gm_reset_75, "75", Reset(75)); gm_parse!(gm_fg_b_0, "90", FgColor(AnsiColor::Bit4(90))); gm_parse!(gm_fg_b_1, "91", FgColor(AnsiColor::Bit4(91))); gm_parse!(gm_fg_b_2, "92", FgColor(AnsiColor::Bit4(92))); gm_parse!(gm_fg_b_3, "93", FgColor(AnsiColor::Bit4(93))); gm_parse!(gm_fg_b_4, "94", FgColor(AnsiColor::Bit4(94))); gm_parse!(gm_fg_b_5, "95", FgColor(AnsiColor::Bit4(95))); gm_parse!(gm_fg_b_6, "96", FgColor(AnsiColor::Bit4(96))); gm_parse!(gm_fg_b_7, "97", FgColor(AnsiColor::Bit4(97))); gm_parse!(gm_bg_b_0, "100", BgColor(AnsiColor::Bit4(100))); gm_parse!(gm_bg_b_1, "101", BgColor(AnsiColor::Bit4(101))); gm_parse!(gm_bg_b_2, "102", BgColor(AnsiColor::Bit4(102))); gm_parse!(gm_bg_b_3, "103", BgColor(AnsiColor::Bit4(103))); gm_parse!(gm_bg_b_4, "104", BgColor(AnsiColor::Bit4(104))); gm_parse!(gm_bg_b_5, "105", BgColor(AnsiColor::Bit4(105))); gm_parse!(gm_bg_b_6, "106", BgColor(AnsiColor::Bit4(106))); gm_parse!(gm_bg_b_7, "107", BgColor(AnsiColor::Bit4(107))); gm_parse_color!(gm_fg_color, "38", FgColor); gm_parse_color!(gm_bg_color, "48", BgColor); gm_parse_color!(gm_undr_color, "58", UndrColor); } ansitok-0.3.0/src/parse/sgr_parser.rs000064400000000000000000000076411046102023000157220ustar 00000000000000use super::{parsers::parse_visual_attribute, Output, VisualAttribute}; /// Creates a parser for Select Graphic Rendition(SGR) sequences. /// /// NOTE: The interace is not very stable yet. pub fn parse_ansi_sgr(text: &str) -> SGRParser<'_> { let text = text .strip_prefix("\x1b[") .and_then(|text| text.strip_suffix('m')) .unwrap_or(text); SGRParser { text: Some(text), is_start: true, } } /// A parser for SGR sequences. #[derive(Debug)] pub struct SGRParser<'a> { is_start: bool, text: Option<&'a str>, } impl<'a> Iterator for SGRParser<'a> { type Item = Output<'a, VisualAttribute>; fn next(&mut self) -> Option { let origin = self.text?; if origin.is_empty() { return None; } let mut text = origin; if !self.is_start && text.starts_with(';') && text.len() > 1 { text = &text[1..]; } if self.is_start { self.is_start = false; } let attr = parse_visual_attribute(text); match attr { Ok((rest, mode)) => { // we need to check that next chars are either separator or it's an end of a string if !rest.is_empty() && !rest.starts_with(';') { self.text = None; Some(Output::Text(origin)) } else { self.text = Some(rest); Some(Output::Escape(mode)) } } Err(_) => { self.text = None; Some(Output::Text(origin)) } } } } // A different logic. // use super::{parsers::parse_visual_attribute, Output, VisualAttribute}; // /// Creates a parser for Select Graphic Rendition(SGR) sequences. // pub fn parse_ansi_sgr(text: &str) -> SGRParser<'_> { // SGRParser { // text, // is_start: true, // next_seq: None, // } // } // /// A parser for SGR sequences. // #[derive(Debug)] // pub struct SGRParser<'a> { // is_start: bool, // text: &'a str, // next_seq: Option, // } // impl<'a> Iterator for SGRParser<'a> { // type Item = Output<'a, VisualAttribute>; // fn next(&mut self) -> Option { // if let Some(seq) = self.next_seq.take() { // return Some(Output::Escape(seq)); // } // if self.text.is_empty() { // return None; // } // let mut text = self.text; // if !self.is_start && text.starts_with(';') && text.len() > 1 { // text = &text[1..]; // } // if self.is_start { // self.is_start = false; // } // let mut unknown_length = 0; // let mut ptr = text; // loop { // let attr = parse_visual_attribute(ptr); // match attr { // Ok((rest, mode)) => { // self.text = rest; // if unknown_length != 0 { // self.next_seq = Some(mode); // let unknown_text = &text[..unknown_length]; // return Some(Output::Text(unknown_text)); // } else { // return Some(Output::Escape(mode)); // } // } // Err(_) => { // if ptr.is_empty() { // let unknown_text = &text[..unknown_length]; // self.text = ""; // return Some(Output::Text(unknown_text)); // } // // the best we can for now is to try to move text one char at a time // let next_char_pos = ptr.chars().next().unwrap().len_utf8(); // ptr = &ptr[next_char_pos..]; // unknown_length += next_char_pos; // } // } // } // } // } ansitok-0.3.0/src/parse/visual_attribute.rs000064400000000000000000000163121046102023000171340ustar 00000000000000use core::fmt; use super::parsers::parse_visual_attribute; /// An attribute of Select Graphic Rendition(SGR) #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum VisualAttribute { /// A bold. Bold, /// A faint. Faint, /// An italic. Italic, /// An underline. Underline, /// A slow blink. SlowBlink, /// A rapid blink. RapidBlink, /// Reverse video or invert. Inverse, /// Conceal or hide. Hide, /// Crossed-out, or strike. Crossedout, /// A font. /// /// A value is in range `10..=19` Font(u8), /// A fraktur (gothic) Fraktur, /// Doubly underlined; or: not bold. DoubleUnderline, /// A proportional spacing. ProportionalSpacing, /// A foreground color. FgColor(AnsiColor), /// A background color. BgColor(AnsiColor), /// An underground color. UndrColor(AnsiColor), /// A framed. Framed, /// An encircled. Encircled, /// An overlined. Overlined, /// Ideogram underline or right side line. IgrmUnderline, /// Ideogram double underline, or double line on the right side. IgrmDoubleUnderline, /// Ideogram overline or left side line. IgrmOverline, /// Ideogram double overline, or double line on the left side. IgrmdDoubleOverline, /// Ideogram stress marking. IgrmStressMarking, /// Superscript. Superscript, /// Subscript. Subscript, /// Bold. Reset(u8), } impl VisualAttribute { /// Parse a visual attribute. /// /// # Example /// /// ``` /// use ansitok::VisualAttribute; /// /// assert_eq!(VisualAttribute::parse("1"), Some(VisualAttribute::Bold)); /// ``` pub fn parse(text: S) -> Option where S: AsRef, { parse_visual_attribute(text.as_ref()) .ok() .map(|(_, attr)| attr) } } impl fmt::Display for VisualAttribute { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "\u{1b}")?; use VisualAttribute::*; match self { Bold => "1".fmt(f)?, Faint => "2".fmt(f)?, Italic => "3".fmt(f)?, Underline => "4".fmt(f)?, SlowBlink => "5".fmt(f)?, RapidBlink => "6".fmt(f)?, Inverse => "7".fmt(f)?, Hide => "8".fmt(f)?, Crossedout => "9".fmt(f)?, Font(n) => n.fmt(f)?, Fraktur => "20".fmt(f)?, DoubleUnderline => "21".fmt(f)?, ProportionalSpacing => "26".fmt(f)?, Framed => "51".fmt(f)?, Encircled => "52".fmt(f)?, Overlined => "53".fmt(f)?, IgrmUnderline => "60".fmt(f)?, IgrmDoubleUnderline => "61".fmt(f)?, IgrmOverline => "62".fmt(f)?, IgrmdDoubleOverline => "63".fmt(f)?, IgrmStressMarking => "64".fmt(f)?, Superscript => "73".fmt(f)?, Subscript => "74".fmt(f)?, Reset(n) => n.fmt(f)?, FgColor(color) => write_color(f, color, "38")?, BgColor(color) => write_color(f, color, "48")?, UndrColor(color) => write_color(f, color, "58")?, }; write!(f, "m")?; Ok(()) } } /// A Color representation in ANSI sequences. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum AnsiColor { /// A color from [VisualAttribute]. /// /// An example: `ESC[39;49m`. Bit4(u8), /// An index color. /// /// An example: `ESC[38:5:⟨n⟩m`. Bit8(u8), /// A 3 digit color. /// /// An example: `ESC[48;2;⟨r⟩;⟨g⟩;⟨b⟩m`. Bit24 { /// Red. r: u8, /// Green. g: u8, /// Blue. b: u8, }, } fn write_color(f: &mut fmt::Formatter, color: &AnsiColor, prefix: &str) -> fmt::Result { match color { AnsiColor::Bit4(b) => write!(f, "{}", b), AnsiColor::Bit8(b) => write!(f, "{};5;{}", prefix, b), AnsiColor::Bit24 { r, g, b } => write!(f, "{};2;{};{};{}", prefix, r, g, b), } } #[cfg(test)] mod tests { use super::*; #[test] fn test_vis_attr_display() { use VisualAttribute::*; macro_rules! assert_vis_attr { ($val:expr, $expected:expr) => { assert_eq!(write_to_string($val), $expected); }; } assert_vis_attr!(Bold, "\u{1b}1m"); assert_vis_attr!(Faint, "\u{1b}2m"); assert_vis_attr!(Italic, "\u{1b}3m"); assert_vis_attr!(Underline, "\u{1b}4m"); assert_vis_attr!(SlowBlink, "\u{1b}5m"); assert_vis_attr!(RapidBlink, "\u{1b}6m"); assert_vis_attr!(Inverse, "\u{1b}7m"); assert_vis_attr!(Hide, "\u{1b}8m"); assert_vis_attr!(Crossedout, "\u{1b}9m"); assert_vis_attr!(Fraktur, "\u{1b}20m"); assert_vis_attr!(DoubleUnderline, "\u{1b}21m"); assert_vis_attr!(ProportionalSpacing, "\u{1b}26m"); assert_vis_attr!(Framed, "\u{1b}51m"); assert_vis_attr!(Encircled, "\u{1b}52m"); assert_vis_attr!(Overlined, "\u{1b}53m"); assert_vis_attr!(IgrmUnderline, "\u{1b}60m"); assert_vis_attr!(IgrmDoubleUnderline, "\u{1b}61m"); assert_vis_attr!(IgrmOverline, "\u{1b}62m"); assert_vis_attr!(IgrmdDoubleOverline, "\u{1b}63m"); assert_vis_attr!(IgrmStressMarking, "\u{1b}64m"); assert_vis_attr!(Superscript, "\u{1b}73m"); assert_vis_attr!(Subscript, "\u{1b}74m"); macro_rules! assert_list { ($val:expr) => { for i in 0..u8::MAX { assert_eq!(write_to_string($val(i)), format!("\u{1b}{}m", i)); } }; } assert_list!(Font); assert_list!(Reset); } #[ignore = "It's a slow function so run only when needed"] #[test] fn test_vis_attr_color_display() { use VisualAttribute::*; macro_rules! assert_color { ($val:expr, $prefix:expr) => { for i in 0..u8::MAX { let val = $val(AnsiColor::Bit4(i)); let got = write_to_string(val); assert_eq!(got, format!("\u{1b}{}m", i)); } for i in 0..u8::MAX { let val = $val(AnsiColor::Bit8(i)); let got = write_to_string(val); assert_eq!(got, format!("\u{1b}{};5;{}m", $prefix, i)); } for r in 0..u8::MAX { for g in 0..u8::MAX { for b in 0..u8::MAX { let val = $val(AnsiColor::Bit24 { r, g, b }); let got = write_to_string(val); assert_eq!(got, format!("\u{1b}{};2;{};{};{}m", $prefix, r, g, b)); } } } }; } assert_color!(FgColor, "38"); assert_color!(BgColor, "48"); assert_color!(UndrColor, "58"); } fn write_to_string(d: D) -> String where D: core::fmt::Display, { use core::fmt::Write; let mut buf = String::new(); write!(&mut buf, "{}", d).expect("failed to write"); buf } } ansitok-0.3.0/tests/ansi_sequence.rs000064400000000000000000000152601046102023000156320ustar 00000000000000use ansitok::{parse_ansi, EscapeCode}; use EscapeCode::*; macro_rules! test_parse_ansi { ($name:ident, $string:expr, $expected:expr) => { #[test] fn $name() { println!("{:?}", parse_ansi($string).collect::>()); let sequences: Vec<_> = parse_ansi($string) .flat_map(|e| EscapeCode::parse(&$string[e.start()..e.end()])) .collect(); assert_eq!(sequences, $expected); } }; } test_parse_ansi!(empty, "", []); test_parse_ansi!( parse_escape, "\x1b\x1b\x1b\x1b\x1b", [Escape, Escape, Escape, Escape, Escape] ); test_parse_ansi!(cur_pos_1, "\x1b[32;102H", [CursorPos(32, 102)]); test_parse_ansi!(cur_pos_2, "\x1b[32;102f", [CursorPos(32, 102)]); test_parse_ansi!(cur_pos_3, "\x1b[32;102;H", []); test_parse_ansi!(cur_pos_4, "\x1b[32;102;f", []); test_parse_ansi!(cur_pos_5, "\x1b[467434;3332H", [CursorPos(467434, 3332)]); test_parse_ansi!(cur_pos_6, "\x1b[467434;3332f", [CursorPos(467434, 3332)]); test_parse_ansi!(cur_pos_7, "\x1b[23;f", [CursorPos(23, 1)]); test_parse_ansi!(cur_pos_8, "\x1b[;23f", [CursorPos(1, 23)]); test_parse_ansi!(cur_pos_empty_1, "\x1b[f", [CursorPos(1, 1)]); test_parse_ansi!(cur_pos_empty_2, "\x1b[H", [CursorPos(1, 1)]); test_parse_ansi!(cur_pos_up, "\x1b[100A", [CursorUp(100)]); test_parse_ansi!(cur_pos_up_big, "\x1b[123213A", [CursorUp(123213)]); test_parse_ansi!(cur_pos_up_empty, "\x1b[A", [CursorUp(1)]); test_parse_ansi!(cur_pos_down, "\x1b[100B", [CursorDown(100)]); test_parse_ansi!(cur_pos_down_big, "\x1b[123213B", [CursorDown(123213)]); test_parse_ansi!(cur_pos_down_empty, "\x1b[B", [CursorDown(1)]); test_parse_ansi!(cur_pos_forward, "\x1b[100C", [CursorForward(100)]); test_parse_ansi!(cur_pos_forward_1, "\x1b[123213C", [CursorForward(123213)]); test_parse_ansi!(cur_pos_forward_empty, "\x1b[C", [CursorForward(1)]); test_parse_ansi!(cur_pos_backward, "\x1b[100D", [CursorBackward(100)]); test_parse_ansi!(cur_pos_backward_1, "\x1b[123213D", [CursorBackward(123213)]); test_parse_ansi!(cur_pos_backward_empty, "\x1b[D", [CursorBackward(1)]); test_parse_ansi!(set_mode, "\x1b[=23h", [SetMode(23)]); test_parse_ansi!(set_mode_1, "\x1b[=h", []); test_parse_ansi!(set_mode_2, "\x1b[=512h", []); test_parse_ansi!(reset_mode, "\x1b[=23l", [ResetMode(23)]); test_parse_ansi!(reset_mode_1, "\x1b[=l", []); test_parse_ansi!(reset_mode_2, "\x1b[=512l", []); test_parse_ansi!(set_top_bot, "\x1b[1;43r", [SetTopAndBottom(1, 43)]); test_parse_ansi!(set_top_bot_1, "\x1b[;43r", [SetTopAndBottom(1, 43)]); test_parse_ansi!(set_top_bot_2, "\x1b[1;43r", [SetTopAndBottom(1, 43)]); test_parse_ansi!(set_top_bot_3, "\x1b[1;r", [SetTopAndBottom(1, 1)]); test_parse_ansi!(set_top_bot_4, "\x1b[;1r", [SetTopAndBottom(1, 1)]); test_parse_ansi!(set_top_bot_5, "\x1b[;r", [SetTopAndBottom(1, 1)]); test_parse_ansi!(set_top_bot_6, "\x1b[500;500r", [SetTopAndBottom(500, 500)]); test_parse_ansi!(cur_save, "\x1b[s", [CursorSave]); test_parse_ansi!(cur_res, "\x1b[u", [CursorRestore]); test_parse_ansi!(erase_dis, "\x1b[2J", [EraseDisplay]); test_parse_ansi!(erase_line, "\x1b[K", [EraseLine]); test_parse_ansi!(cur_hide, "\x1b[?25l", [HideCursor]); test_parse_ansi!(cur_show, "\x1b[?25h", [ShowCursor]); test_parse_ansi!(cur_to_app, "\x1b[?1h", [CursorToApp]); test_parse_ansi!(set_n_line_mode, "\x1b[20h", [SetNewLineMode]); test_parse_ansi!(set_col132, "\x1b[?3h", [SetCol132]); test_parse_ansi!(set_smoot_scroll, "\x1b[?4h", [SetSmoothScroll]); test_parse_ansi!(set_reverse_video, "\x1b[?5h", [SetReverseVideo]); test_parse_ansi!(set_origin_relative, "\x1b[?6h", [SetOriginRelative]); test_parse_ansi!(set_auto_wrap, "\x1b[?7h", [SetAutoWrap]); test_parse_ansi!(set_auto_repeat, "\x1b[?8h", [SetAutoRepeat]); test_parse_ansi!(set_interlacing, "\x1b[?9h", [SetInterlacing]); test_parse_ansi!(set_line_feed_mode, "\x1b[20l", [SetLineFeedMode]); test_parse_ansi!(set_cur_key_cur, "\x1b[?1l", [SetCursorKeyToCursor]); test_parse_ansi!(set_vt52, "\x1b[?2l", [SetVT52]); test_parse_ansi!(set_col80, "\x1b[?3l", [SetCol80]); test_parse_ansi!(set_jump_scroll, "\x1b[?4l", [SetJumpScrolling]); test_parse_ansi!(set_norm_video, "\x1b[?5l", [SetNormalVideo]); test_parse_ansi!(set_origin_abs, "\x1b[?6l", [SetOriginAbsolute]); test_parse_ansi!(reset_autowrap, "\x1b[?7l", [ResetAutoWrap]); test_parse_ansi!(reset_autorepeat, "\x1b[?8l", [ResetAutoRepeat]); test_parse_ansi!(reset_interlacing, "\x1b[?9l", [ResetInterlacing]); test_parse_ansi!(set_alt_keypad, "\x1b=", [SetAlternateKeypad]); test_parse_ansi!(set_num_keypad, "\x1b>", [SetNumericKeypad]); test_parse_ansi!(set_ukg0, "\x1b(A", [SetUKG0]); test_parse_ansi!(set_ukg1, "\x1b)A", [SetUKG1]); test_parse_ansi!(set_usg0, "\x1b(B", [SetUSG0]); test_parse_ansi!(set_usg1, "\x1b)B", [SetUSG1]); test_parse_ansi!(set_g0_spec_chars, "\x1b(0", [SetG0SpecialChars]); test_parse_ansi!(set_g1_spec_chars, "\x1b)0", [SetG1SpecialChars]); test_parse_ansi!(set_g0_alt_chars, "\x1b(1", [SetG0AlternateChar]); test_parse_ansi!(set_g1_alt_chars, "\x1b)1", [SetG1AlternateChar]); test_parse_ansi!(set_g0_spec_alt_chars, "\x1b(2", [SetG0AltAndSpecialGraph]); test_parse_ansi!(set_g1_spec_alt_chars, "\x1b)2", [SetG1AltAndSpecialGraph]); test_parse_ansi!(set_single_shft2, "\x1bN", [SetSingleShift2]); test_parse_ansi!(set_single_shft3, "\x1bO", [SetSingleShift3]); test_parse_ansi!( parse_0, "\x1b[=25l\x1b[=7l\x1b[0m\x1b[36m\x1b[1m-`", [ ResetMode(25), ResetMode(7), SelectGraphicRendition("0"), SelectGraphicRendition("36"), SelectGraphicRendition("1"), ] ); test_parse_ansi!( parse_1, "\x1b[=25l\x1b[=7l\x1b[0m\x1b[36;1;15;2m\x1b[1m-`", [ ResetMode(25), ResetMode(7), SelectGraphicRendition("0"), SelectGraphicRendition("36;1;15;2"), SelectGraphicRendition("1"), ] ); test_parse_ansi!( parse_2, "\x1b[=25l\x1b[=7l\x1b[0m\x1b[36;1;15;2m\x1b[1m-`", [ ResetMode(25), ResetMode(7), SelectGraphicRendition("0"), SelectGraphicRendition("36;1;15;2"), SelectGraphicRendition("1"), ] ); test_parse_ansi!( parse_3, "\x1b[=25l\x1b[=7l\x1b[0m\x1b[36;1;15;2;36;1;15;2m\x1b[1m-`", [ ResetMode(25), ResetMode(7), SelectGraphicRendition("0"), SelectGraphicRendition("36;1;15;2;36;1;15;2"), SelectGraphicRendition("1"), ] ); test_parse_ansi!( parse_4, "\x1b[H\x1b[123456H\x1b[;123456H\x1b[7asd;1234H\x1b[a;sd7H", [CursorPos(1, 1), CursorPos(123456, 1), CursorPos(1, 123456),] ); test_parse_ansi!( parse_5, "\x1b\x1b[33mFoobar", [Escape, SelectGraphicRendition("33")] ); test_parse_ansi!( parse_6, "\x1b[38;5;45mFoobar\x1b[0m", [ SelectGraphicRendition("38;5;45"), SelectGraphicRendition("0") ] ); ansitok-0.3.0/tests/parse_ansi.rs000064400000000000000000000152511046102023000151340ustar 00000000000000use ansitok::{parse_ansi, Element}; macro_rules! test_parse_ansi { ($name:ident, $string:expr, $expected:expr) => { #[test] fn $name() { let sequences: Vec<_> = parse_ansi($string).collect(); assert_eq!(sequences, $expected); } }; } fn csi(start: usize, end: usize) -> Element { Element::csi(start, end) } fn esc(start: usize, end: usize) -> Element { Element::esc(start, end) } fn sgr(start: usize, end: usize) -> Element { Element::sgr(start, end) } fn text(start: usize, end: usize) -> Element { Element::text(start, end) } test_parse_ansi!(empty, "", []); test_parse_ansi!( parse_escape, "\x1b\x1b\x1b\x1b\x1b", [esc(0, 1), esc(1, 2), esc(2, 3), esc(3, 4), esc(4, 5)] ); test_parse_ansi!(cur_pos_1, "\x1b[32;102H", [csi(0, 9)]); test_parse_ansi!(cur_pos_2, "\x1b[32;102f", [csi(0, 9)]); test_parse_ansi!(cur_pos_3, "\x1b[32;102;H", [csi(0, 10)]); test_parse_ansi!(cur_pos_4, "\x1b[32;102;f", [csi(0, 10)]); test_parse_ansi!(cur_pos_5, "\x1b[467434;3332H", [csi(0, 14)]); test_parse_ansi!(cur_pos_6, "\x1b[467434;3332f", [csi(0, 14)]); test_parse_ansi!(cur_pos_7, "\x1b[23;f", [csi(0, 6)]); test_parse_ansi!(cur_pos_8, "\x1b[;23f", [csi(0, 6)]); test_parse_ansi!(cur_pos_empty_1, "\x1b[f", [csi(0, 3)]); test_parse_ansi!(cur_pos_empty_2, "\x1b[H", [csi(0, 3)]); test_parse_ansi!(cur_pos_up, "\x1b[100A", [csi(0, 6)]); test_parse_ansi!(cur_pos_up_big, "\x1b[123213A", [csi(0, 9)]); test_parse_ansi!(cur_pos_up_empty, "\x1b[A", [csi(0, 3)]); test_parse_ansi!(cur_pos_down, "\x1b[100B", [csi(0, 6)]); test_parse_ansi!(cur_pos_down_big, "\x1b[123213B", [csi(0, 9)]); test_parse_ansi!(cur_pos_down_empty, "\x1b[B", [csi(0, 3)]); test_parse_ansi!(cur_pos_forward, "\x1b[100C", [csi(0, 6)]); test_parse_ansi!(cur_pos_forward_1, "\x1b[123213C", [csi(0, 9)]); test_parse_ansi!(cur_pos_forward_empty, "\x1b[C", [csi(0, 3)]); test_parse_ansi!(cur_pos_backward, "\x1b[100D", [csi(0, 6)]); test_parse_ansi!(cur_pos_backward_1, "\x1b[123213D", [csi(0, 9)]); test_parse_ansi!(cur_pos_backward_empty, "\x1b[D", [csi(0, 3)]); test_parse_ansi!(set_mode, "\x1b[=23h", [csi(0, 6)]); test_parse_ansi!(set_mode_1, "\x1b[=h", [csi(0, 4)]); test_parse_ansi!(set_mode_2, "\x1b[=512h", [csi(0, 7)]); test_parse_ansi!(reset_mode, "\x1b[=23l", [csi(0, 6)]); test_parse_ansi!(reset_mode_1, "\x1b[=l", [csi(0, 4)]); test_parse_ansi!(reset_mode_2, "\x1b[=512l", [csi(0, 7)]); test_parse_ansi!(set_top_bot, "\x1b[1;43r", [csi(0, 7)]); test_parse_ansi!(set_top_bot_1, "\x1b[;43r", [csi(0, 6)]); test_parse_ansi!(set_top_bot_2, "\x1b[1;43r", [csi(0, 7)]); test_parse_ansi!(set_top_bot_3, "\x1b[1;r", [csi(0, 5)]); test_parse_ansi!(set_top_bot_4, "\x1b[;1r", [csi(0, 5)]); test_parse_ansi!(set_top_bot_5, "\x1b[;r", [csi(0, 4)]); test_parse_ansi!(set_top_bot_6, "\x1b[500;500r", [csi(0, 10)]); test_parse_ansi!(cur_save, "\x1b[s", [csi(0, 3)]); test_parse_ansi!(cur_res, "\x1b[u", [csi(0, 3)]); test_parse_ansi!(erase_dis, "\x1b[2J", [csi(0, 4)]); test_parse_ansi!(erase_line, "\x1b[K", [csi(0, 3)]); test_parse_ansi!(cur_hide, "\x1b[?25l", [csi(0, 6)]); test_parse_ansi!(cur_show, "\x1b[?25h", [csi(0, 6)]); test_parse_ansi!(cur_to_app, "\x1b[?1h", [csi(0, 5)]); test_parse_ansi!(set_n_line_mode, "\x1b[20h", [csi(0, 5)]); test_parse_ansi!(set_col132, "\x1b[?3h", [csi(0, 5)]); test_parse_ansi!(set_smoot_scroll, "\x1b[?4h", [csi(0, 5)]); test_parse_ansi!(set_reverse_video, "\x1b[?5h", [csi(0, 5)]); test_parse_ansi!(set_origin_relative, "\x1b[?6h", [csi(0, 5)]); test_parse_ansi!(set_auto_wrap, "\x1b[?7h", [csi(0, 5)]); test_parse_ansi!(set_auto_repeat, "\x1b[?8h", [csi(0, 5)]); test_parse_ansi!(set_interlacing, "\x1b[?9h", [csi(0, 5)]); test_parse_ansi!(set_line_feed_mode, "\x1b[20l", [csi(0, 5)]); test_parse_ansi!(set_cur_key_cur, "\x1b[?1l", [csi(0, 5)]); test_parse_ansi!(set_vt52, "\x1b[?2l", [csi(0, 5)]); test_parse_ansi!(set_col80, "\x1b[?3l", [csi(0, 5)]); test_parse_ansi!(set_jump_scroll, "\x1b[?4l", [csi(0, 5)]); test_parse_ansi!(set_norm_video, "\x1b[?5l", [csi(0, 5)]); test_parse_ansi!(set_origin_abs, "\x1b[?6l", [csi(0, 5)]); test_parse_ansi!(reset_autowrap, "\x1b[?7l", [csi(0, 5)]); test_parse_ansi!(reset_autorepeat, "\x1b[?8l", [csi(0, 5)]); test_parse_ansi!(reset_interlacing, "\x1b[?9l", [csi(0, 5)]); test_parse_ansi!(set_alt_keypad, "\x1b=", [esc(0, 2)]); test_parse_ansi!(set_num_keypad, "\x1b>", [esc(0, 2)]); test_parse_ansi!(set_ukg0, "\x1b(A", [esc(0, 3)]); test_parse_ansi!(set_ukg1, "\x1b)A", [esc(0, 3)]); test_parse_ansi!(set_usg0, "\x1b(B", [esc(0, 3)]); test_parse_ansi!(set_usg1, "\x1b)B", [esc(0, 3)]); test_parse_ansi!(set_g0_spec_chars, "\x1b(0", [esc(0, 3)]); test_parse_ansi!(set_g1_spec_chars, "\x1b)0", [esc(0, 3)]); test_parse_ansi!(set_g0_alt_chars, "\x1b(1", [esc(0, 3)]); test_parse_ansi!(set_g1_alt_chars, "\x1b)1", [esc(0, 3)]); test_parse_ansi!(set_g0_spec_alt_chars, "\x1b(2", [esc(0, 3)]); test_parse_ansi!(set_g1_spec_alt_chars, "\x1b)2", [esc(0, 3)]); test_parse_ansi!(set_single_shft2, "\x1bN", [esc(0, 2)]); test_parse_ansi!(set_single_shft3, "\x1bO", [esc(0, 2)]); test_parse_ansi!( parse_0, "\x1b[=25l\x1b[=7l\x1b[0m\x1b[36m\x1b[1m-`", [ csi(0, 6), csi(6, 11), sgr(11, 15), sgr(15, 20), sgr(20, 24), text(24, 26) ] ); test_parse_ansi!( parse_1, "\x1b[=25l\x1b[=7l\x1b[0m\x1b[36;1;15;2m\x1b[1m-`", [ csi(0, 6), csi(6, 11), sgr(11, 15), sgr(15, 27), sgr(27, 31), text(31, 33) ] ); test_parse_ansi!( parse_2, "\x1b[=25l\x1b[=7l\x1b[0m\x1b[36;1;15;2;36;1;15;2m\x1b[1m-`", [ csi(0, 6), csi(6, 11), sgr(11, 15), sgr(15, 37), sgr(37, 41), text(41, 43) ] ); test_parse_ansi!( parse_4, "\x1b[H\x1b[123456H\x1b[;123456H\x1b[7asd;1234H\x1b[a;sd7H", [ csi(0, 3), csi(3, 12), csi(12, 22), csi(22, 26), text(26, 34), csi(34, 37), text(37, 42), ] ); test_parse_ansi!( parse_5, "\x1b\x1b[33mFoobar", [esc(0, 1), sgr(1, 6), text(6, 12),] ); test_parse_ansi!( parse_6, "\x1b[38;5;45mFoobar\x1b[0m", [sgr(0, 10), text(10, 16), sgr(16, 20)] ); test_parse_ansi!(parse_issue_0, "│\u{1b}[0m", [text(0, 3), sgr(3, 7)]); test_parse_ansi!( parse_issue_1, "│\u{1b}\u{1b}[0m", [text(0, 3), esc(3, 4), sgr(4, 8)] ); test_parse_ansi!( parse_issue_3, "││││││││\u{1b}[0m", [text(0, 24), sgr(24, 28)] ); test_parse_ansi!(parse_issue_4, "\u{1b}││││││││", [esc(0, 1), text(1, 25)]); test_parse_ansi!(parse_issue_5, "││││││││\u{1b}", [text(0, 24), esc(24, 25)]); test_parse_ansi!( parse_issue_6, "\u{1b}[37m│\u{1b}", [sgr(0, 5), text(5, 8), esc(8, 9)] ); ansitok-0.3.0/tests/parse_sgr.rs000064400000000000000000000100551046102023000147720ustar 00000000000000use ansitok::{ parse_ansi_sgr, AnsiColor::*, Output, Output::Escape as esc, VisualAttribute::{self, *}, }; macro_rules! test_parse_sgr { ($name:ident, $string:expr, $expected:expr) => { #[test] fn $name() { let sequences: Vec<_> = parse_ansi_sgr($string).collect(); assert_eq!(sequences, $expected); } }; } test_parse_sgr!(parse_empty, "", []); // is this correct? or we shall consider it a RESET 0? test_parse_sgr!( parse_valid_ansi_sequence, "\x1b[38;5;45mFoobar\x1b[0m", [text("38;5;45mFoobar\x1b[0"),] ); test_parse_sgr!( parse_invalid_ansi_sequence, "\x1b[38;5;45Foobar\x1b[0", [text("\x1b[38;5;45Foobar\x1b[0"),] ); test_parse_sgr!( parse_invalid_ansi_sequence2, "38;5;45\x1b[0", [text("38;5;45\x1b[0"),] ); test_parse_sgr!(parse_1, "38;5;45", [esc(FgColor(Bit8(45)))]); test_parse_sgr!(parse_2, "5;45", [esc(SlowBlink), esc(BgColor(Bit4(45)))]); test_parse_sgr!( parse_3, "48;2;127;0;255", [esc(BgColor(Bit24 { r: 127, g: 0, b: 255 }))] ); test_parse_sgr!( parse_4, "2;48;2;127;0;255", [ esc(Faint), esc(BgColor(Bit24 { r: 127, g: 0, b: 255 })) ] ); test_parse_sgr!( parse_5, "1;2;3;38;2;255;255;0;0", [ esc(Bold), esc(Faint), esc(Italic), esc(FgColor(Bit24 { r: 255, g: 255, b: 0, })), esc(Reset(0)), ] ); test_parse_sgr!(parse_fg_8bit, "38;5;128", [esc(FgColor(Bit8(128)))]); test_parse_sgr!( parse_fg_24bit, "38;2;1;2;3", [esc(FgColor(Bit24 { r: 1, g: 2, b: 3 }))] ); test_parse_sgr!( test_some_sequence_1, "3;4;48;2;4;5;6;38;2;1;2;3", [ esc(Italic), esc(Underline), esc(BgColor(Bit24 { r: 4, g: 5, b: 6 })), esc(FgColor(Bit24 { r: 1, g: 2, b: 3 })) ] ); test_parse_sgr!( test_some_sequence_2, "3;4;44;31", [ esc(Italic), esc(Underline), esc(BgColor(Bit4(44))), esc(FgColor(Bit4(31))) ] ); #[test] fn test_parsing_single_byte() { for i in 38..39 { let expected = expect_byte(i); let s = i.to_string(); let got: Vec<_> = parse_ansi_sgr(&s).collect(); assert_eq!(got, [expected]); } } fn expect_byte(b: u8) -> Output<'static, VisualAttribute> { match b { 0 => esc(Reset(0)), 1 => esc(Bold), 2 => esc(Faint), 3 => esc(Italic), 4 => esc(Underline), 5 => esc(SlowBlink), 6 => esc(RapidBlink), 7 => esc(Inverse), 8 => esc(Hide), 9 => esc(Crossedout), 10 => esc(Reset(10)), n @ 11..=19 => esc(Font(n)), 20 => esc(Fraktur), 21 => esc(DoubleUnderline), n @ 22..=25 => esc(Reset(n)), 26 => esc(ProportionalSpacing), n @ 27..=29 => esc(Reset(n)), n @ 30..=37 => esc(FgColor(Bit4(n))), 38 => text("38"), 39 => esc(Reset(39)), n @ 40..=47 => esc(BgColor(Bit4(n))), 48 => text("48"), 49 => esc(Reset(49)), 50 => esc(Reset(50)), 51 => esc(Framed), 52 => esc(Encircled), 53 => esc(Overlined), n @ 54..=55 => esc(Reset(n)), 58 => text("58"), 59 => esc(Reset(59)), 60 => esc(IgrmUnderline), 61 => esc(IgrmDoubleUnderline), 62 => esc(IgrmOverline), 63 => esc(IgrmdDoubleOverline), 64 => esc(IgrmStressMarking), 65 => esc(Reset(65)), 73 => esc(Superscript), 74 => esc(Subscript), 75 => esc(Reset(75)), n @ 90..=97 => esc(FgColor(Bit4(n))), n @ 100..=107 => esc(BgColor(Bit4(n))), n => text(n.to_string()), } } fn text<'a, T>(s: impl Into>) -> Output<'a, T> { match s.into() { std::borrow::Cow::Owned(s) => { let s = s.into_boxed_str(); let s = Box::leak(s); Output::Text(s) } std::borrow::Cow::Borrowed(s) => Output::Text(s), } }