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,35 +1,26 @@
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) { while let Some(index) = text.find('\t') {
match chunk { // Add previous text.
(text, true) => buffer.push_str(text), if index > 0 {
(mut text, false) => { *cursor += index;
while let Some(index) = text.find('\t') { buffer.push_str(&text[0..index]);
// Add previous text.
if index > 0 {
*cursor += index;
buffer.push_str(&text[0..index]);
}
// Add tab.
let spaces = width - (*cursor % width);
*cursor += spaces;
buffer.push_str(&*" ".repeat(spaces));
// Next.
text = &text[index + 1..text.len()];
}
*cursor += text.len();
buffer.push_str(text);
}
} }
// Add tab.
let spaces = width - (*cursor % width);
*cursor += spaces;
buffer.push_str(&*" ".repeat(spaces));
// Next.
text = &text[index + 1..text.len()];
} }
*cursor += text.len();
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,51 +498,37 @@ 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 { let text_trimmed = text.trim_end_matches(|c| c == '\r' || c == '\n');
match chunk {
// ANSI escape passthrough.
(ansi, true) => {
self.ansi_style.update(ansi);
write!(handle, "{}", ansi)?;
}
// Regular text. write!(
(text, false) => { handle,
let text = &*self.preprocess(text, &mut cursor_total); "{}",
let text_trimmed = text.trim_end_matches(|c| c == '\r' || c == '\n'); as_terminal_escaped(
style,
text_trimmed,
true_color,
colored_output,
italics,
background_color
)
)?;
write!( if text.len() != text_trimmed.len() {
handle, if let Some(background_color) = background_color {
"{}", let ansi_style = Style {
as_terminal_escaped( background: to_ansi_color(background_color, true_color),
style, ..Default::default()
&format!("{}{}", self.ansi_style, text_trimmed), };
true_color,
colored_output,
italics,
background_color
)
)?;
if text.len() != text_trimmed.len() { let width = if cursor_total <= cursor_max {
if let Some(background_color) = background_color { cursor_max - cursor_total + 1
let ansi_style = Style { } else {
background: to_ansi_color(background_color, true_color), 0
..Default::default() };
}; write!(handle, "{}", ansi_style.paint(" ".repeat(width)))?;
let width = if cursor_total <= cursor_max {
cursor_max - cursor_total + 1
} else {
0
};
write!(handle, "{}", ansi_style.paint(" ".repeat(width)))?;
}
write!(handle, "{}", &text[text_trimmed.len()..])?;
}
}
} }
write!(handle, "{}", &text[text_trimmed.len()..])?;
} }
} }
@ -556,98 +537,82 @@ impl<'a> Printer for InteractivePrinter<'a> {
} }
} else { } else {
for &(style, region) in &regions { for &(style, region) in &regions {
let ansi_iterator = AnsiCodeIterator::new(region); let text = self.preprocess(
for chunk in ansi_iterator { region.trim_end_matches(|c| c == '\r' || c == '\n'),
match chunk { &mut cursor_total,
// ANSI escape passthrough. );
(ansi, true) => {
self.ansi_style.update(ansi);
write!(handle, "{}", ansi)?;
}
// Regular text. let mut max_width = cursor_max - cursor;
(text, false) => {
let text = self.preprocess(
text.trim_end_matches(|c| c == '\r' || c == '\n'),
&mut cursor_total,
);
let mut max_width = cursor_max - cursor; // line buffer (avoid calling write! for every character)
let mut line_buf = String::with_capacity(max_width * 4);
// line buffer (avoid calling write! for every character) // Displayed width of line_buf
let mut line_buf = String::with_capacity(max_width * 4); let mut current_width = 0;
// Displayed width of line_buf for c in text.chars() {
let mut current_width = 0; // calculate the displayed width for next character
let cw = c.width().unwrap_or(0);
current_width += cw;
for c in text.chars() { // if next character cannot be printed on this line,
// calculate the displayed width for next character // flush the buffer.
let cw = c.width().unwrap_or(0); if current_width > max_width {
current_width += cw; // Generate wrap padding if not already generated.
if panel_wrap.is_none() {
// if next character cannot be printed on this line, panel_wrap = if self.panel_width > 0 {
// flush the buffer. Some(format!(
if current_width > max_width { "{} ",
// Generate wrap padding if not already generated. self.decorations
if panel_wrap.is_none() { .iter()
panel_wrap = if self.panel_width > 0 { .map(|d| d.generate(line_number, true, self).text)
Some(format!( .collect::<Vec<String>>()
"{} ", .join(" ")
self.decorations ))
.iter() } else {
.map(|d| d Some("".to_string())
.generate(line_number, true, self)
.text)
.collect::<Vec<String>>()
.join(" ")
))
} else {
Some("".to_string())
}
}
// It wraps.
write!(
handle,
"{}\n{}",
as_terminal_escaped(
style,
&*format!("{}{}", self.ansi_style, line_buf),
self.config.true_color,
self.config.colored_output,
self.config.use_italic_text,
background_color
),
panel_wrap.clone().unwrap()
)?;
cursor = 0;
max_width = cursor_max;
line_buf.clear();
current_width = cw;
}
line_buf.push(c);
} }
// flush the buffer
cursor += current_width;
write!(
handle,
"{}",
as_terminal_escaped(
style,
&*format!("{}{}", self.ansi_style, line_buf),
self.config.true_color,
self.config.colored_output,
self.config.use_italic_text,
background_color
)
)?;
} }
// It wraps.
write!(
handle,
"{}\n{}",
as_terminal_escaped(
style,
&line_buf,
self.config.true_color,
self.config.colored_output,
self.config.use_italic_text,
background_color
),
panel_wrap.clone().unwrap()
)?;
cursor = 0;
max_width = cursor_max;
line_buf.clear();
current_width = cw;
} }
line_buf.push(c);
} }
// flush the buffer
cursor += current_width;
write!(
handle,
"{}",
as_terminal_escaped(
style,
&line_buf,
self.config.true_color,
self.config.colored_output,
self.config.use_italic_text,
background_color
)
)?;
} }
if let Some(background_color) = background_color { if let Some(background_color) = background_color {
@ -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()