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
|
/// 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()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user