mirror of
				https://github.com/sharkdp/bat.git
				synced 2025-11-04 09:01:56 +00:00 
			
		
		
		
	Feature: Highlight non-printable characters
Adds a new `-A`/`--show-all` option (in analogy to GNU Linux `cat`s option) that highlights non-printable characters like space, tab or newline. This works in two steps: - **Preprocessing**: replace space by `•`, replace tab by `├──┤`, replace newline by ``, etc. - **Highlighting**: Use a newly written Sublime syntax to highlight these special symbols. Note: This feature is not technically a drop-in replacement for GNU `cat`s `--show-all` but it has the same purpose.
This commit is contained in:
		
							
								
								
									
										25
									
								
								assets/syntaxes/show-nonprintable.sublime-syntax
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								assets/syntaxes/show-nonprintable.sublime-syntax
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
%YAML 1.2
 | 
			
		||||
---
 | 
			
		||||
# http://www.sublimetext.com/docs/3/syntax.html
 | 
			
		||||
name: Highlight non-printables
 | 
			
		||||
file_extensions:
 | 
			
		||||
    - show-nonprintable
 | 
			
		||||
scope: whitespace
 | 
			
		||||
contexts:
 | 
			
		||||
  main:
 | 
			
		||||
    - match: "•"
 | 
			
		||||
      scope: support.function.show-nonprintable.space
 | 
			
		||||
    - match: "├─*┤"
 | 
			
		||||
      scope: constant.character.escape.show-nonprintable.tab
 | 
			
		||||
    - match: ""
 | 
			
		||||
      scope: keyword.operator.show-nonprintable.newline
 | 
			
		||||
    - match: "␍"
 | 
			
		||||
      scope: string.show-nonprintable.carriage-return
 | 
			
		||||
    - match: "␀"
 | 
			
		||||
      scope: entity.other.attribute-name.show-nonprintable.null
 | 
			
		||||
    - match: "␇"
 | 
			
		||||
      scope: entity.other.attribute-name.show-nonprintable.bell
 | 
			
		||||
    - match: "␛"
 | 
			
		||||
      scope: entity.other.attribute-name.show-nonprintable.escape
 | 
			
		||||
    - match: "␈"
 | 
			
		||||
      scope: entity.other.attribute-name.show-nonprintable.backspace
 | 
			
		||||
							
								
								
									
										12
									
								
								src/app.rs
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								src/app.rs
									
									
									
									
									
								
							@@ -37,6 +37,9 @@ pub struct Config<'a> {
 | 
			
		||||
    /// The explicitly configured language, if any
 | 
			
		||||
    pub language: Option<&'a str>,
 | 
			
		||||
 | 
			
		||||
    /// Whether or not to show/replace non-printable characters like space, tab and newline.
 | 
			
		||||
    pub show_nonprintable: bool,
 | 
			
		||||
 | 
			
		||||
    /// The character width of the terminal
 | 
			
		||||
    pub term_width: usize,
 | 
			
		||||
 | 
			
		||||
@@ -169,7 +172,14 @@ impl App {
 | 
			
		||||
 | 
			
		||||
        Ok(Config {
 | 
			
		||||
            true_color: is_truecolor_terminal(),
 | 
			
		||||
            language: self.matches.value_of("language"),
 | 
			
		||||
            language: self.matches.value_of("language").or_else(|| {
 | 
			
		||||
                if self.matches.is_present("show-all") {
 | 
			
		||||
                    Some("show-nonprintable")
 | 
			
		||||
                } else {
 | 
			
		||||
                    None
 | 
			
		||||
                }
 | 
			
		||||
            }),
 | 
			
		||||
            show_nonprintable: self.matches.is_present("show-all"),
 | 
			
		||||
            output_wrap: if !self.interactive_output {
 | 
			
		||||
                // We don't have the tty width when piping to another program.
 | 
			
		||||
                // There's no point in wrapping when this is the case.
 | 
			
		||||
 
 | 
			
		||||
@@ -158,6 +158,18 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
 | 
			
		||||
                     '--style=numbers'",
 | 
			
		||||
                ),
 | 
			
		||||
        )
 | 
			
		||||
        .arg(
 | 
			
		||||
            Arg::with_name("show-all")
 | 
			
		||||
                .long("show-all")
 | 
			
		||||
                .alias("show-nonprintable")
 | 
			
		||||
                .short("A")
 | 
			
		||||
                .conflicts_with("language")
 | 
			
		||||
                .help("Show non-printable characters (space, tab, newline, ..).")
 | 
			
		||||
                .long_help(
 | 
			
		||||
                    "Show non-printable characters like space, tab or newline. \
 | 
			
		||||
                     Use '--tabs' to control the width of the tab-placeholders.",
 | 
			
		||||
                ),
 | 
			
		||||
        )
 | 
			
		||||
        .arg(
 | 
			
		||||
            Arg::with_name("line-range")
 | 
			
		||||
                .long("line-range")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
use std::io::{self, Write};
 | 
			
		||||
use std::mem::swap;
 | 
			
		||||
 | 
			
		||||
use app::Config;
 | 
			
		||||
use assets::HighlightingAssets;
 | 
			
		||||
@@ -6,6 +7,7 @@ use errors::*;
 | 
			
		||||
use inputfile::{InputFile, InputFileReader};
 | 
			
		||||
use line_range::{LineRanges, RangeCheckResult};
 | 
			
		||||
use output::OutputType;
 | 
			
		||||
use preprocessor::replace_nonprintable;
 | 
			
		||||
use printer::{InteractivePrinter, Printer, SimplePrinter};
 | 
			
		||||
 | 
			
		||||
pub struct Controller<'a> {
 | 
			
		||||
@@ -64,7 +66,14 @@ impl<'b> Controller<'b> {
 | 
			
		||||
        input_file: InputFile<'a>,
 | 
			
		||||
    ) -> Result<()> {
 | 
			
		||||
        printer.print_header(writer, input_file)?;
 | 
			
		||||
        self.print_file_ranges(printer, writer, reader, &self.config.line_ranges)?;
 | 
			
		||||
        self.print_file_ranges(
 | 
			
		||||
            printer,
 | 
			
		||||
            writer,
 | 
			
		||||
            reader,
 | 
			
		||||
            &self.config.line_ranges,
 | 
			
		||||
            self.config.show_nonprintable,
 | 
			
		||||
            self.config.tab_width,
 | 
			
		||||
        )?;
 | 
			
		||||
        printer.print_footer(writer)?;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
@@ -76,12 +85,20 @@ impl<'b> Controller<'b> {
 | 
			
		||||
        writer: &mut Write,
 | 
			
		||||
        mut reader: InputFileReader,
 | 
			
		||||
        line_ranges: &LineRanges,
 | 
			
		||||
        show_nonprintable: bool,
 | 
			
		||||
        tab_width: usize,
 | 
			
		||||
    ) -> Result<()> {
 | 
			
		||||
        let mut line_buffer = Vec::new();
 | 
			
		||||
        let mut line_buffer_processed = Vec::new();
 | 
			
		||||
 | 
			
		||||
        let mut line_number: usize = 1;
 | 
			
		||||
 | 
			
		||||
        while reader.read_line(&mut line_buffer)? {
 | 
			
		||||
            if show_nonprintable {
 | 
			
		||||
                replace_nonprintable(&mut line_buffer, &mut line_buffer_processed, tab_width);
 | 
			
		||||
                swap(&mut line_buffer, &mut line_buffer_processed);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            match line_ranges.check(line_number) {
 | 
			
		||||
                RangeCheckResult::OutsideRange => {
 | 
			
		||||
                    // Call the printer in case we need to call the syntax highlighter
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
use console::AnsiCodeIterator;
 | 
			
		||||
 | 
			
		||||
/// Expand tabs like an ANSI-enabled expand(1).
 | 
			
		||||
pub fn expand(line: &str, width: usize, cursor: &mut usize) -> String {
 | 
			
		||||
pub fn expand_tabs(line: &str, width: usize, cursor: &mut usize) -> String {
 | 
			
		||||
    let mut buffer = String::with_capacity(line.len() * 2);
 | 
			
		||||
 | 
			
		||||
    for chunk in AnsiCodeIterator::new(line) {
 | 
			
		||||
@@ -32,3 +32,42 @@ pub fn expand(line: &str, width: usize, cursor: &mut usize) -> String {
 | 
			
		||||
 | 
			
		||||
    buffer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn replace_nonprintable(input: &mut Vec<u8>, output: &mut Vec<u8>, tab_width: usize) {
 | 
			
		||||
    output.clear();
 | 
			
		||||
 | 
			
		||||
    let tab_width = if tab_width == 0 {
 | 
			
		||||
        4
 | 
			
		||||
    } else if tab_width == 1 {
 | 
			
		||||
        2
 | 
			
		||||
    } else {
 | 
			
		||||
        tab_width
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    for chr in input {
 | 
			
		||||
        match *chr {
 | 
			
		||||
            // space
 | 
			
		||||
            b' ' => output.extend_from_slice("•".as_bytes()),
 | 
			
		||||
            // tab
 | 
			
		||||
            b'\t' => {
 | 
			
		||||
                output.extend_from_slice("├".as_bytes());
 | 
			
		||||
                output.extend_from_slice("─".repeat(tab_width - 2).as_bytes());
 | 
			
		||||
                output.extend_from_slice("┤".as_bytes());
 | 
			
		||||
            }
 | 
			
		||||
            // new line
 | 
			
		||||
            b'\n' => output.extend_from_slice("".as_bytes()),
 | 
			
		||||
            // carriage return
 | 
			
		||||
            b'\r' => output.extend_from_slice("␍".as_bytes()),
 | 
			
		||||
            // null
 | 
			
		||||
            0x00 => output.extend_from_slice("␀".as_bytes()),
 | 
			
		||||
            // bell
 | 
			
		||||
            0x07 => output.extend_from_slice("␇".as_bytes()),
 | 
			
		||||
            // backspace
 | 
			
		||||
            0x08 => output.extend_from_slice("␈".as_bytes()),
 | 
			
		||||
            // escape
 | 
			
		||||
            0x1B => output.extend_from_slice("␛".as_bytes()),
 | 
			
		||||
            // anything else
 | 
			
		||||
            _ => output.push(*chr),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ use diff::get_git_diff;
 | 
			
		||||
use diff::LineChanges;
 | 
			
		||||
use errors::*;
 | 
			
		||||
use inputfile::{InputFile, InputFileReader};
 | 
			
		||||
use preprocessor::expand;
 | 
			
		||||
use preprocessor::expand_tabs;
 | 
			
		||||
use style::OutputWrap;
 | 
			
		||||
use terminal::{as_terminal_escaped, to_ansi_color};
 | 
			
		||||
 | 
			
		||||
@@ -177,7 +177,7 @@ impl<'a> InteractivePrinter<'a> {
 | 
			
		||||
 | 
			
		||||
    fn preprocess(&self, text: &str, cursor: &mut usize) -> String {
 | 
			
		||||
        if self.config.tab_width > 0 {
 | 
			
		||||
            expand(text, self.config.tab_width, cursor)
 | 
			
		||||
            expand_tabs(text, self.config.tab_width, cursor)
 | 
			
		||||
        } else {
 | 
			
		||||
            text.to_string()
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user