mirror of
				https://github.com/sharkdp/bat.git
				synced 2025-10-31 07:04:04 +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