2023-07-09 02:08:14 +02:00
|
|
|
use std::fmt;
|
|
|
|
use std::io;
|
2018-08-22 22:29:12 +02:00
|
|
|
use std::vec::Vec;
|
|
|
|
|
2023-03-23 10:47:38 +01:00
|
|
|
use nu_ansi_term::Color::{Fixed, Green, Red, Yellow};
|
|
|
|
use nu_ansi_term::Style;
|
2018-08-22 22:29:12 +02:00
|
|
|
|
2022-02-07 20:48:57 +01:00
|
|
|
use bytesize::ByteSize;
|
|
|
|
|
2018-08-23 22:37:27 +02:00
|
|
|
use syntect::easy::HighlightLines;
|
2018-10-10 06:25:33 +02:00
|
|
|
use syntect::highlighting::Color;
|
2024-02-12 07:29:25 +01:00
|
|
|
use syntect::highlighting::FontStyle;
|
2018-08-23 22:37:27 +02:00
|
|
|
use syntect::highlighting::Theme;
|
2018-10-09 21:18:40 +02:00
|
|
|
use syntect::parsing::SyntaxSet;
|
2018-08-22 22:29:12 +02:00
|
|
|
|
2018-10-07 16:44:59 +02:00
|
|
|
use content_inspector::ContentType;
|
|
|
|
|
2023-07-21 16:41:32 +02:00
|
|
|
use encoding_rs::{UTF_16BE, UTF_16LE};
|
2018-10-07 14:24:47 +02:00
|
|
|
|
2020-02-01 10:50:34 +01:00
|
|
|
use unicode_width::UnicodeWidthChar;
|
|
|
|
|
2021-08-08 08:26:17 +02:00
|
|
|
use crate::assets::{HighlightingAssets, SyntaxReferenceInSet};
|
2020-03-21 19:40:43 +01:00
|
|
|
use crate::config::Config;
|
2020-03-30 19:37:29 +02:00
|
|
|
#[cfg(feature = "git")]
|
|
|
|
use crate::decorations::LineChangesDecoration;
|
2020-04-11 19:40:04 +02:00
|
|
|
use crate::decorations::{Decoration, GridBorderDecoration, LineNumberDecoration};
|
2020-03-30 19:37:29 +02:00
|
|
|
#[cfg(feature = "git")]
|
2020-04-23 23:39:30 +02:00
|
|
|
use crate::diff::LineChanges;
|
2020-04-22 21:45:47 +02:00
|
|
|
use crate::error::*;
|
2020-04-22 21:50:21 +02:00
|
|
|
use crate::input::OpenedInput;
|
2020-01-23 04:26:21 +01:00
|
|
|
use crate::line_range::RangeCheckResult;
|
2024-06-11 06:05:20 +02:00
|
|
|
use crate::preprocessor::strip_ansi;
|
2019-10-06 03:44:14 +02:00
|
|
|
use crate::preprocessor::{expand_tabs, replace_nonprintable};
|
2022-02-07 20:48:57 +01:00
|
|
|
use crate::style::StyleComponent;
|
2019-10-06 03:44:14 +02:00
|
|
|
use crate::terminal::{as_terminal_escaped, to_ansi_color};
|
2023-04-18 02:02:42 +02:00
|
|
|
use crate::vscreen::{AnsiStyle, EscapeSequence, EscapeSequenceIterator};
|
2020-04-22 20:34:40 +02:00
|
|
|
use crate::wrapping::WrappingMode;
|
2024-06-11 06:05:20 +02:00
|
|
|
use crate::StripAnsiMode;
|
2018-05-07 01:32:00 +02:00
|
|
|
|
2023-04-18 02:02:42 +02:00
|
|
|
const ANSI_UNDERLINE_ENABLE: EscapeSequence = EscapeSequence::CSI {
|
|
|
|
raw_sequence: "\x1B[4m",
|
|
|
|
parameters: "4",
|
|
|
|
intermediates: "",
|
|
|
|
final_byte: "m",
|
|
|
|
};
|
|
|
|
|
|
|
|
const ANSI_UNDERLINE_DISABLE: EscapeSequence = EscapeSequence::CSI {
|
|
|
|
raw_sequence: "\x1B[24m",
|
|
|
|
parameters: "24",
|
|
|
|
intermediates: "",
|
|
|
|
final_byte: "m",
|
|
|
|
};
|
|
|
|
|
2024-02-12 07:29:25 +01:00
|
|
|
const EMPTY_SYNTECT_STYLE: syntect::highlighting::Style = syntect::highlighting::Style {
|
|
|
|
foreground: Color {
|
|
|
|
r: 127,
|
|
|
|
g: 127,
|
|
|
|
b: 127,
|
|
|
|
a: 255,
|
|
|
|
},
|
|
|
|
background: Color {
|
|
|
|
r: 127,
|
|
|
|
g: 127,
|
|
|
|
b: 127,
|
|
|
|
a: 255,
|
|
|
|
},
|
|
|
|
font_style: FontStyle::empty(),
|
|
|
|
};
|
|
|
|
|
2023-07-09 02:08:14 +02:00
|
|
|
pub enum OutputHandle<'a> {
|
|
|
|
IoWrite(&'a mut dyn io::Write),
|
|
|
|
FmtWrite(&'a mut dyn fmt::Write),
|
|
|
|
}
|
|
|
|
|
2023-10-04 13:44:19 +02:00
|
|
|
impl<'a> OutputHandle<'a> {
|
|
|
|
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<()> {
|
|
|
|
match self {
|
|
|
|
Self::IoWrite(handle) => handle.write_fmt(args).map_err(Into::into),
|
|
|
|
Self::FmtWrite(handle) => handle.write_fmt(args).map_err(Into::into),
|
2023-07-09 02:08:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-22 18:30:06 +02:00
|
|
|
pub(crate) trait Printer {
|
2020-05-12 02:57:51 +02:00
|
|
|
fn print_header(
|
|
|
|
&mut self,
|
2023-07-09 02:20:58 +02:00
|
|
|
handle: &mut OutputHandle,
|
2020-05-12 02:57:51 +02:00
|
|
|
input: &OpenedInput,
|
|
|
|
add_header_padding: bool,
|
|
|
|
) -> Result<()>;
|
2023-07-09 02:20:58 +02:00
|
|
|
fn print_footer(&mut self, handle: &mut OutputHandle, input: &OpenedInput) -> Result<()>;
|
2019-05-25 03:24:13 +02:00
|
|
|
|
2023-07-09 02:20:58 +02:00
|
|
|
fn print_snip(&mut self, handle: &mut OutputHandle) -> Result<()>;
|
2019-05-25 03:24:13 +02:00
|
|
|
|
2018-08-23 23:13:24 +02:00
|
|
|
fn print_line(
|
|
|
|
&mut self,
|
2018-08-23 23:35:57 +02:00
|
|
|
out_of_range: bool,
|
2023-07-09 02:20:58 +02:00
|
|
|
handle: &mut OutputHandle,
|
2018-08-23 23:13:24 +02:00
|
|
|
line_number: usize,
|
|
|
|
line_buffer: &[u8],
|
|
|
|
) -> Result<()>;
|
|
|
|
}
|
|
|
|
|
2020-06-20 17:00:32 +02:00
|
|
|
pub struct SimplePrinter<'a> {
|
2020-09-20 20:47:21 +02:00
|
|
|
config: &'a Config<'a>,
|
2023-09-10 14:28:35 +02:00
|
|
|
consecutive_empty_lines: usize,
|
2020-06-20 17:00:32 +02:00
|
|
|
}
|
2018-08-23 23:13:24 +02:00
|
|
|
|
2020-06-20 17:00:32 +02:00
|
|
|
impl<'a> SimplePrinter<'a> {
|
|
|
|
pub fn new(config: &'a Config) -> Self {
|
2023-09-10 14:28:35 +02:00
|
|
|
SimplePrinter {
|
|
|
|
config,
|
|
|
|
consecutive_empty_lines: 0,
|
|
|
|
}
|
2018-08-23 23:13:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-20 17:00:32 +02:00
|
|
|
impl<'a> Printer for SimplePrinter<'a> {
|
2020-05-12 02:57:51 +02:00
|
|
|
fn print_header(
|
|
|
|
&mut self,
|
2023-07-09 02:20:58 +02:00
|
|
|
_handle: &mut OutputHandle,
|
2020-05-12 02:57:51 +02:00
|
|
|
_input: &OpenedInput,
|
|
|
|
_add_header_padding: bool,
|
|
|
|
) -> Result<()> {
|
2018-08-23 23:13:24 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-07-09 02:20:58 +02:00
|
|
|
fn print_footer(&mut self, _handle: &mut OutputHandle, _input: &OpenedInput) -> Result<()> {
|
2018-08-23 23:13:24 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-07-09 02:20:58 +02:00
|
|
|
fn print_snip(&mut self, _handle: &mut OutputHandle) -> Result<()> {
|
2019-05-25 03:24:13 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2018-08-23 23:13:24 +02:00
|
|
|
fn print_line(
|
|
|
|
&mut self,
|
2018-08-23 23:35:57 +02:00
|
|
|
out_of_range: bool,
|
2023-07-09 02:20:58 +02:00
|
|
|
handle: &mut OutputHandle,
|
2018-08-23 23:13:24 +02:00
|
|
|
_line_number: usize,
|
|
|
|
line_buffer: &[u8],
|
|
|
|
) -> Result<()> {
|
2023-09-10 14:28:35 +02:00
|
|
|
// Skip squeezed lines.
|
|
|
|
if let Some(squeeze_limit) = self.config.squeeze_lines {
|
|
|
|
if String::from_utf8_lossy(line_buffer)
|
|
|
|
.trim_end_matches(|c| c == '\r' || c == '\n')
|
|
|
|
.is_empty()
|
|
|
|
{
|
|
|
|
self.consecutive_empty_lines += 1;
|
|
|
|
if self.consecutive_empty_lines > squeeze_limit {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.consecutive_empty_lines = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-23 23:35:57 +02:00
|
|
|
if !out_of_range {
|
2020-06-20 17:00:32 +02:00
|
|
|
if self.config.show_nonprintable {
|
2023-03-14 22:21:30 +01:00
|
|
|
let line = replace_nonprintable(
|
|
|
|
line_buffer,
|
|
|
|
self.config.tab_width,
|
|
|
|
self.config.nonprintable_notation,
|
|
|
|
);
|
2024-02-24 22:36:14 +01:00
|
|
|
write!(handle, "{line}")?;
|
2020-06-20 17:00:32 +02:00
|
|
|
} else {
|
2023-07-09 02:08:14 +02:00
|
|
|
match handle {
|
|
|
|
OutputHandle::IoWrite(handle) => handle.write_all(line_buffer)?,
|
2023-07-09 02:33:48 +02:00
|
|
|
OutputHandle::FmtWrite(handle) => {
|
|
|
|
write!(
|
|
|
|
handle,
|
|
|
|
"{}",
|
|
|
|
std::str::from_utf8(line_buffer).map_err(|_| Error::Msg(
|
|
|
|
"encountered invalid utf8 while printing to non-io buffer"
|
|
|
|
.to_string()
|
|
|
|
))?
|
|
|
|
)?;
|
|
|
|
}
|
2023-07-09 02:08:14 +02:00
|
|
|
}
|
2020-06-20 17:00:32 +02:00
|
|
|
};
|
2018-08-23 23:35:57 +02:00
|
|
|
}
|
2018-08-23 23:13:24 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
2018-08-23 19:43:10 +02:00
|
|
|
}
|
|
|
|
|
2021-09-21 08:23:18 +02:00
|
|
|
struct HighlighterFromSet<'a> {
|
|
|
|
highlighter: HighlightLines<'a>,
|
|
|
|
syntax_set: &'a SyntaxSet,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> HighlighterFromSet<'a> {
|
|
|
|
fn new(syntax_in_set: SyntaxReferenceInSet<'a>, theme: &'a Theme) -> Self {
|
|
|
|
Self {
|
|
|
|
highlighter: HighlightLines::new(syntax_in_set.syntax, theme),
|
|
|
|
syntax_set: syntax_in_set.syntax_set,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-22 18:30:06 +02:00
|
|
|
pub(crate) struct InteractivePrinter<'a> {
|
2018-05-07 01:32:00 +02:00
|
|
|
colors: Colors,
|
2018-08-23 22:37:27 +02:00
|
|
|
config: &'a Config<'a>,
|
2018-10-07 11:55:39 +02:00
|
|
|
decorations: Vec<Box<dyn Decoration>>,
|
2018-05-12 06:59:26 +02:00
|
|
|
panel_width: usize,
|
2022-09-09 20:21:22 +02:00
|
|
|
ansi_style: AnsiStyle,
|
2019-05-15 21:53:22 +02:00
|
|
|
content_type: Option<ContentType>,
|
2020-03-30 19:37:29 +02:00
|
|
|
#[cfg(feature = "git")]
|
2020-04-23 23:39:30 +02:00
|
|
|
pub line_changes: &'a Option<LineChanges>,
|
2021-09-21 08:23:18 +02:00
|
|
|
highlighter_from_set: Option<HighlighterFromSet<'a>>,
|
2018-12-16 21:00:18 +01:00
|
|
|
background_color_highlight: Option<Color>,
|
2020-12-17 03:10:29 +01:00
|
|
|
consecutive_empty_lines: usize,
|
2024-06-11 06:05:20 +02:00
|
|
|
strip_ansi: bool,
|
2018-05-07 01:32:00 +02:00
|
|
|
}
|
|
|
|
|
2018-08-23 19:43:10 +02:00
|
|
|
impl<'a> InteractivePrinter<'a> {
|
2020-04-22 18:30:06 +02:00
|
|
|
pub(crate) fn new(
|
2018-10-07 13:26:50 +02:00
|
|
|
config: &'a Config,
|
|
|
|
assets: &'a HighlightingAssets,
|
2020-04-22 16:27:34 +02:00
|
|
|
input: &mut OpenedInput,
|
2020-05-13 07:24:51 +02:00
|
|
|
#[cfg(feature = "git")] line_changes: &'a Option<LineChanges>,
|
2020-05-16 11:42:29 +02:00
|
|
|
) -> Result<Self> {
|
2018-08-23 22:37:27 +02:00
|
|
|
let theme = assets.get_theme(&config.theme);
|
|
|
|
|
2018-12-16 21:00:18 +01:00
|
|
|
let background_color_highlight = theme.settings.line_highlight;
|
2018-10-10 06:25:33 +02:00
|
|
|
|
2018-05-10 23:39:13 +02:00
|
|
|
let colors = if config.colored_output {
|
2018-08-19 12:32:35 +02:00
|
|
|
Colors::colored(theme, config.true_color)
|
2018-05-07 01:32:00 +02:00
|
|
|
} else {
|
|
|
|
Colors::plain()
|
|
|
|
};
|
|
|
|
|
2018-05-14 03:44:07 +02:00
|
|
|
// Create decorations.
|
2018-10-07 11:55:39 +02:00
|
|
|
let mut decorations: Vec<Box<dyn Decoration>> = Vec::new();
|
2018-05-14 03:44:07 +02:00
|
|
|
|
2020-03-21 20:54:16 +01:00
|
|
|
if config.style_components.numbers() {
|
2018-05-14 03:44:07 +02:00
|
|
|
decorations.push(Box::new(LineNumberDecoration::new(&colors)));
|
|
|
|
}
|
|
|
|
|
2020-03-30 19:37:29 +02:00
|
|
|
#[cfg(feature = "git")]
|
|
|
|
{
|
|
|
|
if config.style_components.changes() {
|
|
|
|
decorations.push(Box::new(LineChangesDecoration::new(&colors)));
|
|
|
|
}
|
2018-05-14 03:44:07 +02:00
|
|
|
}
|
|
|
|
|
2018-05-15 23:09:51 +02:00
|
|
|
let mut panel_width: usize =
|
2018-05-14 03:44:07 +02:00
|
|
|
decorations.len() + decorations.iter().fold(0, |a, x| a + x.width());
|
|
|
|
|
|
|
|
// The grid border decoration isn't added until after the panel_width calculation, since the
|
|
|
|
// print_horizontal_line, print_header, and print_footer functions all assume the panel
|
|
|
|
// width is without the grid border.
|
2020-03-21 20:54:16 +01:00
|
|
|
if config.style_components.grid() && !decorations.is_empty() {
|
2018-05-14 03:44:07 +02:00
|
|
|
decorations.push(Box::new(GridBorderDecoration::new(&colors)));
|
|
|
|
}
|
|
|
|
|
2018-05-15 23:09:51 +02:00
|
|
|
// Disable the panel if the terminal is too small (i.e. can't fit 5 characters with the
|
|
|
|
// panel showing).
|
2018-05-16 02:45:58 +02:00
|
|
|
if config.term_width
|
|
|
|
< (decorations.len() + decorations.iter().fold(0, |a, x| a + x.width())) + 5
|
|
|
|
{
|
2018-05-15 23:09:51 +02:00
|
|
|
decorations.clear();
|
|
|
|
panel_width = 0;
|
|
|
|
}
|
|
|
|
|
2024-02-12 07:29:25 +01:00
|
|
|
// Get the highlighter for the output.
|
|
|
|
let is_printing_binary = input
|
2020-04-22 16:27:34 +02:00
|
|
|
.reader
|
2019-08-27 05:05:47 +02:00
|
|
|
.content_type
|
2024-02-12 07:29:25 +01:00
|
|
|
.map_or(false, |c| c.is_binary() && !config.show_nonprintable);
|
|
|
|
|
2024-03-11 08:40:08 +01:00
|
|
|
let highlighter_from_set = if is_printing_binary || !config.colored_output {
|
2021-09-21 08:23:18 +02:00
|
|
|
None
|
2018-10-07 14:24:47 +02:00
|
|
|
} else {
|
|
|
|
// Determine the type of syntax for highlighting
|
2021-08-08 08:26:17 +02:00
|
|
|
let syntax_in_set =
|
|
|
|
match assets.get_syntax(config.language, input, &config.syntax_mapping) {
|
|
|
|
Ok(syntax_in_set) => syntax_in_set,
|
2021-09-27 20:09:26 +02:00
|
|
|
Err(Error::UndetectedSyntax(_)) => assets
|
|
|
|
.find_syntax_by_name("Plain Text")?
|
|
|
|
.expect("A plain text syntax is available"),
|
2021-08-08 08:26:17 +02:00
|
|
|
Err(e) => return Err(e),
|
|
|
|
};
|
|
|
|
|
2021-09-21 08:23:18 +02:00
|
|
|
Some(HighlighterFromSet::new(syntax_in_set, theme))
|
2018-08-28 20:12:45 +02:00
|
|
|
};
|
2018-08-23 22:37:27 +02:00
|
|
|
|
2024-06-11 06:05:20 +02:00
|
|
|
// Determine when to strip ANSI sequences
|
|
|
|
let strip_ansi = match config.strip_ansi {
|
|
|
|
_ if config.show_nonprintable => false,
|
|
|
|
StripAnsiMode::Always => true,
|
|
|
|
_ => false,
|
|
|
|
};
|
|
|
|
|
2020-05-16 11:42:29 +02:00
|
|
|
Ok(InteractivePrinter {
|
2018-05-14 03:44:07 +02:00
|
|
|
panel_width,
|
2018-05-07 01:32:00 +02:00
|
|
|
colors,
|
2018-05-10 23:39:13 +02:00
|
|
|
config,
|
2018-05-14 03:44:07 +02:00
|
|
|
decorations,
|
2020-04-22 16:27:34 +02:00
|
|
|
content_type: input.reader.content_type,
|
2022-09-09 20:21:22 +02:00
|
|
|
ansi_style: AnsiStyle::new(),
|
2020-03-30 19:37:29 +02:00
|
|
|
#[cfg(feature = "git")]
|
2018-08-23 22:37:27 +02:00
|
|
|
line_changes,
|
2021-09-21 08:23:18 +02:00
|
|
|
highlighter_from_set,
|
2018-12-16 21:00:18 +01:00
|
|
|
background_color_highlight,
|
2020-12-17 03:10:29 +01:00
|
|
|
consecutive_empty_lines: 0,
|
2024-06-11 06:05:20 +02:00
|
|
|
strip_ansi,
|
2020-05-16 11:42:29 +02:00
|
|
|
})
|
2018-05-07 01:32:00 +02:00
|
|
|
}
|
|
|
|
|
2023-07-09 02:20:58 +02:00
|
|
|
fn print_horizontal_line_term(
|
|
|
|
&mut self,
|
|
|
|
handle: &mut OutputHandle,
|
|
|
|
style: Style,
|
|
|
|
) -> Result<()> {
|
2023-10-04 13:44:19 +02:00
|
|
|
writeln!(
|
2020-10-06 17:57:47 +02:00
|
|
|
handle,
|
|
|
|
"{}",
|
|
|
|
style.paint("─".repeat(self.config.term_width))
|
|
|
|
)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-07-09 02:20:58 +02:00
|
|
|
fn print_horizontal_line(&mut self, handle: &mut OutputHandle, grid_char: char) -> Result<()> {
|
2018-08-23 19:43:10 +02:00
|
|
|
if self.panel_width == 0 {
|
2020-10-06 17:57:47 +02:00
|
|
|
self.print_horizontal_line_term(handle, self.colors.grid)?;
|
2018-08-23 19:43:10 +02:00
|
|
|
} else {
|
|
|
|
let hline = "─".repeat(self.config.term_width - (self.panel_width + 1));
|
|
|
|
let hline = format!("{}{}{}", "─".repeat(self.panel_width), grid_char, hline);
|
2023-10-04 13:44:19 +02:00
|
|
|
writeln!(handle, "{}", self.colors.grid.paint(hline))?;
|
2018-08-23 19:43:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2018-09-11 22:02:22 +02:00
|
|
|
|
2019-05-25 03:24:13 +02:00
|
|
|
fn create_fake_panel(&self, text: &str) -> String {
|
|
|
|
if self.panel_width == 0 {
|
2021-09-10 21:52:09 +02:00
|
|
|
return "".to_string();
|
|
|
|
}
|
|
|
|
|
|
|
|
let text_truncated: String = text.chars().take(self.panel_width - 1).collect();
|
|
|
|
let text_filled: String = format!(
|
|
|
|
"{}{}",
|
|
|
|
text_truncated,
|
|
|
|
" ".repeat(self.panel_width - 1 - text_truncated.len())
|
|
|
|
);
|
|
|
|
if self.config.style_components.grid() {
|
2024-02-24 22:36:14 +01:00
|
|
|
format!("{text_filled} │ ")
|
2019-05-25 03:24:13 +02:00
|
|
|
} else {
|
2021-09-10 21:52:09 +02:00
|
|
|
text_filled
|
2019-05-25 03:24:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-21 18:04:58 +01:00
|
|
|
fn get_header_component_indent_length(&self) -> usize {
|
|
|
|
if self.config.style_components.grid() && self.panel_width > 0 {
|
|
|
|
self.panel_width + 2
|
|
|
|
} else {
|
|
|
|
self.panel_width
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-09 02:20:58 +02:00
|
|
|
fn print_header_component_indent(&mut self, handle: &mut OutputHandle) -> Result<()> {
|
2022-02-07 20:48:57 +01:00
|
|
|
if self.config.style_components.grid() {
|
2023-10-04 13:44:19 +02:00
|
|
|
write!(
|
2022-02-07 20:48:57 +01:00
|
|
|
handle,
|
|
|
|
"{}{}",
|
|
|
|
" ".repeat(self.panel_width),
|
|
|
|
self.colors
|
|
|
|
.grid
|
|
|
|
.paint(if self.panel_width > 0 { "│ " } else { "" }),
|
|
|
|
)
|
|
|
|
} else {
|
2023-10-04 13:44:19 +02:00
|
|
|
write!(handle, "{}", " ".repeat(self.panel_width))
|
2022-02-07 20:48:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-21 17:25:24 +01:00
|
|
|
fn print_header_component_with_indent(
|
|
|
|
&mut self,
|
|
|
|
handle: &mut OutputHandle,
|
|
|
|
content: &str,
|
|
|
|
) -> Result<()> {
|
2024-01-21 17:13:11 +01:00
|
|
|
self.print_header_component_indent(handle)?;
|
2024-02-24 22:36:14 +01:00
|
|
|
writeln!(handle, "{content}")
|
2024-01-21 17:13:11 +01:00
|
|
|
}
|
|
|
|
|
2024-01-21 17:25:24 +01:00
|
|
|
fn print_header_multiline_component(
|
|
|
|
&mut self,
|
|
|
|
handle: &mut OutputHandle,
|
|
|
|
content: &str,
|
|
|
|
) -> Result<()> {
|
2024-01-21 17:13:11 +01:00
|
|
|
let mut content = content;
|
2024-01-21 18:04:58 +01:00
|
|
|
let content_width = self.config.term_width - self.get_header_component_indent_length();
|
2024-01-21 17:13:11 +01:00
|
|
|
while content.len() > content_width {
|
|
|
|
let (content_line, remaining) = content.split_at(content_width);
|
|
|
|
self.print_header_component_with_indent(handle, content_line)?;
|
|
|
|
content = remaining;
|
|
|
|
}
|
|
|
|
self.print_header_component_with_indent(handle, content)
|
|
|
|
}
|
|
|
|
|
2024-02-12 07:27:35 +01:00
|
|
|
fn highlight_regions_for_line<'b>(
|
|
|
|
&mut self,
|
|
|
|
line: &'b str,
|
|
|
|
) -> Result<Vec<(syntect::highlighting::Style, &'b str)>> {
|
|
|
|
let highlighter_from_set = match self.highlighter_from_set {
|
|
|
|
Some(ref mut highlighter_from_set) => highlighter_from_set,
|
|
|
|
_ => return Ok(vec![(EMPTY_SYNTECT_STYLE, line)]),
|
|
|
|
};
|
|
|
|
|
|
|
|
// skip syntax highlighting on long lines
|
|
|
|
let too_long = line.len() > 1024 * 16;
|
|
|
|
|
2024-03-10 11:03:18 +01:00
|
|
|
let for_highlighting: &str = if too_long { "\n" } else { line };
|
2024-02-12 07:27:35 +01:00
|
|
|
|
|
|
|
let mut highlighted_line = highlighter_from_set
|
|
|
|
.highlighter
|
|
|
|
.highlight_line(for_highlighting, highlighter_from_set.syntax_set)?;
|
|
|
|
|
|
|
|
if too_long {
|
|
|
|
highlighted_line[0].1 = &line;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(highlighted_line)
|
|
|
|
}
|
|
|
|
|
2018-09-11 22:45:49 +02:00
|
|
|
fn preprocess(&self, text: &str, cursor: &mut usize) -> String {
|
2018-09-11 22:02:22 +02:00
|
|
|
if self.config.tab_width > 0 {
|
2021-09-10 21:52:09 +02:00
|
|
|
return expand_tabs(text, self.config.tab_width, cursor);
|
2018-09-11 22:02:22 +02:00
|
|
|
}
|
2021-09-10 21:52:09 +02:00
|
|
|
|
|
|
|
*cursor += text.len();
|
|
|
|
text.to_string()
|
2018-09-11 22:02:22 +02:00
|
|
|
}
|
2018-08-23 19:43:10 +02:00
|
|
|
}
|
2018-05-19 12:25:07 +02:00
|
|
|
|
2018-08-23 19:43:10 +02:00
|
|
|
impl<'a> Printer for InteractivePrinter<'a> {
|
2020-05-12 02:57:51 +02:00
|
|
|
fn print_header(
|
|
|
|
&mut self,
|
2023-07-09 02:20:58 +02:00
|
|
|
handle: &mut OutputHandle,
|
2020-05-12 02:57:51 +02:00
|
|
|
input: &OpenedInput,
|
|
|
|
add_header_padding: bool,
|
|
|
|
) -> Result<()> {
|
2020-10-06 17:57:47 +02:00
|
|
|
if add_header_padding && self.config.style_components.rule() {
|
|
|
|
self.print_horizontal_line_term(handle, self.colors.rule)?;
|
|
|
|
}
|
|
|
|
|
2020-03-21 20:54:16 +01:00
|
|
|
if !self.config.style_components.header() {
|
2019-08-31 19:35:04 +02:00
|
|
|
if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable {
|
2023-10-04 13:44:19 +02:00
|
|
|
writeln!(
|
2019-05-14 23:14:41 +02:00
|
|
|
handle,
|
|
|
|
"{}: Binary content from {} will not be printed to the terminal \
|
2019-08-31 19:30:24 +02:00
|
|
|
(but will be present if the output of 'bat' is piped). You can use 'bat -A' \
|
|
|
|
to show the binary file contents.",
|
2019-05-14 23:14:41 +02:00
|
|
|
Yellow.paint("[bat warning]"),
|
2020-05-16 01:18:10 +02:00
|
|
|
input.description.summary(),
|
2019-05-14 23:14:41 +02:00
|
|
|
)?;
|
2020-04-24 08:46:01 +02:00
|
|
|
} else if self.config.style_components.grid() {
|
|
|
|
self.print_horizontal_line(handle, '┬')?;
|
2019-04-24 16:11:31 +02:00
|
|
|
}
|
Make `--style` parameter more flexible
The `--style` parameter now accepts a comma-separated list of strings,
where every element defines either a single output component (`changes`,
`grid`, `header`, `numbers`) or a predefined style (`full`,
`line-numbers`, `plain`).
If available, bat picks the first predefined style in the user-supplied
style-list and ignores everything else. If no predefined style was
requested, the other parameters that are simple output components will
be used.
Examples:
--style changes,full,numbers
Will internally be reduced to only the predefined style `full`.
--style plain,full
Will internally be reduced to only the predefined style `plain`.
--style changes,numbers
Will not be reduced, because the list does not contain any predefined
styles.
(Note: if `grid` is requested but no other parameters, bat still creates
the left-most column with a width of `PANEL_WIDTH`. I didn't want to
introduce further logic in this PR that drops or adapts the width of the
left column.)
2018-05-06 20:15:46 +02:00
|
|
|
return Ok(());
|
2018-05-07 01:32:00 +02:00
|
|
|
}
|
|
|
|
|
2018-10-07 16:44:59 +02:00
|
|
|
let mode = match self.content_type {
|
2019-05-15 21:53:22 +02:00
|
|
|
Some(ContentType::BINARY) => " <BINARY>",
|
|
|
|
Some(ContentType::UTF_16LE) => " <UTF-16LE>",
|
|
|
|
Some(ContentType::UTF_16BE) => " <UTF-16BE>",
|
|
|
|
None => " <EMPTY>",
|
2018-10-07 17:01:26 +02:00
|
|
|
_ => "",
|
2018-10-07 14:24:47 +02:00
|
|
|
};
|
|
|
|
|
2020-05-16 01:18:10 +02:00
|
|
|
let description = &input.description;
|
2022-02-07 20:48:57 +01:00
|
|
|
let metadata = &input.metadata;
|
|
|
|
|
|
|
|
// We use this iterator to have a deterministic order for
|
|
|
|
// header components. HashSet has arbitrary order, but Vec is ordered.
|
|
|
|
let header_components: Vec<StyleComponent> = [
|
|
|
|
(
|
|
|
|
StyleComponent::HeaderFilename,
|
|
|
|
self.config.style_components.header_filename(),
|
|
|
|
),
|
|
|
|
(
|
|
|
|
StyleComponent::HeaderFilesize,
|
|
|
|
self.config.style_components.header_filesize(),
|
|
|
|
),
|
|
|
|
]
|
|
|
|
.iter()
|
|
|
|
.filter(|(_, is_enabled)| *is_enabled)
|
|
|
|
.map(|(component, _)| *component)
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
// Print the cornering grid before the first header component
|
|
|
|
if self.config.style_components.grid() {
|
|
|
|
self.print_horizontal_line(handle, '┬')?;
|
|
|
|
} else {
|
|
|
|
// Only pad space between files, if we haven't already drawn a horizontal rule
|
|
|
|
if add_header_padding && !self.config.style_components.rule() {
|
2023-10-04 13:44:19 +02:00
|
|
|
writeln!(handle)?;
|
2022-02-07 20:48:57 +01:00
|
|
|
}
|
|
|
|
}
|
2020-04-22 16:27:34 +02:00
|
|
|
|
2024-01-21 17:25:24 +01:00
|
|
|
header_components
|
|
|
|
.iter()
|
|
|
|
.try_for_each(|component| match component {
|
2024-01-21 17:13:11 +01:00
|
|
|
StyleComponent::HeaderFilename => {
|
|
|
|
let header_filename = format!(
|
|
|
|
"{}{}{}",
|
|
|
|
description
|
|
|
|
.kind()
|
2024-02-24 22:36:14 +01:00
|
|
|
.map(|kind| format!("{kind}: "))
|
2024-01-21 17:13:11 +01:00
|
|
|
.unwrap_or_else(|| "".into()),
|
|
|
|
self.colors.header_value.paint(description.title()),
|
|
|
|
mode
|
|
|
|
);
|
|
|
|
self.print_header_multiline_component(handle, &header_filename)
|
|
|
|
}
|
2022-02-07 20:48:57 +01:00
|
|
|
StyleComponent::HeaderFilesize => {
|
|
|
|
let bsize = metadata
|
|
|
|
.size
|
|
|
|
.map(|s| format!("{}", ByteSize(s)))
|
|
|
|
.unwrap_or_else(|| "-".into());
|
2024-01-21 17:25:24 +01:00
|
|
|
let header_filesize =
|
|
|
|
format!("Size: {}", self.colors.header_value.paint(bsize));
|
2024-01-21 17:13:11 +01:00
|
|
|
self.print_header_multiline_component(handle, &header_filesize)
|
2022-02-07 20:48:57 +01:00
|
|
|
}
|
|
|
|
_ => Ok(()),
|
2024-01-21 17:25:24 +01:00
|
|
|
})?;
|
Make `--style` parameter more flexible
The `--style` parameter now accepts a comma-separated list of strings,
where every element defines either a single output component (`changes`,
`grid`, `header`, `numbers`) or a predefined style (`full`,
`line-numbers`, `plain`).
If available, bat picks the first predefined style in the user-supplied
style-list and ignores everything else. If no predefined style was
requested, the other parameters that are simple output components will
be used.
Examples:
--style changes,full,numbers
Will internally be reduced to only the predefined style `full`.
--style plain,full
Will internally be reduced to only the predefined style `plain`.
--style changes,numbers
Will not be reduced, because the list does not contain any predefined
styles.
(Note: if `grid` is requested but no other parameters, bat still creates
the left-most column with a width of `PANEL_WIDTH`. I didn't want to
introduce further logic in this PR that drops or adapts the width of the
left column.)
2018-05-06 20:15:46 +02:00
|
|
|
|
2020-03-21 20:54:16 +01:00
|
|
|
if self.config.style_components.grid() {
|
2019-08-31 19:30:24 +02:00
|
|
|
if self.content_type.map_or(false, |c| c.is_text()) || self.config.show_nonprintable {
|
2018-10-07 14:24:47 +02:00
|
|
|
self.print_horizontal_line(handle, '┼')?;
|
|
|
|
} else {
|
|
|
|
self.print_horizontal_line(handle, '┴')?;
|
|
|
|
}
|
Make `--style` parameter more flexible
The `--style` parameter now accepts a comma-separated list of strings,
where every element defines either a single output component (`changes`,
`grid`, `header`, `numbers`) or a predefined style (`full`,
`line-numbers`, `plain`).
If available, bat picks the first predefined style in the user-supplied
style-list and ignores everything else. If no predefined style was
requested, the other parameters that are simple output components will
be used.
Examples:
--style changes,full,numbers
Will internally be reduced to only the predefined style `full`.
--style plain,full
Will internally be reduced to only the predefined style `plain`.
--style changes,numbers
Will not be reduced, because the list does not contain any predefined
styles.
(Note: if `grid` is requested but no other parameters, bat still creates
the left-most column with a width of `PANEL_WIDTH`. I didn't want to
introduce further logic in this PR that drops or adapts the width of the
left column.)
2018-05-06 20:15:46 +02:00
|
|
|
}
|
2018-05-09 22:34:03 +02:00
|
|
|
|
|
|
|
Ok(())
|
2018-05-07 01:32:00 +02:00
|
|
|
}
|
|
|
|
|
2023-07-09 02:20:58 +02:00
|
|
|
fn print_footer(&mut self, handle: &mut OutputHandle, _input: &OpenedInput) -> Result<()> {
|
2020-03-21 20:54:16 +01:00
|
|
|
if self.config.style_components.grid()
|
2019-08-31 19:30:24 +02:00
|
|
|
&& (self.content_type.map_or(false, |c| c.is_text()) || self.config.show_nonprintable)
|
2019-05-15 21:53:22 +02:00
|
|
|
{
|
2018-08-23 22:37:27 +02:00
|
|
|
self.print_horizontal_line(handle, '┴')
|
2018-05-07 01:32:00 +02:00
|
|
|
} else {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-09 02:20:58 +02:00
|
|
|
fn print_snip(&mut self, handle: &mut OutputHandle) -> Result<()> {
|
2019-05-25 03:24:13 +02:00
|
|
|
let panel = self.create_fake_panel(" ...");
|
|
|
|
let panel_count = panel.chars().count();
|
|
|
|
|
|
|
|
let title = "8<";
|
|
|
|
let title_count = title.chars().count();
|
|
|
|
|
2019-08-31 12:46:27 +02:00
|
|
|
let snip_left = "─ ".repeat((self.config.term_width - panel_count - (title_count / 2)) / 4);
|
2019-05-25 03:24:13 +02:00
|
|
|
let snip_left_count = snip_left.chars().count(); // Can't use .len() with Unicode.
|
|
|
|
|
2019-08-31 12:46:27 +02:00
|
|
|
let snip_right =
|
|
|
|
" ─".repeat((self.config.term_width - panel_count - snip_left_count - title_count) / 2);
|
2019-05-25 03:24:13 +02:00
|
|
|
|
2023-10-04 13:44:19 +02:00
|
|
|
writeln!(
|
2019-05-25 03:24:13 +02:00
|
|
|
handle,
|
2020-04-24 08:46:01 +02:00
|
|
|
"{}",
|
2019-05-25 03:24:13 +02:00
|
|
|
self.colors
|
|
|
|
.grid
|
2024-02-24 22:36:14 +01:00
|
|
|
.paint(format!("{panel}{snip_left}{title}{snip_right}"))
|
2019-05-25 03:24:13 +02:00
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2018-08-23 23:13:24 +02:00
|
|
|
fn print_line(
|
|
|
|
&mut self,
|
2018-08-23 23:35:57 +02:00
|
|
|
out_of_range: bool,
|
2023-07-09 02:20:58 +02:00
|
|
|
handle: &mut OutputHandle,
|
2018-08-23 23:13:24 +02:00
|
|
|
line_number: usize,
|
|
|
|
line_buffer: &[u8],
|
|
|
|
) -> Result<()> {
|
2019-08-31 19:30:24 +02:00
|
|
|
let line = if self.config.show_nonprintable {
|
2023-03-14 22:21:30 +01:00
|
|
|
replace_nonprintable(
|
|
|
|
line_buffer,
|
|
|
|
self.config.tab_width,
|
|
|
|
self.config.nonprintable_notation,
|
|
|
|
)
|
2023-07-21 16:41:32 +02:00
|
|
|
.into()
|
2019-08-31 19:30:24 +02:00
|
|
|
} else {
|
2024-06-11 06:05:20 +02:00
|
|
|
let mut line = match self.content_type {
|
2019-08-31 19:30:24 +02:00
|
|
|
Some(ContentType::BINARY) | None => {
|
|
|
|
return Ok(());
|
|
|
|
}
|
2023-07-21 16:41:32 +02:00
|
|
|
Some(ContentType::UTF_16LE) => UTF_16LE.decode_with_bom_removal(line_buffer).0,
|
|
|
|
Some(ContentType::UTF_16BE) => UTF_16BE.decode_with_bom_removal(line_buffer).0,
|
|
|
|
_ => {
|
|
|
|
let line = String::from_utf8_lossy(line_buffer);
|
|
|
|
if line_number == 1 {
|
|
|
|
match line.strip_prefix('\u{feff}') {
|
|
|
|
Some(stripped) => stripped.to_string().into(),
|
|
|
|
None => line,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
line
|
|
|
|
}
|
2022-09-06 19:08:38 +02:00
|
|
|
}
|
2024-06-11 06:05:20 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// If ANSI escape sequences are supposed to be stripped, do it before syntax highlighting.
|
|
|
|
if self.strip_ansi {
|
|
|
|
line = strip_ansi(&line).into()
|
2018-10-07 16:44:59 +02:00
|
|
|
}
|
2024-06-11 06:05:20 +02:00
|
|
|
|
|
|
|
line
|
2018-10-07 16:44:59 +02:00
|
|
|
};
|
2018-11-01 20:29:48 +01:00
|
|
|
|
2024-02-12 07:27:35 +01:00
|
|
|
let regions = self.highlight_regions_for_line(&line)?;
|
2018-08-23 23:35:57 +02:00
|
|
|
if out_of_range {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
2020-12-17 03:10:29 +01:00
|
|
|
// Skip squeezed lines.
|
2020-12-17 03:23:14 +01:00
|
|
|
if let Some(squeeze_limit) = self.config.squeeze_lines {
|
2020-12-17 03:10:29 +01:00
|
|
|
if line.trim_end_matches(|c| c == '\r' || c == '\n').is_empty() {
|
|
|
|
self.consecutive_empty_lines += 1;
|
2020-12-17 03:23:14 +01:00
|
|
|
if self.consecutive_empty_lines > squeeze_limit {
|
2020-12-17 03:10:29 +01:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.consecutive_empty_lines = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-12 15:32:23 +02:00
|
|
|
let mut cursor: usize = 0;
|
2018-05-12 22:44:10 +02:00
|
|
|
let mut cursor_max: usize = self.config.term_width;
|
2018-09-11 22:02:22 +02:00
|
|
|
let mut cursor_total: usize = 0;
|
2018-05-14 03:44:07 +02:00
|
|
|
let mut panel_wrap: Option<String> = None;
|
|
|
|
|
2018-12-16 21:00:18 +01:00
|
|
|
// Line highlighting
|
2020-01-23 04:26:21 +01:00
|
|
|
let highlight_this_line =
|
2020-03-21 17:22:17 +01:00
|
|
|
self.config.highlighted_lines.0.check(line_number) == RangeCheckResult::InRange;
|
2018-12-16 21:53:15 +01:00
|
|
|
|
2022-02-14 19:02:14 +01:00
|
|
|
if highlight_this_line && self.config.theme == "ansi" {
|
2023-04-18 02:02:42 +02:00
|
|
|
self.ansi_style.update(ANSI_UNDERLINE_ENABLE);
|
2022-02-14 19:02:14 +01:00
|
|
|
}
|
|
|
|
|
2018-12-16 21:53:15 +01:00
|
|
|
let background_color = self
|
|
|
|
.background_color_highlight
|
|
|
|
.filter(|_| highlight_this_line);
|
2018-12-16 21:00:18 +01:00
|
|
|
|
2018-05-14 03:44:07 +02:00
|
|
|
// Line decorations.
|
|
|
|
if self.panel_width > 0 {
|
2018-07-17 23:38:45 +02:00
|
|
|
let decorations = self
|
|
|
|
.decorations
|
2018-05-14 03:44:07 +02:00
|
|
|
.iter()
|
2021-09-10 21:56:40 +02:00
|
|
|
.map(|d| d.generate(line_number, false, self));
|
2018-05-14 03:44:07 +02:00
|
|
|
|
|
|
|
for deco in decorations {
|
2023-10-04 13:44:19 +02:00
|
|
|
write!(handle, "{} ", deco.text)?;
|
2018-05-14 03:44:07 +02:00
|
|
|
cursor_max -= deco.width + 1;
|
2018-05-12 22:44:10 +02:00
|
|
|
}
|
2018-05-14 03:44:07 +02:00
|
|
|
}
|
2018-05-12 22:44:10 +02:00
|
|
|
|
|
|
|
// Line contents.
|
2021-01-09 00:23:11 +01:00
|
|
|
if matches!(self.config.wrapping_mode, WrappingMode::NoWrapping(_)) {
|
2018-05-12 22:44:10 +02:00
|
|
|
let true_color = self.config.true_color;
|
|
|
|
let colored_output = self.config.colored_output;
|
2018-11-04 18:08:33 +01:00
|
|
|
let italics = self.config.use_italic_text;
|
2018-11-02 12:41:56 +01:00
|
|
|
|
2021-09-10 21:56:40 +02:00
|
|
|
for &(style, region) in ®ions {
|
2023-04-18 02:02:42 +02:00
|
|
|
let ansi_iterator = EscapeSequenceIterator::new(region);
|
2022-09-09 20:21:22 +02:00
|
|
|
for chunk in ansi_iterator {
|
|
|
|
match chunk {
|
|
|
|
// Regular text.
|
2023-04-18 02:02:42 +02:00
|
|
|
EscapeSequence::Text(text) => {
|
2023-04-18 04:19:49 +02:00
|
|
|
let text = self.preprocess(text, &mut cursor_total);
|
2022-09-09 20:21:22 +02:00
|
|
|
let text_trimmed = text.trim_end_matches(|c| c == '\r' || c == '\n');
|
|
|
|
|
2023-10-04 13:44:19 +02:00
|
|
|
write!(
|
2022-09-09 20:21:22 +02:00
|
|
|
handle,
|
2023-04-18 04:19:49 +02:00
|
|
|
"{}{}",
|
2022-09-09 20:21:22 +02:00
|
|
|
as_terminal_escaped(
|
|
|
|
style,
|
|
|
|
&format!("{}{}", self.ansi_style, text_trimmed),
|
|
|
|
true_color,
|
|
|
|
colored_output,
|
|
|
|
italics,
|
|
|
|
background_color
|
2023-04-18 04:19:49 +02:00
|
|
|
),
|
|
|
|
self.ansi_style.to_reset_sequence(),
|
2022-09-09 20:21:22 +02:00
|
|
|
)?;
|
|
|
|
|
2023-04-18 04:19:49 +02:00
|
|
|
// Pad the rest of the line.
|
2022-09-09 20:21:22 +02:00
|
|
|
if text.len() != text_trimmed.len() {
|
|
|
|
if let Some(background_color) = background_color {
|
|
|
|
let ansi_style = Style {
|
|
|
|
background: to_ansi_color(background_color, true_color),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
let width = if cursor_total <= cursor_max {
|
|
|
|
cursor_max - cursor_total + 1
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
};
|
2023-10-04 13:44:19 +02:00
|
|
|
write!(handle, "{}", ansi_style.paint(" ".repeat(width)))?;
|
2022-09-09 20:21:22 +02:00
|
|
|
}
|
2023-10-04 13:44:19 +02:00
|
|
|
write!(handle, "{}", &text[text_trimmed.len()..])?;
|
2022-09-09 20:21:22 +02:00
|
|
|
}
|
|
|
|
}
|
2023-04-18 02:02:42 +02:00
|
|
|
|
|
|
|
// ANSI escape passthrough.
|
|
|
|
_ => {
|
|
|
|
write!(handle, "{}", chunk.raw())?;
|
|
|
|
self.ansi_style.update(chunk);
|
|
|
|
}
|
2018-12-16 21:53:15 +01:00
|
|
|
}
|
|
|
|
}
|
2018-09-11 22:45:49 +02:00
|
|
|
}
|
2018-09-10 21:36:58 +02:00
|
|
|
|
2020-12-16 19:22:31 +01:00
|
|
|
if !self.config.style_components.plain() && line.bytes().next_back() != Some(b'\n') {
|
2023-10-04 13:44:19 +02:00
|
|
|
writeln!(handle)?;
|
2018-09-10 21:36:58 +02:00
|
|
|
}
|
Make `--style` parameter more flexible
The `--style` parameter now accepts a comma-separated list of strings,
where every element defines either a single output component (`changes`,
`grid`, `header`, `numbers`) or a predefined style (`full`,
`line-numbers`, `plain`).
If available, bat picks the first predefined style in the user-supplied
style-list and ignores everything else. If no predefined style was
requested, the other parameters that are simple output components will
be used.
Examples:
--style changes,full,numbers
Will internally be reduced to only the predefined style `full`.
--style plain,full
Will internally be reduced to only the predefined style `plain`.
--style changes,numbers
Will not be reduced, because the list does not contain any predefined
styles.
(Note: if `grid` is requested but no other parameters, bat still creates
the left-most column with a width of `PANEL_WIDTH`. I didn't want to
introduce further logic in this PR that drops or adapts the width of the
left column.)
2018-05-06 20:15:46 +02:00
|
|
|
} else {
|
2021-09-10 21:56:40 +02:00
|
|
|
for &(style, region) in ®ions {
|
2023-04-18 02:02:42 +02:00
|
|
|
let ansi_iterator = EscapeSequenceIterator::new(region);
|
2022-09-09 20:21:22 +02:00
|
|
|
for chunk in ansi_iterator {
|
|
|
|
match chunk {
|
|
|
|
// Regular text.
|
2023-04-18 02:02:42 +02:00
|
|
|
EscapeSequence::Text(text) => {
|
2022-09-09 20:21:22 +02:00
|
|
|
let text = self.preprocess(
|
|
|
|
text.trim_end_matches(|c| c == '\r' || c == '\n'),
|
|
|
|
&mut cursor_total,
|
|
|
|
);
|
|
|
|
|
|
|
|
let mut max_width = cursor_max - cursor;
|
|
|
|
|
|
|
|
// line buffer (avoid calling write! for every character)
|
|
|
|
let mut line_buf = String::with_capacity(max_width * 4);
|
|
|
|
|
|
|
|
// Displayed width of line_buf
|
|
|
|
let mut current_width = 0;
|
|
|
|
|
|
|
|
for c in text.chars() {
|
|
|
|
// calculate the displayed width for next character
|
|
|
|
let cw = c.width().unwrap_or(0);
|
|
|
|
current_width += cw;
|
|
|
|
|
|
|
|
// if next character cannot be printed on this line,
|
|
|
|
// flush the buffer.
|
|
|
|
if current_width > max_width {
|
|
|
|
// Generate wrap padding if not already generated.
|
|
|
|
if panel_wrap.is_none() {
|
|
|
|
panel_wrap = if self.panel_width > 0 {
|
|
|
|
Some(format!(
|
|
|
|
"{} ",
|
|
|
|
self.decorations
|
|
|
|
.iter()
|
|
|
|
.map(|d| d
|
|
|
|
.generate(line_number, true, self)
|
|
|
|
.text)
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join(" ")
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
Some("".to_string())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// It wraps.
|
2023-10-04 13:44:19 +02:00
|
|
|
write!(
|
2022-09-09 20:21:22 +02:00
|
|
|
handle,
|
2023-04-18 04:19:49 +02:00
|
|
|
"{}{}\n{}",
|
2022-09-09 20:21:22 +02:00
|
|
|
as_terminal_escaped(
|
|
|
|
style,
|
2023-03-14 21:19:14 +01:00
|
|
|
&format!("{}{}", self.ansi_style, line_buf),
|
2022-09-09 20:21:22 +02:00
|
|
|
self.config.true_color,
|
|
|
|
self.config.colored_output,
|
|
|
|
self.config.use_italic_text,
|
|
|
|
background_color
|
|
|
|
),
|
2023-04-18 04:19:49 +02:00
|
|
|
self.ansi_style.to_reset_sequence(),
|
2022-09-09 20:21:22 +02:00
|
|
|
panel_wrap.clone().unwrap()
|
|
|
|
)?;
|
|
|
|
|
|
|
|
cursor = 0;
|
|
|
|
max_width = cursor_max;
|
|
|
|
|
|
|
|
line_buf.clear();
|
|
|
|
current_width = cw;
|
|
|
|
}
|
|
|
|
|
|
|
|
line_buf.push(c);
|
|
|
|
}
|
2022-08-29 18:49:29 +02:00
|
|
|
|
2022-09-09 20:21:22 +02:00
|
|
|
// flush the buffer
|
|
|
|
cursor += current_width;
|
2023-10-04 13:44:19 +02:00
|
|
|
write!(
|
2022-09-09 20:21:22 +02:00
|
|
|
handle,
|
|
|
|
"{}",
|
|
|
|
as_terminal_escaped(
|
|
|
|
style,
|
2023-03-14 21:19:14 +01:00
|
|
|
&format!("{}{}", self.ansi_style, line_buf),
|
2022-09-09 20:21:22 +02:00
|
|
|
self.config.true_color,
|
|
|
|
self.config.colored_output,
|
|
|
|
self.config.use_italic_text,
|
|
|
|
background_color
|
|
|
|
)
|
|
|
|
)?;
|
|
|
|
}
|
2023-04-18 02:02:42 +02:00
|
|
|
|
|
|
|
// ANSI escape passthrough.
|
|
|
|
_ => {
|
|
|
|
write!(handle, "{}", chunk.raw())?;
|
|
|
|
self.ansi_style.update(chunk);
|
|
|
|
}
|
2022-09-09 20:21:22 +02:00
|
|
|
}
|
2018-05-12 06:59:26 +02:00
|
|
|
}
|
|
|
|
}
|
2018-05-12 22:23:33 +02:00
|
|
|
|
2018-12-16 21:53:15 +01:00
|
|
|
if let Some(background_color) = background_color {
|
2021-05-21 15:47:27 +02:00
|
|
|
let ansi_style = Style {
|
|
|
|
background: to_ansi_color(background_color, self.config.true_color),
|
|
|
|
..Default::default()
|
|
|
|
};
|
2018-12-16 21:53:15 +01:00
|
|
|
|
2023-10-04 13:44:19 +02:00
|
|
|
write!(
|
2018-12-16 21:53:15 +01:00
|
|
|
handle,
|
|
|
|
"{}",
|
|
|
|
ansi_style.paint(" ".repeat(cursor_max - cursor))
|
|
|
|
)?;
|
|
|
|
}
|
2023-10-04 13:44:19 +02:00
|
|
|
writeln!(handle)?;
|
2018-05-07 01:32:00 +02:00
|
|
|
}
|
2018-05-12 06:59:26 +02:00
|
|
|
|
2022-02-14 19:02:14 +01:00
|
|
|
if highlight_this_line && self.config.theme == "ansi" {
|
2023-04-18 02:02:42 +02:00
|
|
|
write!(handle, "{}", ANSI_UNDERLINE_DISABLE.raw())?;
|
|
|
|
self.ansi_style.update(ANSI_UNDERLINE_DISABLE);
|
2022-02-14 19:02:14 +01:00
|
|
|
}
|
|
|
|
|
2018-05-12 15:32:23 +02:00
|
|
|
Ok(())
|
2018-05-07 01:32:00 +02:00
|
|
|
}
|
|
|
|
}
|
2018-05-21 15:00:00 +02:00
|
|
|
|
2018-08-19 12:32:35 +02:00
|
|
|
const DEFAULT_GUTTER_COLOR: u8 = 238;
|
2018-05-21 15:00:00 +02:00
|
|
|
|
2019-10-15 03:25:53 +02:00
|
|
|
#[derive(Debug, Default)]
|
2018-05-21 15:00:00 +02:00
|
|
|
pub struct Colors {
|
|
|
|
pub grid: Style,
|
2020-10-06 17:57:47 +02:00
|
|
|
pub rule: Style,
|
2022-02-07 20:48:57 +01:00
|
|
|
pub header_value: Style,
|
2018-05-21 15:00:00 +02:00
|
|
|
pub git_added: Style,
|
|
|
|
pub git_removed: Style,
|
|
|
|
pub git_modified: Style,
|
|
|
|
pub line_number: Style,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Colors {
|
|
|
|
fn plain() -> Self {
|
|
|
|
Colors::default()
|
|
|
|
}
|
|
|
|
|
2018-08-19 12:32:35 +02:00
|
|
|
fn colored(theme: &Theme, true_color: bool) -> Self {
|
2020-11-29 23:16:54 +01:00
|
|
|
let gutter_style = Style {
|
|
|
|
foreground: match theme.settings.gutter_foreground {
|
|
|
|
// If the theme provides a gutter foreground color, use it.
|
|
|
|
// Note: It might be the special value #00000001, in which case
|
|
|
|
// to_ansi_color returns None and we use an empty Style
|
|
|
|
// (resulting in the terminal's default foreground color).
|
|
|
|
Some(c) => to_ansi_color(c, true_color),
|
|
|
|
// Otherwise, use a specific fallback color.
|
|
|
|
None => Some(Fixed(DEFAULT_GUTTER_COLOR)),
|
|
|
|
},
|
|
|
|
..Style::default()
|
|
|
|
};
|
2018-08-19 12:32:35 +02:00
|
|
|
|
2018-05-21 15:00:00 +02:00
|
|
|
Colors {
|
2020-11-29 23:16:54 +01:00
|
|
|
grid: gutter_style,
|
|
|
|
rule: gutter_style,
|
2022-02-07 20:48:57 +01:00
|
|
|
header_value: Style::new().bold(),
|
2018-05-21 15:00:00 +02:00
|
|
|
git_added: Green.normal(),
|
|
|
|
git_removed: Red.normal(),
|
|
|
|
git_modified: Yellow.normal(),
|
2020-11-29 23:16:54 +01:00
|
|
|
line_number: gutter_style,
|
2018-05-21 15:00:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|