mirror of
				https://github.com/sharkdp/bat.git
				synced 2025-10-30 22:54:07 +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 |     /// The explicitly configured language, if any | ||||||
|     pub language: Option<&'a str>, |     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 |     /// The character width of the terminal | ||||||
|     pub term_width: usize, |     pub term_width: usize, | ||||||
|  |  | ||||||
| @@ -169,7 +172,14 @@ impl App { | |||||||
|  |  | ||||||
|         Ok(Config { |         Ok(Config { | ||||||
|             true_color: is_truecolor_terminal(), |             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 { |             output_wrap: if !self.interactive_output { | ||||||
|                 // We don't have the tty width when piping to another program. |                 // We don't have the tty width when piping to another program. | ||||||
|                 // There's no point in wrapping when this is the case. |                 // 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'", |                      '--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( | ||||||
|             Arg::with_name("line-range") |             Arg::with_name("line-range") | ||||||
|                 .long("line-range") |                 .long("line-range") | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| use std::io::{self, Write}; | use std::io::{self, Write}; | ||||||
|  | use std::mem::swap; | ||||||
|  |  | ||||||
| use app::Config; | use app::Config; | ||||||
| use assets::HighlightingAssets; | use assets::HighlightingAssets; | ||||||
| @@ -6,6 +7,7 @@ use errors::*; | |||||||
| use inputfile::{InputFile, InputFileReader}; | use inputfile::{InputFile, InputFileReader}; | ||||||
| use line_range::{LineRanges, RangeCheckResult}; | use line_range::{LineRanges, RangeCheckResult}; | ||||||
| use output::OutputType; | use output::OutputType; | ||||||
|  | use preprocessor::replace_nonprintable; | ||||||
| use printer::{InteractivePrinter, Printer, SimplePrinter}; | use printer::{InteractivePrinter, Printer, SimplePrinter}; | ||||||
|  |  | ||||||
| pub struct Controller<'a> { | pub struct Controller<'a> { | ||||||
| @@ -64,7 +66,14 @@ impl<'b> Controller<'b> { | |||||||
|         input_file: InputFile<'a>, |         input_file: InputFile<'a>, | ||||||
|     ) -> Result<()> { |     ) -> Result<()> { | ||||||
|         printer.print_header(writer, input_file)?; |         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)?; |         printer.print_footer(writer)?; | ||||||
|  |  | ||||||
|         Ok(()) |         Ok(()) | ||||||
| @@ -76,12 +85,20 @@ impl<'b> Controller<'b> { | |||||||
|         writer: &mut Write, |         writer: &mut Write, | ||||||
|         mut reader: InputFileReader, |         mut reader: InputFileReader, | ||||||
|         line_ranges: &LineRanges, |         line_ranges: &LineRanges, | ||||||
|  |         show_nonprintable: bool, | ||||||
|  |         tab_width: usize, | ||||||
|     ) -> Result<()> { |     ) -> Result<()> { | ||||||
|         let mut line_buffer = Vec::new(); |         let mut line_buffer = Vec::new(); | ||||||
|  |         let mut line_buffer_processed = Vec::new(); | ||||||
|  |  | ||||||
|         let mut line_number: usize = 1; |         let mut line_number: usize = 1; | ||||||
|  |  | ||||||
|         while reader.read_line(&mut line_buffer)? { |         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) { |             match line_ranges.check(line_number) { | ||||||
|                 RangeCheckResult::OutsideRange => { |                 RangeCheckResult::OutsideRange => { | ||||||
|                     // Call the printer in case we need to call the syntax highlighter |                     // Call the printer in case we need to call the syntax highlighter | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| use console::AnsiCodeIterator; | use console::AnsiCodeIterator; | ||||||
|  |  | ||||||
| /// Expand tabs like an ANSI-enabled expand(1). | /// 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); |     let mut buffer = String::with_capacity(line.len() * 2); | ||||||
|  |  | ||||||
|     for chunk in AnsiCodeIterator::new(line) { |     for chunk in AnsiCodeIterator::new(line) { | ||||||
| @@ -32,3 +32,42 @@ pub fn expand(line: &str, width: usize, cursor: &mut usize) -> String { | |||||||
|  |  | ||||||
|     buffer |     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 diff::LineChanges; | ||||||
| use errors::*; | use errors::*; | ||||||
| use inputfile::{InputFile, InputFileReader}; | use inputfile::{InputFile, InputFileReader}; | ||||||
| use preprocessor::expand; | use preprocessor::expand_tabs; | ||||||
| use style::OutputWrap; | use style::OutputWrap; | ||||||
| use terminal::{as_terminal_escaped, to_ansi_color}; | 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 { |     fn preprocess(&self, text: &str, cursor: &mut usize) -> String { | ||||||
|         if self.config.tab_width > 0 { |         if self.config.tab_width > 0 { | ||||||
|             expand(text, self.config.tab_width, cursor) |             expand_tabs(text, self.config.tab_width, cursor) | ||||||
|         } else { |         } else { | ||||||
|             text.to_string() |             text.to_string() | ||||||
|         } |         } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user