Remove code that tries to handle ANSI escape inputs

Syntax highlighting is broken when input contains ANSI escape characters
anyway, so there is not much point in trying to handle ANSI escapes in
input.
This commit is contained in:
Martin Nordholts 2022-05-10 08:34:13 +02:00
parent a6297b282c
commit 8174e02279
6 changed files with 114 additions and 390 deletions

View File

@ -14,6 +14,7 @@
- Relaxed glibc requirements on amd64, see #2106 and #2194 (@sharkdp) - Relaxed glibc requirements on amd64, see #2106 and #2194 (@sharkdp)
- Improved fish completions. See #2275 (@zgracem) - Improved fish completions. See #2275 (@zgracem)
- Stop pre-processing ANSI escape characters. Syntax highlighting on ANSI escaped input is not supported. See #2185 and #2189 (@Enselic)
## Syntaxes ## Syntaxes

View File

@ -46,7 +46,6 @@ pub(crate) mod printer;
pub mod style; pub mod style;
pub(crate) mod syntax_mapping; pub(crate) mod syntax_mapping;
mod terminal; mod terminal;
mod vscreen;
pub(crate) mod wrapping; pub(crate) mod wrapping;
pub use pretty_printer::{Input, PrettyPrinter}; pub use pretty_printer::{Input, PrettyPrinter};

View File

@ -1,13 +1,7 @@
use console::AnsiCodeIterator;
/// Expand tabs like an ANSI-enabled expand(1). /// Expand tabs like an ANSI-enabled expand(1).
pub fn expand_tabs(line: &str, width: usize, cursor: &mut usize) -> String { pub fn expand_tabs(mut text: &str, width: usize, cursor: &mut usize) -> String {
let mut buffer = String::with_capacity(line.len() * 2); let mut buffer = String::with_capacity(text.len() * 2);
for chunk in AnsiCodeIterator::new(line) {
match chunk {
(text, true) => buffer.push_str(text),
(mut text, false) => {
while let Some(index) = text.find('\t') { while let Some(index) = text.find('\t') {
// Add previous text. // Add previous text.
if index > 0 { if index > 0 {
@ -26,9 +20,6 @@ pub fn expand_tabs(line: &str, width: usize, cursor: &mut usize) -> String {
*cursor += text.len(); *cursor += text.len();
buffer.push_str(text); buffer.push_str(text);
}
}
}
buffer buffer
} }

View File

@ -6,8 +6,6 @@ use ansi_term::Style;
use bytesize::ByteSize; use bytesize::ByteSize;
use console::AnsiCodeIterator;
use syntect::easy::HighlightLines; use syntect::easy::HighlightLines;
use syntect::highlighting::Color; use syntect::highlighting::Color;
use syntect::highlighting::Theme; use syntect::highlighting::Theme;
@ -33,7 +31,6 @@ use crate::line_range::RangeCheckResult;
use crate::preprocessor::{expand_tabs, replace_nonprintable}; use crate::preprocessor::{expand_tabs, replace_nonprintable};
use crate::style::StyleComponent; use crate::style::StyleComponent;
use crate::terminal::{as_terminal_escaped, to_ansi_color}; use crate::terminal::{as_terminal_escaped, to_ansi_color};
use crate::vscreen::AnsiStyle;
use crate::wrapping::WrappingMode; use crate::wrapping::WrappingMode;
pub(crate) trait Printer { pub(crate) trait Printer {
@ -122,7 +119,6 @@ pub(crate) struct InteractivePrinter<'a> {
config: &'a Config<'a>, config: &'a Config<'a>,
decorations: Vec<Box<dyn Decoration>>, decorations: Vec<Box<dyn Decoration>>,
panel_width: usize, panel_width: usize,
ansi_style: AnsiStyle,
content_type: Option<ContentType>, content_type: Option<ContentType>,
#[cfg(feature = "git")] #[cfg(feature = "git")]
pub line_changes: &'a Option<LineChanges>, pub line_changes: &'a Option<LineChanges>,
@ -206,7 +202,6 @@ impl<'a> InteractivePrinter<'a> {
config, config,
decorations, decorations,
content_type: input.reader.content_type, content_type: input.reader.content_type,
ansi_style: AnsiStyle::new(),
#[cfg(feature = "git")] #[cfg(feature = "git")]
line_changes, line_changes,
highlighter_from_set, highlighter_from_set,
@ -476,7 +471,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
self.config.highlighted_lines.0.check(line_number) == RangeCheckResult::InRange; self.config.highlighted_lines.0.check(line_number) == RangeCheckResult::InRange;
if highlight_this_line && self.config.theme == "ansi" { if highlight_this_line && self.config.theme == "ansi" {
self.ansi_style.update("^[4m"); write!(handle, "\x1B[4m")?;
} }
let background_color = self let background_color = self
@ -503,18 +498,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
let italics = self.config.use_italic_text; let italics = self.config.use_italic_text;
for &(style, region) in &regions { for &(style, region) in &regions {
let ansi_iterator = AnsiCodeIterator::new(region); let text = &*self.preprocess(region, &mut cursor_total);
for chunk in ansi_iterator {
match chunk {
// ANSI escape passthrough.
(ansi, true) => {
self.ansi_style.update(ansi);
write!(handle, "{}", ansi)?;
}
// Regular text.
(text, false) => {
let text = &*self.preprocess(text, &mut cursor_total);
let text_trimmed = text.trim_end_matches(|c| c == '\r' || c == '\n'); let text_trimmed = text.trim_end_matches(|c| c == '\r' || c == '\n');
write!( write!(
@ -522,7 +506,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
"{}", "{}",
as_terminal_escaped( as_terminal_escaped(
style, style,
&format!("{}{}", self.ansi_style, text_trimmed), text_trimmed,
true_color, true_color,
colored_output, colored_output,
italics, italics,
@ -547,28 +531,14 @@ impl<'a> Printer for InteractivePrinter<'a> {
write!(handle, "{}", &text[text_trimmed.len()..])?; write!(handle, "{}", &text[text_trimmed.len()..])?;
} }
} }
}
}
}
if !self.config.style_components.plain() && line.bytes().next_back() != Some(b'\n') { if !self.config.style_components.plain() && line.bytes().next_back() != Some(b'\n') {
writeln!(handle)?; writeln!(handle)?;
} }
} else { } else {
for &(style, region) in &regions { for &(style, region) in &regions {
let ansi_iterator = AnsiCodeIterator::new(region);
for chunk in ansi_iterator {
match chunk {
// ANSI escape passthrough.
(ansi, true) => {
self.ansi_style.update(ansi);
write!(handle, "{}", ansi)?;
}
// Regular text.
(text, false) => {
let text = self.preprocess( let text = self.preprocess(
text.trim_end_matches(|c| c == '\r' || c == '\n'), region.trim_end_matches(|c| c == '\r' || c == '\n'),
&mut cursor_total, &mut cursor_total,
); );
@ -595,9 +565,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
"{} ", "{} ",
self.decorations self.decorations
.iter() .iter()
.map(|d| d .map(|d| d.generate(line_number, true, self).text)
.generate(line_number, true, self)
.text)
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(" ") .join(" ")
)) ))
@ -612,7 +580,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
"{}\n{}", "{}\n{}",
as_terminal_escaped( as_terminal_escaped(
style, style,
&*format!("{}{}", self.ansi_style, line_buf), &line_buf,
self.config.true_color, self.config.true_color,
self.config.colored_output, self.config.colored_output,
self.config.use_italic_text, self.config.use_italic_text,
@ -638,7 +606,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
"{}", "{}",
as_terminal_escaped( as_terminal_escaped(
style, style,
&*format!("{}{}", self.ansi_style, line_buf), &line_buf,
self.config.true_color, self.config.true_color,
self.config.colored_output, self.config.colored_output,
self.config.use_italic_text, self.config.use_italic_text,
@ -646,9 +614,6 @@ impl<'a> Printer for InteractivePrinter<'a> {
) )
)?; )?;
} }
}
}
}
if let Some(background_color) = background_color { if let Some(background_color) = background_color {
let ansi_style = Style { let ansi_style = Style {
@ -666,7 +631,6 @@ impl<'a> Printer for InteractivePrinter<'a> {
} }
if highlight_this_line && self.config.theme == "ansi" { if highlight_this_line && self.config.theme == "ansi" {
self.ansi_style.update("^[24m");
write!(handle, "\x1B[24m")?; write!(handle, "\x1B[24m")?;
} }

View File

@ -1,212 +0,0 @@
use std::fmt::{Display, Formatter};
// Wrapper to avoid unnecessary branching when input doesn't have ANSI escape sequences.
pub struct AnsiStyle {
attributes: Option<Attributes>,
}
impl AnsiStyle {
pub fn new() -> Self {
AnsiStyle { attributes: None }
}
pub fn update(&mut self, sequence: &str) -> bool {
match &mut self.attributes {
Some(a) => a.update(sequence),
None => {
self.attributes = Some(Attributes::new());
self.attributes.as_mut().unwrap().update(sequence)
}
}
}
}
impl Display for AnsiStyle {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.attributes {
Some(ref a) => a.fmt(f),
None => Ok(()),
}
}
}
struct Attributes {
foreground: String,
background: String,
underlined: String,
/// The character set to use.
/// REGEX: `\^[()][AB0-3]`
charset: String,
/// A buffer for unknown sequences.
unknown_buffer: String,
/// ON: ^[1m
/// OFF: ^[22m
bold: String,
/// ON: ^[2m
/// OFF: ^[22m
dim: String,
/// ON: ^[4m
/// OFF: ^[24m
underline: String,
/// ON: ^[3m
/// OFF: ^[23m
italic: String,
/// ON: ^[9m
/// OFF: ^[29m
strike: String,
}
impl Attributes {
pub fn new() -> Self {
Attributes {
foreground: "".to_owned(),
background: "".to_owned(),
underlined: "".to_owned(),
charset: "".to_owned(),
unknown_buffer: "".to_owned(),
bold: "".to_owned(),
dim: "".to_owned(),
underline: "".to_owned(),
italic: "".to_owned(),
strike: "".to_owned(),
}
}
/// Update the attributes with an escape sequence.
/// Returns `false` if the sequence is unsupported.
pub fn update(&mut self, sequence: &str) -> bool {
let mut chars = sequence.char_indices().skip(1);
if let Some((_, t)) = chars.next() {
match t {
'(' => self.update_with_charset('(', chars.map(|(_, c)| c)),
')' => self.update_with_charset(')', chars.map(|(_, c)| c)),
'[' => {
if let Some((i, last)) = chars.last() {
// SAFETY: Always starts with ^[ and ends with m.
self.update_with_csi(last, &sequence[2..i])
} else {
false
}
}
_ => self.update_with_unsupported(sequence),
}
} else {
false
}
}
fn sgr_reset(&mut self) {
self.foreground.clear();
self.background.clear();
self.underlined.clear();
self.bold.clear();
self.dim.clear();
self.underline.clear();
self.italic.clear();
self.strike.clear();
}
fn update_with_sgr(&mut self, parameters: &str) -> bool {
let mut iter = parameters
.split(';')
.map(|p| if p.is_empty() { "0" } else { p })
.map(|p| p.parse::<u16>())
.map(|p| p.unwrap_or(0)); // Treat errors as 0.
while let Some(p) = iter.next() {
match p {
0 => self.sgr_reset(),
1 => self.bold = format!("\x1B[{}m", parameters),
2 => self.dim = format!("\x1B[{}m", parameters),
3 => self.italic = format!("\x1B[{}m", parameters),
4 => self.underline = format!("\x1B[{}m", parameters),
23 => self.italic.clear(),
24 => self.underline.clear(),
22 => {
self.bold.clear();
self.dim.clear();
}
30..=39 => self.foreground = Self::parse_color(p, &mut iter),
40..=49 => self.background = Self::parse_color(p, &mut iter),
58..=59 => self.underlined = Self::parse_color(p, &mut iter),
90..=97 => self.foreground = Self::parse_color(p, &mut iter),
100..=107 => self.foreground = Self::parse_color(p, &mut iter),
_ => {
// Unsupported SGR sequence.
// Be compatible and pretend one just wasn't was provided.
}
}
}
true
}
fn update_with_csi(&mut self, finalizer: char, sequence: &str) -> bool {
if finalizer == 'm' {
self.update_with_sgr(sequence)
} else {
false
}
}
fn update_with_unsupported(&mut self, sequence: &str) -> bool {
self.unknown_buffer.push_str(sequence);
false
}
fn update_with_charset(&mut self, kind: char, set: impl Iterator<Item = char>) -> bool {
self.charset = format!("\x1B{}{}", kind, set.take(1).collect::<String>());
true
}
fn parse_color(color: u16, parameters: &mut dyn Iterator<Item = u16>) -> String {
match color % 10 {
8 => match parameters.next() {
Some(5) /* 256-color */ => format!("\x1B[{};5;{}m", color, join(";", 1, parameters)),
Some(2) /* 24-bit color */ => format!("\x1B[{};2;{}m", color, join(";", 3, parameters)),
Some(c) => format!("\x1B[{};{}m", color, c),
_ => "".to_owned(),
},
9 => "".to_owned(),
_ => format!("\x1B[{}m", color),
}
}
}
impl Display for Attributes {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}{}{}{}{}{}{}{}{}",
self.foreground,
self.background,
self.underlined,
self.charset,
self.bold,
self.dim,
self.underline,
self.italic,
self.strike,
)
}
}
fn join(
delimiter: &str,
limit: usize,
iterator: &mut dyn Iterator<Item = impl ToString>,
) -> String {
iterator
.take(limit)
.map(|i| i.to_string())
.collect::<Vec<String>>()
.join(delimiter)
}

View File

@ -1419,25 +1419,6 @@ fn ansi_highlight_underline() {
.stderr(""); .stderr("");
} }
// Ensure that ANSI passthrough is emitted properly for both wrapping and non-wrapping printer.
#[test]
fn ansi_passthrough_emit() {
for wrapping in &["never", "character"] {
bat()
.arg("--paging=never")
.arg("--color=never")
.arg("--terminal-width=80")
.arg(format!("--wrap={}", wrapping))
.arg("--decorations=always")
.arg("--style=plain")
.write_stdin("\x1B[33mColor\nColor \x1B[m\nPlain\n")
.assert()
.success()
.stdout("\x1B[33m\x1B[33mColor\n\x1B[33mColor \x1B[m\nPlain\n")
.stderr("");
}
}
#[test] #[test]
fn ignored_suffix_arg() { fn ignored_suffix_arg() {
bat() bat()