mirror of
https://github.com/sharkdp/bat.git
synced 2024-12-30 10:18:51 +01: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:
parent
cbed338c3a
commit
ecd862d9ff
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()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user