Treat OSC ANSI Sequences as Invisible Text & Add OSC 8 Support
Fixes #2541 by ~~removing OSC sequences from lines before starting to print them.~~ no longer considering ANSI OSC sequences as visible text.

|<img width="1278" alt="image" src="https://user-images.githubusercontent.com/32112321/232371560-eaa6389a-2ae4-434e-9e90-3f9e8fdf6a72.png">|
|:-:|
|(Left: Fixed, Right: Current version)|

Prior to this change, we don't interpret OSC sequences in any special way. They aren't caught by `console::AnsiCodeIterator`, so the OSC sequences were being treated as visible text.

- When the OSC is printed in full, the terminal would see a valid sequence and hide it. As a consequence, lines would b e wrapped too early.

- When the OSC is broken for line wrapping, the terminal wouldn't see a valid sequence. In this case, it prints both halves of the text on different lines and appears to be junk.

---

**Edit:** I had some extra time. A couple of new commits gave special handling for the OSC 8 sequence, allowing bat to disable the hyperlink at the end of the line and then re-emit it at the start of the next line.

<img width="1838" alt="image" src="https://user-images.githubusercontent.com/32112321/232653339-a4b09511-2ada-4b93-9259-5f736be9dca4.png">

Hello -

When bat displays something that already includes an OSC8 sequence, it has erratic output, dependent on the terminal-width. Seemingly, OSC8 ids and urls are included in line-wrapping which leads to strange output depending on where the line is cut.

**What steps will reproduce the bug?**

Works as expected:
```sh
echo '\e]8;;http://example.com\e\\This is a link\e]8;;\e\\\n' | bat --terminal-width=64
```
Includes escape characters in output:
```sh
echo '\e]8;;http://example.com\e\\This is a link\e]8;;\e\\\n' | bat --terminal-width=48
```
No escape characters, but incorrect wrapping:
```sh
echo '\e]8;;http://example.com\e\\This is a link\e]8;;\e\\\n' | bat --terminal-width=40
```

**What happens?**

If you keep changing the terminal width, strange things keep happening.

**What did you expect to happen instead?**

The output should be the same in all above scenarios, as long as `terminal-width` is greater than the length of the printable section of an OSC8 sequence ("This is a link") in this case.


**How did you install `bat`?**

```sh
sudo pacman -S bat
```

**bat version and environment**


#### Software version

bat 0.23.0 (871abd27)

#### Operating system

Linux 6.2.10-arch1-1

#### Command-line

```bash
bat --terminal-width=24 --diagnostic 
```

#### Environment variables

```bash
SHELL=/usr/bin/zsh
PAGER=<not set>
LESS=<not set>
LANG=en_US.UTF-8
LC_ALL=<not set>
BAT_PAGER=<not set>
BAT_CACHE_PATH=<not set>
BAT_CONFIG_PATH=<not set>
BAT_OPTS=<not set>
BAT_STYLE=<not set>
BAT_TABS=<not set>
BAT_THEME=<not set>
XDG_CONFIG_HOME=<not set>
XDG_CACHE_HOME=<not set>
COLORTERM=truecolor
NO_COLOR=<not set>
MANPAGER=<not set>
```

#### System Config file

Could not read contents of '/etc/bat/config': No such file or directory (os error 2).

#### Config file

Could not read contents of '/home/tim/.config/bat/config': No such file or directory (os error 2).

#### Custom assets metadata

Could not read contents of '/home/tim/.cache/bat/metadata.yaml': No such file or directory (os error 2).

#### Custom assets

'/home/tim/.cache/bat' not found

#### Compile time information

- Profile: release
- Target triple: x86_64-unknown-linux-gnu
- Family: unix
- OS: linux
- Architecture: x86_64
- Pointer width: 64
- Endian: little
- CPU features: fxsr,sse,sse2
- Host: x86_64-unknown-linux-gnu

#### Less version

```
> less --version 
less 608 (PCRE2 regular expressions)
Copyright (C) 1984-2022  Mark Nudelman

less comes with NO WARRANTY, to the extent permitted by law.
For information about the terms of redistribution,
see the file named README in the less distribution.
Home page: https://greenwoodsoftware.com/less
```



Erratic output when OSC8 sequences are present
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7ee759a4ef..b6e134695b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
 
 - Fix long file name wrapping in header, see #2835 (@FilipRazek)
 - Fix `NO_COLOR` support, see #2767 (@acuteenvy)
+- Fix handling of inputs with OSC ANSI escape sequences, see #2541 and #2544 (@eth-p)
 
 ## Other
 
diff --git a/src/printer.rs b/src/printer.rs
index 257cc76659..f413fdc310 100644
--- a/src/printer.rs
+++ b/src/printer.rs
@@ -7,8 +7,6 @@ use nu_ansi_term::Style;
 
 use bytesize::ByteSize;
 
-use console::AnsiCodeIterator;
-
 use syntect::easy::HighlightLines;
 use syntect::highlighting::Color;
 use syntect::highlighting::Theme;
@@ -33,9 +31,23 @@ use crate::line_range::RangeCheckResult;
 use crate::preprocessor::{expand_tabs, replace_nonprintable};
 use crate::style::StyleComponent;
 use crate::terminal::{as_terminal_escaped, to_ansi_color};
-use crate::vscreen::AnsiStyle;
+use crate::vscreen::{AnsiStyle, EscapeSequence, EscapeSequenceIterator};
 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> {
     IoWrite(&'a mut dyn io::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;
 
         if highlight_this_line && self.config.theme == "ansi" {
-            self.ansi_style.update("^[4m");
+            self.ansi_style.update(ANSI_UNDERLINE_ENABLE);
         }
 
         let background_color = self
@@ -581,23 +593,17 @@ impl<'a> Printer for InteractivePrinter<'a> {
             let italics = self.config.use_italic_text;
 
             for &(style, region) in &regions {
-                let ansi_iterator = AnsiCodeIterator::new(region);
+                let ansi_iterator = EscapeSequenceIterator::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(text, &mut cursor_total);
+                        EscapeSequence::Text(text) => {
+                            let text = self.preprocess(text, &mut cursor_total);
                             let text_trimmed = text.trim_end_matches(|c| c == '\r' || c == '\n');
 
                             write!(
                                 handle,
-                                "{}",
+                                "{}{}",
                                 as_terminal_escaped(
                                     style,
                                     &format!("{}{}", self.ansi_style, text_trimmed),
@@ -605,9 +611,11 @@ impl<'a> Printer for InteractivePrinter<'a> {
                                     colored_output,
                                     italics,
                                     background_color
-                                )
+                                ),
+                                self.ansi_style.to_reset_sequence(),
                             )?;
 
+                            // Pad the rest of the line.
                             if text.len() != text_trimmed.len() {
                                 if let Some(background_color) = background_color {
                                     let ansi_style = Style {
@@ -625,6 +633,12 @@ impl<'a> Printer for InteractivePrinter<'a> {
                                 write!(handle, "{}", &text[text_trimmed.len()..])?;
                             }
                         }
+
+                        // ANSI escape passthrough.
+                        _ => {
+                            write!(handle, "{}", chunk.raw())?;
+                            self.ansi_style.update(chunk);
+                        }
                     }
                 }
             }
@@ -634,17 +648,11 @@ impl<'a> Printer for InteractivePrinter<'a> {
             }
         } else {
             for &(style, region) in &regions {
-                let ansi_iterator = AnsiCodeIterator::new(region);
+                let ansi_iterator = EscapeSequenceIterator::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) => {
+                        EscapeSequence::Text(text) => {
                             let text = self.preprocess(
                                 text.trim_end_matches(|c| c == '\r' || c == '\n'),
                                 &mut cursor_total,
@@ -687,7 +695,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
                                     // It wraps.
                                     write!(
                                         handle,
-                                        "{}\n{}",
+                                        "{}{}\n{}",
                                         as_terminal_escaped(
                                             style,
                                             &format!("{}{}", self.ansi_style, line_buf),
@@ -696,6 +704,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
                                             self.config.use_italic_text,
                                             background_color
                                         ),
+                                        self.ansi_style.to_reset_sequence(),
                                         panel_wrap.clone().unwrap()
                                     )?;
 
@@ -724,6 +733,12 @@ impl<'a> Printer for InteractivePrinter<'a> {
                                 )
                             )?;
                         }
+
+                        // ANSI escape passthrough.
+                        _ => {
+                            write!(handle, "{}", chunk.raw())?;
+                            self.ansi_style.update(chunk);
+                        }
                     }
                 }
             }
@@ -744,8 +759,8 @@ impl<'a> Printer for InteractivePrinter<'a> {
         }
 
         if highlight_this_line && self.config.theme == "ansi" {
-            self.ansi_style.update("^[24m");
-            write!(handle, "\x1B[24m")?;
+            write!(handle, "{}", ANSI_UNDERLINE_DISABLE.raw())?;
+            self.ansi_style.update(ANSI_UNDERLINE_DISABLE);
         }
 
         Ok(())
diff --git a/src/vscreen.rs b/src/vscreen.rs
index ea5d4da6f0..c902d42bf6 100644
--- a/src/vscreen.rs
+++ b/src/vscreen.rs
@@ -1,4 +1,8 @@
-use std::fmt::{Display, Formatter};
+use std::{
+    fmt::{Display, Formatter},
+    iter::Peekable,
+    str::CharIndices,
+};
 
 // Wrapper to avoid unnecessary branching when input doesn't have ANSI escape sequences.
 pub struct AnsiStyle {
@@ -10,7 +14,7 @@ impl AnsiStyle {
         AnsiStyle { attributes: None }
     }
 
-    pub fn update(&mut self, sequence: &str) -> bool {
+    pub fn update(&mut self, sequence: EscapeSequence) -> bool {
         match &mut self.attributes {
             Some(a) => a.update(sequence),
             None => {
@@ -19,6 +23,13 @@ impl AnsiStyle {
             }
         }
     }
+
+    pub fn to_reset_sequence(&mut self) -> String {
+        match &mut self.attributes {
+            Some(a) => a.to_reset_sequence(),
+            None => String::new(),
+        }
+    }
 }
 
 impl Display for AnsiStyle {
@@ -31,6 +42,8 @@ impl Display for AnsiStyle {
 }
 
 struct Attributes {
+    has_sgr_sequences: bool,
+
     foreground: String,
     background: String,
     underlined: String,
@@ -61,11 +74,20 @@ struct Attributes {
     /// ON:  ^[9m
     /// OFF: ^[29m
     strike: String,
+
+    /// The hyperlink sequence.
+    /// FORMAT: \x1B]8;{ID};{URL}\e\\
+    ///
+    /// `\e\\` may be replaced with BEL `\x07`.
+    /// Setting both {ID} and {URL} to an empty string represents no hyperlink.
+    hyperlink: String,
 }
 
 impl Attributes {
     pub fn new() -> Self {
         Attributes {
+            has_sgr_sequences: false,
+
             foreground: "".to_owned(),
             background: "".to_owned(),
             underlined: "".to_owned(),
@@ -76,34 +98,56 @@ impl Attributes {
             underline: "".to_owned(),
             italic: "".to_owned(),
             strike: "".to_owned(),
+            hyperlink: "".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
+    pub fn update(&mut self, sequence: EscapeSequence) -> bool {
+        use EscapeSequence::*;
+        match sequence {
+            Text(_) => return false,
+            Unknown(_) => { /* defer to update_with_unsupported */ }
+            OSC {
+                raw_sequence,
+                command,
+                ..
+            } => {
+                if command.starts_with("8;") {
+                    return self.update_with_hyperlink(raw_sequence);
+                }
+                /* defer to update_with_unsupported */
+            }
+            CSI {
+                final_byte,
+                parameters,
+                ..
+            } => {
+                match final_byte {
+                    "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 {
-            false
+            NF { nf_sequence, .. } => {
+                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) {
+        self.has_sgr_sequences = false;
+
         self.foreground.clear();
         self.background.clear();
         self.underlined.clear();
@@ -121,6 +165,7 @@ impl Attributes {
             .map(|p| p.parse::<u16>())
             .map(|p| p.unwrap_or(0)); // Treat errors as 0.
 
+        self.has_sgr_sequences = true;
         while let Some(p) = iter.next() {
             match p {
                 0 => self.sgr_reset(),
@@ -149,19 +194,23 @@ impl Attributes {
         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_hyperlink(&mut self, sequence: &str) -> bool {
+        if sequence == "8;;" {
+            // Empty hyperlink ID and HREF -> end of hyperlink.
+            self.hyperlink.clear();
+        } else {
+            self.hyperlink.clear();
+            self.hyperlink.push_str(sequence);
+        }
+
+        true
+    }
+
     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
@@ -179,13 +228,35 @@ impl Attributes {
             _ => format!("\x1B[{}m", color),
         }
     }
+
+    /// Gets an ANSI escape sequence to reset all the known attributes.
+    pub fn to_reset_sequence(&self) -> String {
+        let mut buf = String::with_capacity(17);
+
+        // TODO: Enable me in a later pull request.
+        // if self.has_sgr_sequences {
+        //     buf.push_str("\x1B[m");
+        // }
+
+        if !self.hyperlink.is_empty() {
+            buf.push_str("\x1B]8;;\x1B\\"); // Disable hyperlink.
+        }
+
+        // TODO: Enable me in a later pull request.
+        // if !self.charset.is_empty() {
+        //     // https://espterm.github.io/docs/VT100%20escape%20codes.html
+        //     buf.push_str("\x1B(B\x1B)B"); // setusg0 and setusg1
+        // }
+
+        buf
+    }
 }
 
 impl Display for Attributes {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
         write!(
             f,
-            "{}{}{}{}{}{}{}{}{}",
+            "{}{}{}{}{}{}{}{}{}{}",
             self.foreground,
             self.background,
             self.underlined,
@@ -195,6 +266,7 @@ impl Display for Attributes {
             self.underline,
             self.italic,
             self.strike,
+            self.hyperlink,
         )
     }
 }
@@ -210,3 +282,612 @@ fn join(
         .collect::<Vec<String>>()
         .join(delimiter)
 }
+
+/// A range of indices for a raw ANSI escape sequence.
+#[derive(Debug, PartialEq)]
+enum EscapeSequenceOffsets {
+    Text {
+        start: usize,
+        end: usize,
+    },
+    Unknown {
+        start: usize,
+        end: usize,
+    },
+    NF {
+        // https://en.wikipedia.org/wiki/ANSI_escape_code#nF_Escape_sequences
+        start_sequence: usize,
+        start: usize,
+        end: usize,
+    },
+    OSC {
+        // https://en.wikipedia.org/wiki/ANSI_escape_code#OSC_(Operating_System_Command)_sequences
+        start_sequence: usize,
+        start_command: usize,
+        start_terminator: usize,
+        end: usize,
+    },
+    CSI {
+        // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences
+        start_sequence: usize,
+        start_parameters: usize,
+        start_intermediates: usize,
+        start_final_byte: usize,
+        end: usize,
+    },
+}
+
+/// An iterator over the offests of ANSI/VT escape sequences within a string.
+///
+/// ## Example
+///
+/// ```ignore
+/// let iter = EscapeSequenceOffsetsIterator::new("\x1B[33mThis is yellow text.\x1B[m");
+/// ```
+struct EscapeSequenceOffsetsIterator<'a> {
+    text: &'a str,
+    chars: Peekable<CharIndices<'a>>,
+}
+
+impl<'a> EscapeSequenceOffsetsIterator<'a> {
+    pub fn new(text: &'a str) -> EscapeSequenceOffsetsIterator<'a> {
+        return EscapeSequenceOffsetsIterator {
+            text,
+            chars: text.char_indices().peekable(),
+        };
+    }
+
+    /// Takes values from the iterator while the predicate returns true.
+    /// If the predicate returns false, that value is left.
+    fn chars_take_while(&mut self, pred: impl Fn(char) -> bool) -> Option<(usize, usize)> {
+        if self.chars.peek().is_none() {
+            return None;
+        }
+
+        let start = self.chars.peek().unwrap().0;
+        let mut end: usize = start;
+        while let Some((i, c)) = self.chars.peek() {
+            if !pred(*c) {
+                break;
+            }
+
+            end = *i + c.len_utf8();
+            self.chars.next();
+        }
+
+        Some((start, end))
+    }
+
+    fn next_text(&mut self) -> Option<EscapeSequenceOffsets> {
+        match self.chars_take_while(|c| c != '\x1B') {
+            None => None,
+            Some((start, end)) => Some(EscapeSequenceOffsets::Text { start, end }),
+        }
+    }
+
+    fn next_sequence(&mut self) -> Option<EscapeSequenceOffsets> {
+        let (start_sequence, c) = self.chars.next().expect("to not be finished");
+        match self.chars.peek() {
+            None => Some(EscapeSequenceOffsets::Unknown {
+                start: start_sequence,
+                end: start_sequence + c.len_utf8(),
+            }),
+
+            Some((_, ']')) => self.next_osc(start_sequence),
+            Some((_, '[')) => self.next_csi(start_sequence),
+            Some((i, c)) => match c {
+                '\x20'..='\x2F' => self.next_nf(start_sequence),
+                c => Some(EscapeSequenceOffsets::Unknown {
+                    start: start_sequence,
+                    end: i + c.len_utf8(),
+                }),
+            },
+        }
+    }
+
+    fn next_osc(&mut self, start_sequence: usize) -> Option<EscapeSequenceOffsets> {
+        let (osc_open_index, osc_open_char) = self.chars.next().expect("to not be finished");
+        debug_assert_eq!(osc_open_char, ']');
+
+        let mut start_terminator: usize;
+        let mut end_sequence: usize;
+
+        loop {
+            match self.chars_take_while(|c| !matches!(c, '\x07' | '\x1B')) {
+                None => {
+                    start_terminator = self.text.len();
+                    end_sequence = start_terminator;
+                    break;
+                }
+
+                Some((_, end)) => {
+                    start_terminator = end;
+                    end_sequence = end;
+                }
+            }
+
+            match self.chars.next() {
+                Some((ti, '\x07')) => {
+                    end_sequence = ti + '\x07'.len_utf8();
+                    break;
+                }
+
+                Some((ti, '\x1B')) => {
+                    match self.chars.next() {
+                        Some((i, '\\')) => {
+                            end_sequence = i + '\\'.len_utf8();
+                            break;
+                        }
+
+                        None => {
+                            end_sequence = ti + '\x1B'.len_utf8();
+                            break;
+                        }
+
+                        _ => {
+                            // Repeat, since `\\`(anything) isn't a valid ST.
+                        }
+                    }
+                }
+
+                None => {
+                    // Prematurely ends.
+                    break;
+                }
+
+                Some((_, tc)) => {
+                    panic!("this should not be reached: char {:?}", tc)
+                }
+            }
+        }
+
+        Some(EscapeSequenceOffsets::OSC {
+            start_sequence,
+            start_command: osc_open_index + osc_open_char.len_utf8(),
+            start_terminator: start_terminator,
+            end: end_sequence,
+        })
+    }
+
+    fn next_csi(&mut self, start_sequence: usize) -> Option<EscapeSequenceOffsets> {
+        let (csi_open_index, csi_open_char) = self.chars.next().expect("to not be finished");
+        debug_assert_eq!(csi_open_char, '[');
+
+        let start_parameters: usize = csi_open_index + csi_open_char.len_utf8();
+
+        // Keep iterating while within the range of `0x30-0x3F`.
+        let mut start_intermediates: usize = start_parameters;
+        if let Some((_, end)) = self.chars_take_while(|c| matches!(c, '\x30'..='\x3F')) {
+            start_intermediates = end;
+        }
+
+        // Keep iterating while within the range of `0x20-0x2F`.
+        let mut start_final_byte: usize = start_intermediates;
+        if let Some((_, end)) = self.chars_take_while(|c| matches!(c, '\x20'..='\x2F')) {
+            start_final_byte = end;
+        }
+
+        // Take the last char.
+        let end_of_sequence = match self.chars.next() {
+            None => start_final_byte,
+            Some((i, c)) => i + c.len_utf8(),
+        };
+
+        Some(EscapeSequenceOffsets::CSI {
+            start_sequence,
+            start_parameters,
+            start_intermediates,
+            start_final_byte,
+            end: end_of_sequence,
+        })
+    }
+
+    fn next_nf(&mut self, start_sequence: usize) -> Option<EscapeSequenceOffsets> {
+        let (nf_open_index, nf_open_char) = self.chars.next().expect("to not be finished");
+        debug_assert!(matches!(nf_open_char, '\x20'..='\x2F'));
+
+        let start: usize = nf_open_index;
+        let mut end: usize = start;
+
+        // Keep iterating while within the range of `0x20-0x2F`.
+        match self.chars_take_while(|c| matches!(c, '\x20'..='\x2F')) {
+            Some((_, i)) => end = i,
+            None => {
+                return Some(EscapeSequenceOffsets::NF {
+                    start_sequence,
+                    start,
+                    end,
+                })
+            }
+        }
+
+        // Get the final byte.
+        match self.chars.next() {
+            Some((i, c)) => end = i + c.len_utf8(),
+            None => {}
+        }
+
+        Some(EscapeSequenceOffsets::NF {
+            start_sequence,
+            start,
+            end,
+        })
+    }
+}
+
+impl<'a> Iterator for EscapeSequenceOffsetsIterator<'a> {
+    type Item = EscapeSequenceOffsets;
+    fn next(&mut self) -> Option<Self::Item> {
+        match self.chars.peek() {
+            Some((_, '\x1B')) => self.next_sequence(),
+            Some((_, _)) => self.next_text(),
+            None => None,
+        }
+    }
+}
+
+/// An iterator over ANSI/VT escape sequences within a string.
+///
+/// ## Example
+///
+/// ```ignore
+/// let iter = EscapeSequenceIterator::new("\x1B[33mThis is yellow text.\x1B[m");
+/// ```
+pub struct EscapeSequenceIterator<'a> {
+    text: &'a str,
+    offset_iter: EscapeSequenceOffsetsIterator<'a>,
+}
+
+impl<'a> EscapeSequenceIterator<'a> {
+    pub fn new(text: &'a str) -> EscapeSequenceIterator<'a> {
+        return EscapeSequenceIterator {
+            text,
+            offset_iter: EscapeSequenceOffsetsIterator::new(text),
+        };
+    }
+}
+
+impl<'a> Iterator for EscapeSequenceIterator<'a> {
+    type Item = EscapeSequence<'a>;
+    fn next(&mut self) -> Option<Self::Item> {
+        use EscapeSequenceOffsets::*;
+        self.offset_iter.next().map(|offsets| match offsets {
+            Unknown { start, end } => EscapeSequence::Unknown(&self.text[start..end]),
+            Text { start, end } => EscapeSequence::Text(&self.text[start..end]),
+            NF {
+                start_sequence,
+                start,
+                end,
+            } => EscapeSequence::NF {
+                raw_sequence: &self.text[start_sequence..end],
+                nf_sequence: &self.text[start..end],
+            },
+            OSC {
+                start_sequence,
+                start_command,
+                start_terminator,
+                end,
+            } => EscapeSequence::OSC {
+                raw_sequence: &self.text[start_sequence..end],
+                command: &self.text[start_command..start_terminator],
+                terminator: &self.text[start_terminator..end],
+            },
+            CSI {
+                start_sequence,
+                start_parameters,
+                start_intermediates,
+                start_final_byte,
+                end,
+            } => EscapeSequence::CSI {
+                raw_sequence: &self.text[start_sequence..end],
+                parameters: &self.text[start_parameters..start_intermediates],
+                intermediates: &self.text[start_intermediates..start_final_byte],
+                final_byte: &self.text[start_final_byte..end],
+            },
+        })
+    }
+}
+
+/// A parsed ANSI/VT100 escape sequence.
+#[derive(Debug, PartialEq)]
+pub enum EscapeSequence<'a> {
+    Text(&'a str),
+    Unknown(&'a str),
+    NF {
+        raw_sequence: &'a str,
+        nf_sequence: &'a str,
+    },
+    OSC {
+        raw_sequence: &'a str,
+        command: &'a str,
+        terminator: &'a str,
+    },
+    CSI {
+        raw_sequence: &'a str,
+        parameters: &'a str,
+        intermediates: &'a str,
+        final_byte: &'a str,
+    },
+}
+
+impl<'a> EscapeSequence<'a> {
+    pub fn raw(&self) -> &'a str {
+        use EscapeSequence::*;
+        match *self {
+            Text(raw) => raw,
+            Unknown(raw) => raw,
+            NF { raw_sequence, .. } => raw_sequence,
+            OSC { raw_sequence, .. } => raw_sequence,
+            CSI { raw_sequence, .. } => raw_sequence,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::vscreen::{
+        EscapeSequence, EscapeSequenceIterator, EscapeSequenceOffsets,
+        EscapeSequenceOffsetsIterator,
+    };
+
+    #[test]
+    fn test_escape_sequence_offsets_iterator_parses_text() {
+        let mut iter = EscapeSequenceOffsetsIterator::new("text");
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::Text { start: 0, end: 4 })
+        );
+    }
+
+    #[test]
+    fn test_escape_sequence_offsets_iterator_parses_text_stops_at_esc() {
+        let mut iter = EscapeSequenceOffsetsIterator::new("text\x1B[ming");
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::Text { start: 0, end: 4 })
+        );
+    }
+
+    #[test]
+    fn test_escape_sequence_offsets_iterator_parses_osc_with_bel() {
+        let mut iter = EscapeSequenceOffsetsIterator::new("\x1B]abc\x07");
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::OSC {
+                start_sequence: 0,
+                start_command: 2,
+                start_terminator: 5,
+                end: 6,
+            })
+        );
+    }
+
+    #[test]
+    fn test_escape_sequence_offsets_iterator_parses_osc_with_st() {
+        let mut iter = EscapeSequenceOffsetsIterator::new("\x1B]abc\x1B\\");
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::OSC {
+                start_sequence: 0,
+                start_command: 2,
+                start_terminator: 5,
+                end: 7,
+            })
+        );
+    }
+
+    #[test]
+    fn test_escape_sequence_offsets_iterator_parses_osc_thats_broken() {
+        let mut iter = EscapeSequenceOffsetsIterator::new("\x1B]ab");
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::OSC {
+                start_sequence: 0,
+                start_command: 2,
+                start_terminator: 4,
+                end: 4,
+            })
+        );
+    }
+
+    #[test]
+    fn test_escape_sequence_offsets_iterator_parses_csi() {
+        let mut iter = EscapeSequenceOffsetsIterator::new("\x1B[m");
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::CSI {
+                start_sequence: 0,
+                start_parameters: 2,
+                start_intermediates: 2,
+                start_final_byte: 2,
+                end: 3
+            })
+        );
+    }
+
+    #[test]
+    fn test_escape_sequence_offsets_iterator_parses_csi_with_parameters() {
+        let mut iter = EscapeSequenceOffsetsIterator::new("\x1B[1;34m");
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::CSI {
+                start_sequence: 0,
+                start_parameters: 2,
+                start_intermediates: 6,
+                start_final_byte: 6,
+                end: 7
+            })
+        );
+    }
+
+    #[test]
+    fn test_escape_sequence_offsets_iterator_parses_csi_with_intermediates() {
+        let mut iter = EscapeSequenceOffsetsIterator::new("\x1B[$m");
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::CSI {
+                start_sequence: 0,
+                start_parameters: 2,
+                start_intermediates: 2,
+                start_final_byte: 3,
+                end: 4
+            })
+        );
+    }
+
+    #[test]
+    fn test_escape_sequence_offsets_iterator_parses_csi_with_parameters_and_intermediates() {
+        let mut iter = EscapeSequenceOffsetsIterator::new("\x1B[1$m");
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::CSI {
+                start_sequence: 0,
+                start_parameters: 2,
+                start_intermediates: 3,
+                start_final_byte: 4,
+                end: 5
+            })
+        );
+    }
+
+    #[test]
+    fn test_escape_sequence_offsets_iterator_parses_csi_thats_broken() {
+        let mut iter = EscapeSequenceOffsetsIterator::new("\x1B[");
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::CSI {
+                start_sequence: 0,
+                start_parameters: 2,
+                start_intermediates: 2,
+                start_final_byte: 2,
+                end: 2
+            })
+        );
+
+        let mut iter = EscapeSequenceOffsetsIterator::new("\x1B[1");
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::CSI {
+                start_sequence: 0,
+                start_parameters: 2,
+                start_intermediates: 3,
+                start_final_byte: 3,
+                end: 3
+            })
+        );
+
+        let mut iter = EscapeSequenceOffsetsIterator::new("\x1B[1$");
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::CSI {
+                start_sequence: 0,
+                start_parameters: 2,
+                start_intermediates: 3,
+                start_final_byte: 4,
+                end: 4
+            })
+        );
+    }
+
+    #[test]
+    fn test_escape_sequence_offsets_iterator_parses_nf() {
+        let mut iter = EscapeSequenceOffsetsIterator::new("\x1B($0");
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::NF {
+                start_sequence: 0,
+                start: 1,
+                end: 4
+            })
+        );
+    }
+
+    #[test]
+    fn test_escape_sequence_offsets_iterator_parses_nf_thats_broken() {
+        let mut iter = EscapeSequenceOffsetsIterator::new("\x1B(");
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::NF {
+                start_sequence: 0,
+                start: 1,
+                end: 1
+            })
+        );
+    }
+
+    #[test]
+    fn test_escape_sequence_offsets_iterator_iterates() {
+        let mut iter = EscapeSequenceOffsetsIterator::new("text\x1B[33m\x1B]OSC\x07\x1B(0");
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::Text { start: 0, end: 4 })
+        );
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::CSI {
+                start_sequence: 4,
+                start_parameters: 6,
+                start_intermediates: 8,
+                start_final_byte: 8,
+                end: 9
+            })
+        );
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::OSC {
+                start_sequence: 9,
+                start_command: 11,
+                start_terminator: 14,
+                end: 15
+            })
+        );
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequenceOffsets::NF {
+                start_sequence: 15,
+                start: 16,
+                end: 18
+            })
+        );
+        assert_eq!(iter.next(), None);
+    }
+
+    #[test]
+    fn test_escape_sequence_iterator_iterates() {
+        let mut iter = EscapeSequenceIterator::new("text\x1B[33m\x1B]OSC\x07\x1B]OSC\x1B\\\x1B(0");
+        assert_eq!(iter.next(), Some(EscapeSequence::Text("text")));
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequence::CSI {
+                raw_sequence: "\x1B[33m",
+                parameters: "33",
+                intermediates: "",
+                final_byte: "m",
+            })
+        );
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequence::OSC {
+                raw_sequence: "\x1B]OSC\x07",
+                command: "OSC",
+                terminator: "\x07",
+            })
+        );
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequence::OSC {
+                raw_sequence: "\x1B]OSC\x1B\\",
+                command: "OSC",
+                terminator: "\x1B\\",
+            })
+        );
+        assert_eq!(
+            iter.next(),
+            Some(EscapeSequence::NF {
+                raw_sequence: "\x1B(0",
+                nf_sequence: "(0",
+            })
+        );
+        assert_eq!(iter.next(), None);
+    }
+}

diff --git a/tests/examples/regression_tests/issue_2541.txt b/tests/examples/regression_tests/issue_2541.txt
new file mode 100644
index 0000000000..1059b94ec6
--- /dev/null
+++ b/tests/examples/regression_tests/issue_2541.txt
@@ -0,0 +1,1 @@
+]8;;http://example.com\This is a link]8;;\n
\ No newline at end of file
diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs
index 437ae8e787..ecc37ed74b 100644
--- a/tests/integration_tests.rs
+++ b/tests/integration_tests.rs
@@ -1163,6 +1163,20 @@ fn bom_stripped_when_no_color_and_not_loop_through() {
         );
 }
 
+// Regression test for https://github.com/sharkdp/bat/issues/2541
+#[test]
+fn no_broken_osc_emit_with_line_wrapping() {
+    bat()
+        .arg("--color=always")
+        .arg("--decorations=never")
+        .arg("--wrap=character")
+        .arg("--terminal-width=40")
+        .arg("regression_tests/issue_2541.txt")
+        .assert()
+        .success()
+        .stdout(predicate::function(|s: &str| s.lines().count() == 1));
+}
+
 #[test]
 fn can_print_file_named_cache() {
     bat_with_config()
@@ -1919,6 +1933,62 @@ fn ansi_passthrough_emit() {
     }
 }
 
+// Ensure that a simple ANSI sequence passthrough is emitted properly on wrapped lines.
+// This also helps ensure that escape sequences are counted as part of the visible characters when wrapping.
+#[test]
+fn ansi_sgr_emitted_when_wrapped() {
+    bat()
+        .arg("--paging=never")
+        .arg("--color=never")
+        .arg("--terminal-width=20")
+        .arg("--wrap=character")
+        .arg("--decorations=always")
+        .arg("--style=plain")
+        .write_stdin("\x1B[33mColor...............Also color.\n")
+        .assert()
+        .success()
+        .stdout("\x1B[33m\x1B[33mColor...............\n\x1B[33mAlso color.\n")
+        // FIXME:              ~~~~~~~~ should not be emitted twice.
+        .stderr("");
+}
+
+// Ensure that a simple ANSI sequence passthrough is emitted properly on wrapped lines.
+// This also helps ensure that escape sequences are counted as part of the visible characters when wrapping.
+#[test]
+fn ansi_hyperlink_emitted_when_wrapped() {
+    bat()
+        .arg("--paging=never")
+        .arg("--color=never")
+        .arg("--terminal-width=20")
+        .arg("--wrap=character")
+        .arg("--decorations=always")
+        .arg("--style=plain")
+        .write_stdin("\x1B]8;;http://example.com/\x1B\\Hyperlinks..........Wrap across lines.\n")
+        .assert()
+        .success()
+        .stdout("\x1B]8;;http://example.com/\x1B\\\x1B]8;;http://example.com/\x1B\\Hyperlinks..........\x1B]8;;\x1B\\\n\x1B]8;;http://example.com/\x1B\\Wrap across lines.\n")
+        // FIXME:                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ should not be emitted twice.
+        .stderr("");
+}
+
+// Ensure that multiple ANSI sequence SGR attributes are combined when emitted on wrapped lines.
+#[test]
+fn ansi_sgr_joins_attributes_when_wrapped() {
+    bat()
+            .arg("--paging=never")
+            .arg("--color=never")
+            .arg("--terminal-width=20")
+            .arg("--wrap=character")
+            .arg("--decorations=always")
+            .arg("--style=plain")
+            .write_stdin("\x1B[33mColor. \x1B[1mBold.........Also bold and color.\n")
+            .assert()
+            .success()
+            .stdout("\x1B[33m\x1B[33mColor. \x1B[1m\x1B[33m\x1B[1mBold.........\n\x1B[33m\x1B[1mAlso bold and color.\n")
+            // FIXME:              ~~~~~~~~       ~~~~~~~~~~~~~~~ should not be emitted twice.
+            .stderr("");
+}
+
 #[test]
 fn ignored_suffix_arg() {
     bat()

{"header": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_csi_thats_broken": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "grid_numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes_header_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes_grid": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "no_duplicate_extensions": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "full": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_iterates": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_osc_with_st": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_csi_with_parameters": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "syntaxes": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "grid_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_csi": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "header_numbers_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes_header": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "grid_header": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_nf_thats_broken": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "plain": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_iterator_iterates": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "changes_grid_header": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "no_broken_osc_emit_with_line_wrapping": {"run": "NONE", "test": "FAIL", "fix": "PASS"}, "changes_grid_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes_grid_header_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "header_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "header_numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_text": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "grid_header_numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "ansi_hyperlink_emitted_when_wrapped": {"run": "NONE", "test": "FAIL", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_csi_with_parameters_and_intermediates": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "changes_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_osc_with_bel": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "changes_grid_header_numbers_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_csi_with_intermediates": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_text_stops_at_esc": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "grid_header_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes_grid_header_numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_osc_thats_broken": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "changes_header_numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes_grid_numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "grid": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes_numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_nf": {"run": "NONE", "test": "NONE", "fix": "PASS"}}
{"highlighting_independant_from_map_syntax_case": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_wrapping_when_auto": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "basic": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "tabs_4": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "assets::tests::syntax_detection_with_custom_mapping": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "no_line_wrapping_with_s_flag": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "tabs_4_env_overrides_config": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "filename_multiple_err": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_ranges_open_high": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range_last_3": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "theme_arg_overrides_env": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "enable_pager_if_pp_flag_comes_before_paging": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "syntax_mapping::tests::all_fixed_builtin_mappings_can_compile": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "accepts_no_custom_assets_arg": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "input::utf16le": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "short_help": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "pager_disable": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "filename_stdin_binary": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "tabs_8_wrapped": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_numbers": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "map_syntax_and_ignored_suffix_work_together": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "theme_arg_overrides_env_withconfig": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "assets::tests::syntax_detection_well_defined_mapping_for_duplicate_extensions": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "header_full_basic": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "concatenate_empty_last": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "show_all_extends_tab_markers_to_next_tabstop_width_8": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "all_jobs_not_missing_any_jobs": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "bom_not_stripped_in_loop_through_mode": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "snip": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "header_padding": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_parse_fail": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "concatenate": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "show_all_mode": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "filename_stdin": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "can_print_file_starting_with_cache": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "env_var_pager_value_bat": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_parse_partial_max": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "pager_arg_override_env_withconfig": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "pager_env_pager_nooverride_config": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "concatenate_single_line": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "no_first_line_fallback_when_mapping_to_invalid_syntax": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_parse_minus_success": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "stdin": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range_first_two": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "tabs_numbers": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "basic_io_cycle": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "file_with_invalid_utf8_filename": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "empty_file_cycle_is_ok": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_parse_plus_fail": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "assets::build_assets::acknowledgements::tests::test_append_to_acknowledgements_adds_newline_if_missing": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "show_all_with_unicode": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "fail_directory": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "less::test_parse_less_version_wrong_program": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_ranges_open_low": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "assets::tests::syntax_detection_invalid_utf8": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "ansi_passthrough_emit": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "empty_file_leads_to_empty_output_with_grid_enabled": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "config_location_from_bat_config_dir_variable": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "pager_value_bat": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "pager_overwrite": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "assets::tests::syntax_detection_is_case_insensitive": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "config::quotes": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "pager_arg_override_env_noconfig": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "tabs_4_arg_overrides_env": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "less::test_parse_less_version_581_2": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "acknowledgements": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "concatenate_single_line_empty": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "tabs_passthrough": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_ranges_none": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "preprocessor::test_try_parse_utf8_char": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "syntax_mapping::tests::custom_mappings_precedence": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "config::empty": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "concatenate_empty_both": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range_multiple": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "env_var_bat_paging": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "theme_env_overrides_config": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "input::basic": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "syntax_mapping::tests::custom_mappings_work": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "long_help": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "filename_multiple_ok": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_parse_minus_fail": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "config::default_config_should_highlight_no_lines": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "no_wrapping_with_chop_long_lines": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "empty_file_leads_to_empty_output_with_rule_enabled": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "pager_most_from_bat_pager_env_var": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_ranges_advanced": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_parse_minus_edge_cases_success": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range_2_3": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "ansi_highlight_underline": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "pager_more": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "can_print_file_named_cache_with_additional_argument": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "tabs_8": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "header_full_padding": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "assets::tests::syntax_detection_with_extension_mapping_to_unknown": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "env_var_bat_pager_value_bat": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "cache_build": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "pager_most_from_pager_env_var": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "config::single": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "bom_stripped_when_colored_output": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "clap_app::verify_app": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "concatenate_empty_first": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_parse_plus_overflow": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_parse_full": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "header_env_basic": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "assets::tests::syntax_detection_stdin_filename": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "tabs_4_wrapped": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "tabs_4_arg_overrides_env_noconfig": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "less::test_parse_less_version_529": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "plain_mode_does_not_add_nonexisting_newline": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "ignored_suffix_arg": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "does_not_print_unwanted_file_named_cache": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "concatenate_empty_between": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_parse_partial_min": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_parse_plus": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "header_padding_rule": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "header_full_padding_rule": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "config::default_config_should_include_all_lines": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "enable_pager_if_disable_paging_flag_comes_before_paging": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "syntax_mapping::tests::custom_mappings_override_builtin": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "header_arg_overrides_env": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "pager_env_bat_pager_override_config": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "pager_basic_arg": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "header_default_is_default": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "do_not_detect_different_syntax_for_stdin_and_files": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "concatenate_stdin": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "syntax_mapping::tests::builtin_mappings_matcher_only_compile_once": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "less::test_parse_less_version_551": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_parse_single": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "pager_basic": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "header_binary": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "header_very_narrow_terminal": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "no_line_wrapping_when_set_to_never": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "assets::tests::syntax_detection_same_for_inputkinds": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_ranges_all": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "config::multi_line": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "tabs_passthrough_wrapped": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "less::test_parse_less_version_busybox": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "pager_most_with_arg": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "line_range::test_ranges_simple": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "alias_pager_disable_long_overrides_short": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "assets::tests::syntax_detection_for_symlinked_file": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "cache_clear": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "diagnostic_sanity_check": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "grid_for_file_without_newline": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "pager_failed_to_parse": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "utf16": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "less::test_parse_less_version_487": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "disable_pager_if_pp_flag_comes_after_paging": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "unicode_wrap": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "all_global_git_config_locations_syntax_mapping_work": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "header_default": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "bom_stripped_when_no_color_and_not_loop_through": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "config::comments": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "pager_most_from_pager_arg": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "config_location_when_generating": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "syntax_mapping::ignored_suffixes::internal_suffixes": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "config_read_arguments_from_file": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "syntax_mapping::tests::builtin_mappings_work": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "highlighting_is_skipped_on_long_lines": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "ansi_sgr_emitted_when_wrapped": {"run": "NONE", "test": "PASS", "fix": "PASS"}, "assets::tests::syntax_detection_basic": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "less::test_parse_less_version_invalid_utf_8": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "config::multiple": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "assets::tests::syntax_detection_first_line": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "grid_overrides_rule": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "header_basic": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "disable_pager_if_disable_paging_flag_comes_after_paging": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "no_paging_short_arg": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "concatenate_empty_first_and_last": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "alias_pager_disable": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "no_pager_arg": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "no_paging_arg": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "stdin_to_stdout_cycle": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "fail_non_existing": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "ansi_sgr_joins_attributes_when_wrapped": {"run": "NONE", "test": "PASS", "fix": "PASS"}, "assets::build_assets::acknowledgements::tests::test_normalize_license_text_with_windows_line_endings": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "do_not_panic_regression_tests": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "can_print_file_named_cache": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "first_file_cyclic_is_ok": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "config_location_test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "do_not_exit_directory": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "syntax_mapping::ignored_suffixes::external_suffixes": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "show_all_with_caret_notation": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "assets::build_assets::acknowledgements::tests::test_normalize_license_text": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "no_args_doesnt_break": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "header_full_binary": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "show_all_extends_tab_markers_to_next_tabstop": {"run": "PASS", "test": "PASS", "fix": "PASS"}}
{"no_broken_osc_emit_with_line_wrapping": {"run": "NONE", "test": "FAIL", "fix": "PASS"}, "ansi_hyperlink_emitted_when_wrapped": {"run": "NONE", "test": "FAIL", "fix": "PASS"}}
{"header": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_csi_thats_broken": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "grid_numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes_header_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes_grid": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "no_duplicate_extensions": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "full": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_iterates": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_osc_with_st": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_csi_with_parameters": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "syntaxes": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "grid_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_csi": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "header_numbers_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes_header": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "grid_header": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_nf_thats_broken": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "plain": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_iterator_iterates": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "changes_grid_header": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes_grid_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes_grid_header_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "header_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "header_numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_text": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "grid_header_numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_csi_with_parameters_and_intermediates": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "changes_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_osc_with_bel": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "changes_grid_header_numbers_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_csi_with_intermediates": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_text_stops_at_esc": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "grid_header_rule": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes_grid_header_numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_osc_thats_broken": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "changes_header_numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes_grid_numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "grid": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "changes_numbers": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "vscreen::tests::test_escape_sequence_offsets_iterator_parses_nf": {"run": "NONE", "test": "NONE", "fix": "PASS"}}
{"passed_count": 214, "failed_count": 0, "skipped_count": 3, "passed_tests": ["highlighting_independant_from_map_syntax_case", "line_wrapping_when_auto", "basic", "tabs_4", "assets::tests::syntax_detection_with_custom_mapping", "no_line_wrapping_with_s_flag", "tabs_4_env_overrides_config", "header", "filename_multiple_err", "line_range::test_ranges_open_high", "line_range_last_3", "theme_arg_overrides_env", "enable_pager_if_pp_flag_comes_before_paging", "syntax_mapping::tests::all_fixed_builtin_mappings_can_compile", "accepts_no_custom_assets_arg", "input::utf16le", "short_help", "grid_numbers", "changes_header_rule", "pager_disable", "filename_stdin_binary", "tabs_8_wrapped", "line_numbers", "map_syntax_and_ignored_suffix_work_together", "theme_arg_overrides_env_withconfig", "assets::tests::syntax_detection_well_defined_mapping_for_duplicate_extensions", "header_full_basic", "concatenate_empty_last", "show_all_extends_tab_markers_to_next_tabstop_width_8", "all_jobs_not_missing_any_jobs", "bom_not_stripped_in_loop_through_mode", "snip", "header_padding", "line_range::test_parse_fail", "concatenate", "show_all_mode", "filename_stdin", "can_print_file_starting_with_cache", "env_var_pager_value_bat", "line_range::test_parse_partial_max", "pager_arg_override_env_withconfig", "changes", "pager_env_pager_nooverride_config", "changes_grid", "concatenate_single_line", "no_first_line_fallback_when_mapping_to_invalid_syntax", "line_range::test_parse_minus_success", "stdin", "line_range_first_two", "tabs_numbers", "basic_io_cycle", "file_with_invalid_utf8_filename", "empty_file_cycle_is_ok", "line_range::test_parse_plus_fail", "assets::build_assets::acknowledgements::tests::test_append_to_acknowledgements_adds_newline_if_missing", "show_all_with_unicode", "fail_directory", "less::test_parse_less_version_wrong_program", "line_range::test_ranges_open_low", "assets::tests::syntax_detection_invalid_utf8", "no_duplicate_extensions", "full", "ansi_passthrough_emit", "empty_file_leads_to_empty_output_with_grid_enabled", "config_location_from_bat_config_dir_variable", "pager_value_bat", "pager_overwrite", "assets::tests::syntax_detection_is_case_insensitive", "config::quotes", "pager_arg_override_env_noconfig", "tabs_4_arg_overrides_env", "less::test_parse_less_version_581_2", "acknowledgements", "concatenate_single_line_empty", "tabs_passthrough", "line_range::test_ranges_none", "preprocessor::test_try_parse_utf8_char", "syntax_mapping::tests::custom_mappings_precedence", "config::empty", "syntaxes", "grid_rule", "concatenate_empty_both", "line_range_multiple", "env_var_bat_paging", "theme_env_overrides_config", "input::basic", "header_numbers_rule", "syntax_mapping::tests::custom_mappings_work", "long_help", "filename_multiple_ok", "line_range::test_parse_minus_fail", "config::default_config_should_highlight_no_lines", "no_wrapping_with_chop_long_lines", "empty_file_leads_to_empty_output_with_rule_enabled", "pager_most_from_bat_pager_env_var", "line_range::test_ranges_advanced", "line_range::test_parse_minus_edge_cases_success", "line_range_2_3", "changes_header", "ansi_highlight_underline", "pager_more", "grid_header", "can_print_file_named_cache_with_additional_argument", "tabs_8", "header_full_padding", "assets::tests::syntax_detection_with_extension_mapping_to_unknown", "env_var_bat_pager_value_bat", "plain", "cache_build", "pager_most_from_pager_env_var", "config::single", "bom_stripped_when_colored_output", "clap_app::verify_app", "concatenate_empty_first", "line_range::test_parse_plus_overflow", "line_range::test_parse_full", "header_env_basic", "assets::tests::syntax_detection_stdin_filename", "tabs_4_wrapped", "tabs_4_arg_overrides_env_noconfig", "less::test_parse_less_version_529", "plain_mode_does_not_add_nonexisting_newline", "ignored_suffix_arg", "does_not_print_unwanted_file_named_cache", "concatenate_empty_between", "line_range::test_parse_partial_min", "line_range::test_parse_plus", "header_padding_rule", "changes_grid_header", "header_full_padding_rule", "changes_grid_rule", "config::default_config_should_include_all_lines", "enable_pager_if_disable_paging_flag_comes_before_paging", "syntax_mapping::tests::custom_mappings_override_builtin", "header_arg_overrides_env", "pager_env_bat_pager_override_config", "pager_basic_arg", "header_default_is_default", "changes_grid_header_rule", "do_not_detect_different_syntax_for_stdin_and_files", "header_rule", "concatenate_stdin", "rule", "syntax_mapping::tests::builtin_mappings_matcher_only_compile_once", "header_numbers", "less::test_parse_less_version_551", "line_range::test_parse_single", "pager_basic", "grid_header_numbers", "header_binary", "header_very_narrow_terminal", "no_line_wrapping_when_set_to_never", "changes_rule", "assets::tests::syntax_detection_same_for_inputkinds", "line_range::test_ranges_all", "config::multi_line", "changes_grid_header_numbers_rule", "tabs_passthrough_wrapped", "less::test_parse_less_version_busybox", "pager_most_with_arg", "line_range::test_ranges_simple", "alias_pager_disable_long_overrides_short", "assets::tests::syntax_detection_for_symlinked_file", "cache_clear", "diagnostic_sanity_check", "grid_for_file_without_newline", "pager_failed_to_parse", "utf16", "grid_header_rule", "less::test_parse_less_version_487", "numbers", "disable_pager_if_pp_flag_comes_after_paging", "unicode_wrap", "all_global_git_config_locations_syntax_mapping_work", "header_default", "bom_stripped_when_no_color_and_not_loop_through", "config::comments", "pager_most_from_pager_arg", "changes_grid_header_numbers", "config_location_when_generating", "syntax_mapping::ignored_suffixes::internal_suffixes", "config_read_arguments_from_file", "syntax_mapping::tests::builtin_mappings_work", "highlighting_is_skipped_on_long_lines", "assets::tests::syntax_detection_basic", "less::test_parse_less_version_invalid_utf_8", "config::multiple", "assets::tests::syntax_detection_first_line", "grid_overrides_rule", "header_basic", "disable_pager_if_disable_paging_flag_comes_after_paging", "changes_header_numbers", "no_paging_short_arg", "concatenate_empty_first_and_last", "alias_pager_disable", "no_pager_arg", "no_paging_arg", "stdin_to_stdout_cycle", "fail_non_existing", "changes_grid_numbers", "grid", "changes_numbers", "assets::build_assets::acknowledgements::tests::test_normalize_license_text_with_windows_line_endings", "do_not_panic_regression_tests", "can_print_file_named_cache", "first_file_cyclic_is_ok", "config_location_test", "do_not_exit_directory", "syntax_mapping::ignored_suffixes::external_suffixes", "show_all_with_caret_notation", "assets::build_assets::acknowledgements::tests::test_normalize_license_text", "no_args_doesnt_break", "header_full_binary", "show_all_extends_tab_markers_to_next_tabstop"], "failed_tests": [], "skipped_tests": ["config_overrides_system_config", "use_systemwide_config", "all_themes_are_present"]}
{"passed_count": 187, "failed_count": 2, "skipped_count": 1, "passed_tests": ["highlighting_independant_from_map_syntax_case", "line_wrapping_when_auto", "basic", "tabs_4", "assets::tests::syntax_detection_with_custom_mapping", "no_line_wrapping_with_s_flag", "tabs_4_env_overrides_config", "filename_multiple_err", "line_range::test_ranges_open_high", "line_range_last_3", "theme_arg_overrides_env", "enable_pager_if_pp_flag_comes_before_paging", "syntax_mapping::tests::all_fixed_builtin_mappings_can_compile", "accepts_no_custom_assets_arg", "input::utf16le", "short_help", "pager_disable", "filename_stdin_binary", "tabs_8_wrapped", "line_numbers", "map_syntax_and_ignored_suffix_work_together", "theme_arg_overrides_env_withconfig", "assets::tests::syntax_detection_well_defined_mapping_for_duplicate_extensions", "header_full_basic", "concatenate_empty_last", "show_all_extends_tab_markers_to_next_tabstop_width_8", "all_jobs_not_missing_any_jobs", "bom_not_stripped_in_loop_through_mode", "snip", "header_padding", "line_range::test_parse_fail", "concatenate", "can_print_file_starting_with_cache", "show_all_mode", "filename_stdin", "env_var_pager_value_bat", "line_range::test_parse_partial_max", "pager_arg_override_env_withconfig", "pager_env_pager_nooverride_config", "concatenate_single_line", "no_first_line_fallback_when_mapping_to_invalid_syntax", "line_range::test_parse_minus_success", "stdin", "line_range_first_two", "tabs_numbers", "fail_directory", "file_with_invalid_utf8_filename", "empty_file_cycle_is_ok", "assets::build_assets::acknowledgements::tests::test_append_to_acknowledgements_adds_newline_if_missing", "line_range::test_parse_plus_fail", "show_all_with_unicode", "basic_io_cycle", "less::test_parse_less_version_wrong_program", "line_range::test_ranges_open_low", "assets::tests::syntax_detection_invalid_utf8", "ansi_passthrough_emit", "empty_file_leads_to_empty_output_with_grid_enabled", "config_location_from_bat_config_dir_variable", "pager_value_bat", "pager_overwrite", "assets::tests::syntax_detection_is_case_insensitive", "config::quotes", "pager_arg_override_env_noconfig", "tabs_4_arg_overrides_env", "less::test_parse_less_version_581_2", "acknowledgements", "concatenate_single_line_empty", "tabs_passthrough", "line_range::test_ranges_none", "preprocessor::test_try_parse_utf8_char", "syntax_mapping::tests::custom_mappings_precedence", "config::empty", "concatenate_empty_both", "line_range_multiple", "env_var_bat_paging", "theme_env_overrides_config", "input::basic", "syntax_mapping::tests::custom_mappings_work", "long_help", "filename_multiple_ok", "line_range::test_parse_minus_fail", "config::default_config_should_highlight_no_lines", "no_wrapping_with_chop_long_lines", "pager_most_from_bat_pager_env_var", "empty_file_leads_to_empty_output_with_rule_enabled", "line_range::test_ranges_advanced", "line_range::test_parse_minus_edge_cases_success", "line_range_2_3", "ansi_highlight_underline", "pager_more", "can_print_file_named_cache_with_additional_argument", "tabs_8", "header_full_padding", "assets::tests::syntax_detection_with_extension_mapping_to_unknown", "env_var_bat_pager_value_bat", "cache_build", "pager_most_from_pager_env_var", "config::single", "bom_stripped_when_colored_output", "clap_app::verify_app", "concatenate_empty_first", "line_range::test_parse_plus_overflow", "line_range::test_parse_full", "header_env_basic", "assets::tests::syntax_detection_stdin_filename", "tabs_4_wrapped", "tabs_4_arg_overrides_env_noconfig", "less::test_parse_less_version_529", "plain_mode_does_not_add_nonexisting_newline", "ignored_suffix_arg", "does_not_print_unwanted_file_named_cache", "concatenate_empty_between", "line_range::test_parse_partial_min", "line_range::test_parse_plus", "header_padding_rule", "header_full_padding_rule", "config::default_config_should_include_all_lines", "enable_pager_if_disable_paging_flag_comes_before_paging", "syntax_mapping::tests::custom_mappings_override_builtin", "header_arg_overrides_env", "pager_env_bat_pager_override_config", "pager_basic_arg", "header_default_is_default", "do_not_detect_different_syntax_for_stdin_and_files", "concatenate_stdin", "syntax_mapping::tests::builtin_mappings_matcher_only_compile_once", "less::test_parse_less_version_551", "line_range::test_parse_single", "pager_basic", "header_binary", "header_very_narrow_terminal", "no_line_wrapping_when_set_to_never", "assets::tests::syntax_detection_same_for_inputkinds", "line_range::test_ranges_all", "config::multi_line", "tabs_passthrough_wrapped", "less::test_parse_less_version_busybox", "pager_most_with_arg", "line_range::test_ranges_simple", "alias_pager_disable_long_overrides_short", "assets::tests::syntax_detection_for_symlinked_file", "cache_clear", "grid_for_file_without_newline", "diagnostic_sanity_check", "pager_failed_to_parse", "utf16", "less::test_parse_less_version_487", "disable_pager_if_pp_flag_comes_after_paging", "unicode_wrap", "all_global_git_config_locations_syntax_mapping_work", "header_default", "bom_stripped_when_no_color_and_not_loop_through", "config::comments", "pager_most_from_pager_arg", "config_location_when_generating", "syntax_mapping::ignored_suffixes::internal_suffixes", "config_read_arguments_from_file", "syntax_mapping::tests::builtin_mappings_work", "ansi_sgr_emitted_when_wrapped", "highlighting_is_skipped_on_long_lines", "assets::tests::syntax_detection_basic", "less::test_parse_less_version_invalid_utf_8", "config::multiple", "assets::tests::syntax_detection_first_line", "grid_overrides_rule", "header_basic", "disable_pager_if_disable_paging_flag_comes_after_paging", "alias_pager_disable", "no_paging_short_arg", "concatenate_empty_first_and_last", "no_pager_arg", "no_paging_arg", "fail_non_existing", "stdin_to_stdout_cycle", "ansi_sgr_joins_attributes_when_wrapped", "assets::build_assets::acknowledgements::tests::test_normalize_license_text_with_windows_line_endings", "do_not_panic_regression_tests", "can_print_file_named_cache", "first_file_cyclic_is_ok", "config_location_test", "do_not_exit_directory", "syntax_mapping::ignored_suffixes::external_suffixes", "show_all_with_caret_notation", "assets::build_assets::acknowledgements::tests::test_normalize_license_text", "no_args_doesnt_break", "header_full_binary", "show_all_extends_tab_markers_to_next_tabstop"], "failed_tests": ["ansi_hyperlink_emitted_when_wrapped", "no_broken_osc_emit_with_line_wrapping"], "skipped_tests": ["all_themes_are_present"]}
{"passed_count": 232, "failed_count": 0, "skipped_count": 3, "passed_tests": ["highlighting_independant_from_map_syntax_case", "line_wrapping_when_auto", "basic", "tabs_4", "assets::tests::syntax_detection_with_custom_mapping", "no_line_wrapping_with_s_flag", "tabs_4_env_overrides_config", "header", "vscreen::tests::test_escape_sequence_offsets_iterator_parses_csi_thats_broken", "filename_multiple_err", "line_range::test_ranges_open_high", "line_range_last_3", "theme_arg_overrides_env", "enable_pager_if_pp_flag_comes_before_paging", "syntax_mapping::tests::all_fixed_builtin_mappings_can_compile", "accepts_no_custom_assets_arg", "input::utf16le", "short_help", "grid_numbers", "changes_header_rule", "pager_disable", "filename_stdin_binary", "tabs_8_wrapped", "line_numbers", "map_syntax_and_ignored_suffix_work_together", "theme_arg_overrides_env_withconfig", "assets::tests::syntax_detection_well_defined_mapping_for_duplicate_extensions", "header_full_basic", "concatenate_empty_last", "show_all_extends_tab_markers_to_next_tabstop_width_8", "all_jobs_not_missing_any_jobs", "bom_not_stripped_in_loop_through_mode", "snip", "header_padding", "line_range::test_parse_fail", "concatenate", "can_print_file_starting_with_cache", "show_all_mode", "filename_stdin", "env_var_pager_value_bat", "line_range::test_parse_partial_max", "pager_arg_override_env_withconfig", "changes", "pager_env_pager_nooverride_config", "changes_grid", "concatenate_single_line", "no_first_line_fallback_when_mapping_to_invalid_syntax", "line_range::test_parse_minus_success", "stdin", "line_range_first_two", "tabs_numbers", "basic_io_cycle", "fail_directory", "empty_file_cycle_is_ok", "assets::build_assets::acknowledgements::tests::test_append_to_acknowledgements_adds_newline_if_missing", "line_range::test_parse_plus_fail", "show_all_with_unicode", "file_with_invalid_utf8_filename", "less::test_parse_less_version_wrong_program", "line_range::test_ranges_open_low", "assets::tests::syntax_detection_invalid_utf8", "no_duplicate_extensions", "full", "ansi_passthrough_emit", "empty_file_leads_to_empty_output_with_grid_enabled", "config_location_from_bat_config_dir_variable", "vscreen::tests::test_escape_sequence_offsets_iterator_iterates", "vscreen::tests::test_escape_sequence_offsets_iterator_parses_osc_with_st", "pager_value_bat", "pager_overwrite", "assets::tests::syntax_detection_is_case_insensitive", "config::quotes", "pager_arg_override_env_noconfig", "tabs_4_arg_overrides_env", "less::test_parse_less_version_581_2", "acknowledgements", "concatenate_single_line_empty", "tabs_passthrough", "line_range::test_ranges_none", "preprocessor::test_try_parse_utf8_char", "syntax_mapping::tests::custom_mappings_precedence", "vscreen::tests::test_escape_sequence_offsets_iterator_parses_csi_with_parameters", "config::empty", "syntaxes", "grid_rule", "concatenate_empty_both", "line_range_multiple", "env_var_bat_paging", "vscreen::tests::test_escape_sequence_offsets_iterator_parses_csi", "theme_env_overrides_config", "input::basic", "header_numbers_rule", "syntax_mapping::tests::custom_mappings_work", "long_help", "filename_multiple_ok", "line_range::test_parse_minus_fail", "config::default_config_should_highlight_no_lines", "no_wrapping_with_chop_long_lines", "empty_file_leads_to_empty_output_with_rule_enabled", "pager_most_from_bat_pager_env_var", "line_range::test_ranges_advanced", "line_range::test_parse_minus_edge_cases_success", "line_range_2_3", "changes_header", "ansi_highlight_underline", "pager_more", "grid_header", "can_print_file_named_cache_with_additional_argument", "tabs_8", "header_full_padding", "vscreen::tests::test_escape_sequence_offsets_iterator_parses_nf_thats_broken", "assets::tests::syntax_detection_with_extension_mapping_to_unknown", "env_var_bat_pager_value_bat", "plain", "cache_build", "pager_most_from_pager_env_var", "config::single", "bom_stripped_when_colored_output", "clap_app::verify_app", "concatenate_empty_first", "line_range::test_parse_plus_overflow", "line_range::test_parse_full", "header_env_basic", "assets::tests::syntax_detection_stdin_filename", "tabs_4_wrapped", "tabs_4_arg_overrides_env_noconfig", "less::test_parse_less_version_529", "vscreen::tests::test_escape_sequence_iterator_iterates", "ignored_suffix_arg", "plain_mode_does_not_add_nonexisting_newline", "does_not_print_unwanted_file_named_cache", "concatenate_empty_between", "line_range::test_parse_partial_min", "line_range::test_parse_plus", "header_padding_rule", "changes_grid_header", "header_full_padding_rule", "no_broken_osc_emit_with_line_wrapping", "changes_grid_rule", "config::default_config_should_include_all_lines", "enable_pager_if_disable_paging_flag_comes_before_paging", "syntax_mapping::tests::custom_mappings_override_builtin", "header_arg_overrides_env", "pager_env_bat_pager_override_config", "pager_basic_arg", "header_default_is_default", "changes_grid_header_rule", "do_not_detect_different_syntax_for_stdin_and_files", "header_rule", "concatenate_stdin", "rule", "syntax_mapping::tests::builtin_mappings_matcher_only_compile_once", "header_numbers", "less::test_parse_less_version_551", "vscreen::tests::test_escape_sequence_offsets_iterator_parses_text", "line_range::test_parse_single", "ansi_hyperlink_emitted_when_wrapped", "pager_basic", "grid_header_numbers", "header_binary", "header_very_narrow_terminal", "vscreen::tests::test_escape_sequence_offsets_iterator_parses_csi_with_parameters_and_intermediates", "no_line_wrapping_when_set_to_never", "changes_rule", "vscreen::tests::test_escape_sequence_offsets_iterator_parses_osc_with_bel", "assets::tests::syntax_detection_same_for_inputkinds", "line_range::test_ranges_all", "config::multi_line", "changes_grid_header_numbers_rule", "vscreen::tests::test_escape_sequence_offsets_iterator_parses_csi_with_intermediates", "tabs_passthrough_wrapped", "less::test_parse_less_version_busybox", "vscreen::tests::test_escape_sequence_offsets_iterator_parses_text_stops_at_esc", "pager_most_with_arg", "line_range::test_ranges_simple", "alias_pager_disable_long_overrides_short", "assets::tests::syntax_detection_for_symlinked_file", "cache_clear", "diagnostic_sanity_check", "grid_for_file_without_newline", "pager_failed_to_parse", "utf16", "grid_header_rule", "less::test_parse_less_version_487", "numbers", "disable_pager_if_pp_flag_comes_after_paging", "unicode_wrap", "all_global_git_config_locations_syntax_mapping_work", "header_default", "bom_stripped_when_no_color_and_not_loop_through", "config::comments", "pager_most_from_pager_arg", "changes_grid_header_numbers", "vscreen::tests::test_escape_sequence_offsets_iterator_parses_osc_thats_broken", "syntax_mapping::ignored_suffixes::internal_suffixes", "config_location_when_generating", "config_read_arguments_from_file", "syntax_mapping::tests::builtin_mappings_work", "ansi_sgr_emitted_when_wrapped", "highlighting_is_skipped_on_long_lines", "assets::tests::syntax_detection_basic", "less::test_parse_less_version_invalid_utf_8", "config::multiple", "assets::tests::syntax_detection_first_line", "grid_overrides_rule", "header_basic", "disable_pager_if_disable_paging_flag_comes_after_paging", "changes_header_numbers", "alias_pager_disable", "no_paging_short_arg", "concatenate_empty_first_and_last", "no_pager_arg", "no_paging_arg", "fail_non_existing", "stdin_to_stdout_cycle", "ansi_sgr_joins_attributes_when_wrapped", "changes_grid_numbers", "grid", "changes_numbers", "vscreen::tests::test_escape_sequence_offsets_iterator_parses_nf", "assets::build_assets::acknowledgements::tests::test_normalize_license_text_with_windows_line_endings", "do_not_panic_regression_tests", "first_file_cyclic_is_ok", "can_print_file_named_cache", "do_not_exit_directory", "config_location_test", "syntax_mapping::ignored_suffixes::external_suffixes", "show_all_with_caret_notation", "assets::build_assets::acknowledgements::tests::test_normalize_license_text", "no_args_doesnt_break", "header_full_binary", "show_all_extends_tab_markers_to_next_tabstop"], "failed_tests": [], "skipped_tests": ["config_overrides_system_config", "use_systemwide_config", "all_themes_are_present"]}