mirror of
https://github.com/sharkdp/bat.git
synced 2025-02-07 13:41:14 +00:00
Replace AnsiCodeIterator in printer.rs
This uses the new EscapeSequenceIterator, saving us a preprocessing step for each line.
This commit is contained in:
parent
6b9b085be3
commit
165c495e75
@ -7,8 +7,6 @@ use nu_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,9 +31,23 @@ 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::{strip_problematic_sequences, AnsiStyle};
|
use crate::vscreen::{AnsiStyle, EscapeSequence, EscapeSequenceIterator};
|
||||||
use crate::wrapping::WrappingMode;
|
use crate::wrapping::WrappingMode;
|
||||||
|
|
||||||
|
const ANSI_UNDERLINE_ENABLE: EscapeSequence = EscapeSequence::CSI {
|
||||||
|
raw_sequence: "\x1B[4m",
|
||||||
|
parameters: "4",
|
||||||
|
intermediates: "",
|
||||||
|
final_byte: "m",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ANSI_UNDERLINE_DISABLE: EscapeSequence = EscapeSequence::CSI {
|
||||||
|
raw_sequence: "\x1B[24m",
|
||||||
|
parameters: "24",
|
||||||
|
intermediates: "",
|
||||||
|
final_byte: "m",
|
||||||
|
};
|
||||||
|
|
||||||
pub enum OutputHandle<'a> {
|
pub enum OutputHandle<'a> {
|
||||||
IoWrite(&'a mut dyn io::Write),
|
IoWrite(&'a mut dyn io::Write),
|
||||||
FmtWrite(&'a mut dyn fmt::Write),
|
FmtWrite(&'a mut dyn fmt::Write),
|
||||||
@ -554,7 +566,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");
|
self.ansi_style.update(ANSI_UNDERLINE_ENABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
let background_color = self
|
let background_color = self
|
||||||
@ -581,18 +593,11 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
let italics = self.config.use_italic_text;
|
let italics = self.config.use_italic_text;
|
||||||
|
|
||||||
for &(style, region) in ®ions {
|
for &(style, region) in ®ions {
|
||||||
let text = strip_problematic_sequences(region);
|
let ansi_iterator = EscapeSequenceIterator::new(region);
|
||||||
let ansi_iterator = AnsiCodeIterator::new(&text);
|
|
||||||
for chunk in ansi_iterator {
|
for chunk in ansi_iterator {
|
||||||
match chunk {
|
match chunk {
|
||||||
// ANSI escape passthrough.
|
|
||||||
(ansi, true) => {
|
|
||||||
self.ansi_style.update(ansi);
|
|
||||||
write!(handle, "{}", ansi)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular text.
|
// Regular text.
|
||||||
(text, false) => {
|
EscapeSequence::Text(text) => {
|
||||||
let text = &*self.preprocess(text, &mut cursor_total);
|
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');
|
||||||
|
|
||||||
@ -626,6 +631,12 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
write!(handle, "{}", &text[text_trimmed.len()..])?;
|
write!(handle, "{}", &text[text_trimmed.len()..])?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ANSI escape passthrough.
|
||||||
|
_ => {
|
||||||
|
write!(handle, "{}", chunk.raw())?;
|
||||||
|
self.ansi_style.update(chunk);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -635,18 +646,11 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for &(style, region) in ®ions {
|
for &(style, region) in ®ions {
|
||||||
let text = strip_problematic_sequences(region);
|
let ansi_iterator = EscapeSequenceIterator::new(region);
|
||||||
let ansi_iterator = AnsiCodeIterator::new(&text);
|
|
||||||
for chunk in ansi_iterator {
|
for chunk in ansi_iterator {
|
||||||
match chunk {
|
match chunk {
|
||||||
// ANSI escape passthrough.
|
|
||||||
(ansi, true) => {
|
|
||||||
self.ansi_style.update(ansi);
|
|
||||||
write!(handle, "{}", ansi)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular text.
|
// Regular text.
|
||||||
(text, false) => {
|
EscapeSequence::Text(text) => {
|
||||||
let text = self.preprocess(
|
let text = self.preprocess(
|
||||||
text.trim_end_matches(|c| c == '\r' || c == '\n'),
|
text.trim_end_matches(|c| c == '\r' || c == '\n'),
|
||||||
&mut cursor_total,
|
&mut cursor_total,
|
||||||
@ -726,6 +730,12 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
)
|
)
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ANSI escape passthrough.
|
||||||
|
_ => {
|
||||||
|
write!(handle, "{}", chunk.raw())?;
|
||||||
|
self.ansi_style.update(chunk);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -746,8 +756,8 @@ 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, "{}", ANSI_UNDERLINE_DISABLE.raw())?;
|
||||||
write!(handle, "\x1B[24m")?;
|
self.ansi_style.update(ANSI_UNDERLINE_DISABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
105
src/vscreen.rs
105
src/vscreen.rs
@ -14,7 +14,7 @@ impl AnsiStyle {
|
|||||||
AnsiStyle { attributes: None }
|
AnsiStyle { attributes: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, sequence: &str) -> bool {
|
pub fn update(&mut self, sequence: EscapeSequence) -> bool {
|
||||||
match &mut self.attributes {
|
match &mut self.attributes {
|
||||||
Some(a) => a.update(sequence),
|
Some(a) => a.update(sequence),
|
||||||
None => {
|
None => {
|
||||||
@ -85,26 +85,36 @@ impl Attributes {
|
|||||||
|
|
||||||
/// Update the attributes with an escape sequence.
|
/// Update the attributes with an escape sequence.
|
||||||
/// Returns `false` if the sequence is unsupported.
|
/// Returns `false` if the sequence is unsupported.
|
||||||
pub fn update(&mut self, sequence: &str) -> bool {
|
pub fn update(&mut self, sequence: EscapeSequence) -> bool {
|
||||||
let mut chars = sequence.char_indices().skip(1);
|
use EscapeSequence::*;
|
||||||
|
match sequence {
|
||||||
if let Some((_, t)) = chars.next() {
|
Text(_) => return false,
|
||||||
match t {
|
Unknown(_) => { /* defer to update_with_unsupported */ }
|
||||||
'(' => self.update_with_charset('(', chars.map(|(_, c)| c)),
|
OSC { .. } => return false,
|
||||||
')' => self.update_with_charset(')', chars.map(|(_, c)| c)),
|
CSI {
|
||||||
'[' => {
|
final_byte,
|
||||||
if let Some((i, last)) = chars.last() {
|
parameters,
|
||||||
// SAFETY: Always starts with ^[ and ends with m.
|
..
|
||||||
self.update_with_csi(last, &sequence[2..i])
|
} => {
|
||||||
} else {
|
match final_byte {
|
||||||
false
|
"m" => return self.update_with_sgr(parameters),
|
||||||
|
_ => {
|
||||||
|
// NOTE(eth-p): We might want to ignore these, since they involve cursor or buffer manipulation.
|
||||||
|
/* defer to update_with_unsupported */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => self.update_with_unsupported(sequence),
|
|
||||||
}
|
}
|
||||||
} else {
|
NF { nf_sequence, .. } => {
|
||||||
false
|
let mut iter = nf_sequence.chars();
|
||||||
|
match iter.next() {
|
||||||
|
Some('(') => return self.update_with_charset('(', iter),
|
||||||
|
Some(')') => return self.update_with_charset(')', iter),
|
||||||
|
_ => { /* defer to update_with_unsupported */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.update_with_unsupported(sequence.raw())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sgr_reset(&mut self) {
|
fn sgr_reset(&mut self) {
|
||||||
@ -153,14 +163,6 @@ impl Attributes {
|
|||||||
true
|
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 {
|
fn update_with_unsupported(&mut self, sequence: &str) -> bool {
|
||||||
self.unknown_buffer.push_str(sequence);
|
self.unknown_buffer.push_str(sequence);
|
||||||
false
|
false
|
||||||
@ -520,49 +522,6 @@ impl<'a> Iterator for EscapeSequenceIterator<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Strips problematic ANSI escape sequences from a string.
|
|
||||||
///
|
|
||||||
/// Ideally, this will be replaced with something that uses [[Attributes]] to create a table of char offsets
|
|
||||||
/// -> absolute styles and style deltas. Something like that would let us simplify the printer (and support
|
|
||||||
/// re-printing OSC hyperlink commands).
|
|
||||||
pub fn strip_problematic_sequences(text: &str) -> String {
|
|
||||||
use EscapeSequenceOffsets::*;
|
|
||||||
|
|
||||||
let mut buffer = String::with_capacity(text.len());
|
|
||||||
for seq in EscapeSequenceOffsetsIterator::new(text) {
|
|
||||||
match seq {
|
|
||||||
Text { start, end } => buffer.push_str(&text[start..end]),
|
|
||||||
Unknown { start, end } => buffer.push_str(&text[start..end]),
|
|
||||||
|
|
||||||
NF {
|
|
||||||
start_sequence: start,
|
|
||||||
start: _,
|
|
||||||
end,
|
|
||||||
} => buffer.push_str(&text[start..end]),
|
|
||||||
|
|
||||||
CSI {
|
|
||||||
start_sequence: start,
|
|
||||||
start_parameters: _,
|
|
||||||
start_intermediates: _,
|
|
||||||
start_final_byte: _,
|
|
||||||
end,
|
|
||||||
} => buffer.push_str(&text[start..end]),
|
|
||||||
|
|
||||||
OSC {
|
|
||||||
start_sequence: _,
|
|
||||||
start_command: _,
|
|
||||||
start_terminator: _,
|
|
||||||
end: _,
|
|
||||||
} => {
|
|
||||||
// TODO(eth-p): Support re-printing hyperlinks.
|
|
||||||
// In the meantime, strip these.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A parsed ANSI/VT100 escape sequence.
|
/// A parsed ANSI/VT100 escape sequence.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum EscapeSequence<'a> {
|
pub enum EscapeSequence<'a> {
|
||||||
@ -601,7 +560,7 @@ impl<'a> EscapeSequence<'a> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::vscreen::{
|
use crate::vscreen::{
|
||||||
strip_problematic_sequences, EscapeSequence, EscapeSequenceIterator, EscapeSequenceOffsets,
|
EscapeSequence, EscapeSequenceIterator, EscapeSequenceOffsets,
|
||||||
EscapeSequenceOffsetsIterator,
|
EscapeSequenceOffsetsIterator,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -827,14 +786,6 @@ mod tests {
|
|||||||
assert_eq!(iter.next(), None);
|
assert_eq!(iter.next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_strip_problematic_sequences() {
|
|
||||||
assert_eq!(
|
|
||||||
strip_problematic_sequences("text\x1B[33m\x1B]OSC\x1B\\\x1B(0"),
|
|
||||||
"text\x1B[33m\x1B(0"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_escape_sequence_iterator_iterates() {
|
fn test_escape_sequence_iterator_iterates() {
|
||||||
let mut iter = EscapeSequenceIterator::new("text\x1B[33m\x1B]OSC\x07\x1B]OSC\x1B\\\x1B(0");
|
let mut iter = EscapeSequenceIterator::new("text\x1B[33m\x1B]OSC\x07\x1B]OSC\x1B\\\x1B(0");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user