mirror of
https://github.com/sharkdp/bat.git
synced 2025-09-02 03:12:25 +01:00
Merge branch 'master' into pacman-conf
This commit is contained in:
@@ -2,11 +2,14 @@ use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::io::IsTerminal;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::{
|
||||
clap_app,
|
||||
config::{get_args_from_config_file, get_args_from_env_opts_var, get_args_from_env_vars},
|
||||
};
|
||||
use bat::style::StyleComponentList;
|
||||
use bat::StripAnsiMode;
|
||||
use clap::ArgMatches;
|
||||
|
||||
use console::Term;
|
||||
@@ -85,7 +88,6 @@ impl App {
|
||||
|
||||
// .. and the rest at the end
|
||||
cli_args.for_each(|a| args.push(a));
|
||||
|
||||
args
|
||||
};
|
||||
|
||||
@@ -242,6 +244,16 @@ impl App {
|
||||
4
|
||||
},
|
||||
),
|
||||
strip_ansi: match self
|
||||
.matches
|
||||
.get_one::<String>("strip-ansi")
|
||||
.map(|s| s.as_str())
|
||||
{
|
||||
Some("never") => StripAnsiMode::Never,
|
||||
Some("always") => StripAnsiMode::Always,
|
||||
Some("auto") => StripAnsiMode::Auto,
|
||||
_ => unreachable!("other values for --strip-ansi are not allowed"),
|
||||
},
|
||||
theme: self
|
||||
.matches
|
||||
.get_one::<String>("theme")
|
||||
@@ -353,34 +365,57 @@ impl App {
|
||||
Ok(file_input)
|
||||
}
|
||||
|
||||
fn forced_style_components(&self) -> Option<StyleComponents> {
|
||||
// No components if `--decorations=never``.
|
||||
if self
|
||||
.matches
|
||||
.get_one::<String>("decorations")
|
||||
.map(|s| s.as_str())
|
||||
== Some("never")
|
||||
{
|
||||
return Some(StyleComponents(HashSet::new()));
|
||||
}
|
||||
|
||||
// Only line numbers if `--number`.
|
||||
if self.matches.get_flag("number") {
|
||||
return Some(StyleComponents(HashSet::from([
|
||||
StyleComponent::LineNumbers,
|
||||
])));
|
||||
}
|
||||
|
||||
// Plain if `--plain` is specified at least once.
|
||||
if self.matches.get_count("plain") > 0 {
|
||||
return Some(StyleComponents(HashSet::from([StyleComponent::Plain])));
|
||||
}
|
||||
|
||||
// Default behavior.
|
||||
None
|
||||
}
|
||||
|
||||
fn style_components(&self) -> Result<StyleComponents> {
|
||||
let matches = &self.matches;
|
||||
let mut styled_components = StyleComponents(
|
||||
if matches.get_one::<String>("decorations").map(|s| s.as_str()) == Some("never") {
|
||||
HashSet::new()
|
||||
} else if matches.get_flag("number") {
|
||||
[StyleComponent::LineNumbers].iter().cloned().collect()
|
||||
} else if 0 < matches.get_count("plain") {
|
||||
[StyleComponent::Plain].iter().cloned().collect()
|
||||
} else {
|
||||
matches
|
||||
.get_one::<String>("style")
|
||||
.map(|styles| {
|
||||
styles
|
||||
.split(',')
|
||||
.map(|style| style.parse::<StyleComponent>())
|
||||
.filter_map(|style| style.ok())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_else(|| vec![StyleComponent::Default])
|
||||
let mut styled_components = match self.forced_style_components() {
|
||||
Some(forced_components) => forced_components,
|
||||
|
||||
// Parse the `--style` arguments and merge them.
|
||||
None if matches.contains_id("style") => {
|
||||
let lists = matches
|
||||
.get_many::<String>("style")
|
||||
.expect("styles present")
|
||||
.map(|v| StyleComponentList::from_str(v))
|
||||
.collect::<Result<Vec<StyleComponentList>>>()?;
|
||||
|
||||
StyleComponentList::to_components(lists, self.interactive_output, true)
|
||||
}
|
||||
|
||||
// Use the default.
|
||||
None => StyleComponents(HashSet::from_iter(
|
||||
StyleComponent::Default
|
||||
.components(self.interactive_output)
|
||||
.into_iter()
|
||||
.map(|style| style.components(self.interactive_output))
|
||||
.fold(HashSet::new(), |mut acc, components| {
|
||||
acc.extend(components.iter().cloned());
|
||||
acc
|
||||
})
|
||||
},
|
||||
);
|
||||
.cloned(),
|
||||
)),
|
||||
};
|
||||
|
||||
// If `grid` is set, remove `rule` as it is a subset of `grid`, and print a warning.
|
||||
if styled_components.grid() && styled_components.0.remove(&StyleComponent::Rule) {
|
||||
|
@@ -1,9 +1,11 @@
|
||||
use bat::style::StyleComponentList;
|
||||
use clap::{
|
||||
crate_name, crate_version, value_parser, Arg, ArgAction, ArgGroup, ColorChoice, Command,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
static VERSION: Lazy<String> = Lazy::new(|| {
|
||||
#[cfg(feature = "bugreport")]
|
||||
@@ -402,37 +404,30 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
.long_help("Set the maximum number of consecutive empty lines to be printed.")
|
||||
.hide_short_help(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("strip-ansi")
|
||||
.long("strip-ansi")
|
||||
.overrides_with("strip-ansi")
|
||||
.value_name("when")
|
||||
.value_parser(["auto", "always", "never"])
|
||||
.default_value("never")
|
||||
.hide_default_value(true)
|
||||
.help("Strip colors from the input (auto, always, *never*)")
|
||||
.long_help("Specify when to strip ANSI escape sequences from the input. \
|
||||
The automatic mode will remove escape sequences unless the syntax highlighting \
|
||||
language is plain text. Possible values: auto, always, *never*.")
|
||||
.hide_short_help(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("style")
|
||||
.long("style")
|
||||
.action(ArgAction::Append)
|
||||
.value_name("components")
|
||||
.overrides_with("style")
|
||||
.overrides_with("plain")
|
||||
.overrides_with("number")
|
||||
// Cannot use claps built in validation because we have to turn off clap's delimiters
|
||||
.value_parser(|val: &str| {
|
||||
let mut invalid_vals = val.split(',').filter(|style| {
|
||||
!&[
|
||||
"auto",
|
||||
"full",
|
||||
"default",
|
||||
"plain",
|
||||
"header",
|
||||
"header-filename",
|
||||
"header-filesize",
|
||||
"grid",
|
||||
"rule",
|
||||
"numbers",
|
||||
"snip",
|
||||
#[cfg(feature = "git")]
|
||||
"changes",
|
||||
].contains(style)
|
||||
});
|
||||
|
||||
if let Some(invalid) = invalid_vals.next() {
|
||||
Err(format!("Unknown style, '{invalid}'"))
|
||||
} else {
|
||||
Ok(val.to_owned())
|
||||
match StyleComponentList::from_str(val) {
|
||||
Err(err) => Err(err),
|
||||
Ok(_) => Ok(val.to_owned()),
|
||||
}
|
||||
})
|
||||
.help(
|
||||
@@ -447,6 +442,12 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
pre-defined style ('full'). To set a default style, add the \
|
||||
'--style=\"..\"' option to the configuration file or export the \
|
||||
BAT_STYLE environment variable (e.g.: export BAT_STYLE=\"..\").\n\n\
|
||||
When styles are specified in multiple places, the \"nearest\" set \
|
||||
of styles take precedence. The command-line arguments are the highest \
|
||||
priority, followed by the BAT_STYLE environment variable, and then \
|
||||
the configuration file. If any set of styles consists entirely of \
|
||||
components prefixed with \"+\" or \"-\", it will modify the \
|
||||
previous set of styles instead of replacing them.\n\n\
|
||||
By default, the following components are enabled:\n \
|
||||
changes, grid, header-filename, numbers, snip\n\n\
|
||||
Possible values:\n\n \
|
||||
|
@@ -146,8 +146,11 @@ pub fn get_args_from_env_vars() -> Vec<OsString> {
|
||||
("--style", "BAT_STYLE"),
|
||||
]
|
||||
.iter()
|
||||
.filter_map(|(flag, key)| env::var(key).ok().map(|var| [flag.to_string(), var]))
|
||||
.flatten()
|
||||
.filter_map(|(flag, key)| {
|
||||
env::var(key)
|
||||
.ok()
|
||||
.map(|var| [flag.to_string(), var].join("="))
|
||||
})
|
||||
.map(|a| a.into())
|
||||
.collect()
|
||||
}
|
||||
|
@@ -202,7 +202,7 @@ pub fn list_themes(cfg: &Config, config_dir: &Path, cache_dir: &Path) -> Result<
|
||||
|
||||
let default_theme = HighlightingAssets::default_theme();
|
||||
for theme in assets.themes() {
|
||||
let default_theme_info = if default_theme == theme {
|
||||
let default_theme_info = if !config.loop_through && default_theme == theme {
|
||||
" (default)"
|
||||
} else {
|
||||
""
|
||||
|
@@ -5,6 +5,7 @@ use crate::paging::PagingMode;
|
||||
use crate::style::StyleComponents;
|
||||
use crate::syntax_mapping::SyntaxMapping;
|
||||
use crate::wrapping::WrappingMode;
|
||||
use crate::StripAnsiMode;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum VisibleLines {
|
||||
@@ -100,6 +101,9 @@ pub struct Config<'a> {
|
||||
|
||||
/// The maximum number of consecutive empty lines to display
|
||||
pub squeeze_lines: Option<usize>,
|
||||
|
||||
// Weather or not to set terminal title when using a pager
|
||||
pub strip_ansi: StripAnsiMode,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "minimal-application", feature = "paging"))]
|
||||
|
@@ -53,6 +53,7 @@ mod vscreen;
|
||||
pub(crate) mod wrapping;
|
||||
|
||||
pub use nonprintable_notation::NonprintableNotation;
|
||||
pub use preprocessor::StripAnsiMode;
|
||||
pub use pretty_printer::{Input, PrettyPrinter, Syntax};
|
||||
pub use syntax_mapping::{MappingTarget, SyntaxMapping};
|
||||
pub use wrapping::WrappingMode;
|
||||
|
@@ -1,17 +1,18 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use console::AnsiCodeIterator;
|
||||
|
||||
use crate::nonprintable_notation::NonprintableNotation;
|
||||
use crate::{
|
||||
nonprintable_notation::NonprintableNotation,
|
||||
vscreen::{EscapeSequenceOffsets, EscapeSequenceOffsetsIterator},
|
||||
};
|
||||
|
||||
/// Expand tabs like an ANSI-enabled expand(1).
|
||||
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) {
|
||||
match chunk {
|
||||
(text, true) => buffer.push_str(text),
|
||||
(mut text, false) => {
|
||||
for seq in EscapeSequenceOffsetsIterator::new(line) {
|
||||
match seq {
|
||||
EscapeSequenceOffsets::Text { .. } => {
|
||||
let mut text = &line[seq.index_of_start()..seq.index_past_end()];
|
||||
while let Some(index) = text.find('\t') {
|
||||
// Add previous text.
|
||||
if index > 0 {
|
||||
@@ -31,6 +32,10 @@ pub fn expand_tabs(line: &str, width: usize, cursor: &mut usize) -> String {
|
||||
*cursor += text.len();
|
||||
buffer.push_str(text);
|
||||
}
|
||||
_ => {
|
||||
// Copy the ANSI escape sequence.
|
||||
buffer.push_str(&line[seq.index_of_start()..seq.index_past_end()])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +136,27 @@ pub fn replace_nonprintable(
|
||||
output
|
||||
}
|
||||
|
||||
/// Strips ANSI escape sequences from the input.
|
||||
pub fn strip_ansi(line: &str) -> String {
|
||||
let mut buffer = String::with_capacity(line.len());
|
||||
|
||||
for seq in EscapeSequenceOffsetsIterator::new(line) {
|
||||
if let EscapeSequenceOffsets::Text { .. } = seq {
|
||||
buffer.push_str(&line[seq.index_of_start()..seq.index_past_end()]);
|
||||
}
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Default)]
|
||||
pub enum StripAnsiMode {
|
||||
#[default]
|
||||
Never,
|
||||
Always,
|
||||
Auto,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_parse_utf8_char() {
|
||||
assert_eq!(try_parse_utf8_char(&[0x20]), Some((' ', 1)));
|
||||
@@ -174,3 +200,14 @@ fn test_try_parse_utf8_char() {
|
||||
assert_eq!(try_parse_utf8_char(&[0xef, 0x20]), None);
|
||||
assert_eq!(try_parse_utf8_char(&[0xf0, 0xf0]), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strip_ansi() {
|
||||
// The sequence detection is covered by the tests in the vscreen module.
|
||||
assert_eq!(strip_ansi("no ansi"), "no ansi");
|
||||
assert_eq!(strip_ansi("\x1B[33mone"), "one");
|
||||
assert_eq!(
|
||||
strip_ansi("\x1B]1\x07multiple\x1B[J sequences"),
|
||||
"multiple sequences"
|
||||
);
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ use crate::{
|
||||
input,
|
||||
line_range::{HighlightedLineRanges, LineRange, LineRanges},
|
||||
style::StyleComponent,
|
||||
SyntaxMapping, WrappingMode,
|
||||
StripAnsiMode, SyntaxMapping, WrappingMode,
|
||||
};
|
||||
|
||||
#[cfg(feature = "paging")]
|
||||
@@ -182,6 +182,15 @@ impl<'a> PrettyPrinter<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether to remove ANSI escape sequences from the input (default: never)
|
||||
///
|
||||
/// If `Auto` is used, escape sequences will only be removed when the input
|
||||
/// is not plain text.
|
||||
pub fn strip_ansi(&mut self, mode: StripAnsiMode) -> &mut Self {
|
||||
self.config.strip_ansi = mode;
|
||||
self
|
||||
}
|
||||
|
||||
/// Text wrapping mode (default: do not wrap)
|
||||
pub fn wrapping_mode(&mut self, mode: WrappingMode) -> &mut Self {
|
||||
self.config.wrapping_mode = mode;
|
||||
|
@@ -29,11 +29,13 @@ use crate::diff::LineChanges;
|
||||
use crate::error::*;
|
||||
use crate::input::OpenedInput;
|
||||
use crate::line_range::RangeCheckResult;
|
||||
use crate::preprocessor::strip_ansi;
|
||||
use crate::preprocessor::{expand_tabs, replace_nonprintable};
|
||||
use crate::style::StyleComponent;
|
||||
use crate::terminal::{as_terminal_escaped, to_ansi_color};
|
||||
use crate::vscreen::{AnsiStyle, EscapeSequence, EscapeSequenceIterator};
|
||||
use crate::wrapping::WrappingMode;
|
||||
use crate::StripAnsiMode;
|
||||
|
||||
const ANSI_UNDERLINE_ENABLE: EscapeSequence = EscapeSequence::CSI {
|
||||
raw_sequence: "\x1B[4m",
|
||||
@@ -207,6 +209,7 @@ pub(crate) struct InteractivePrinter<'a> {
|
||||
highlighter_from_set: Option<HighlighterFromSet<'a>>,
|
||||
background_color_highlight: Option<Color>,
|
||||
consecutive_empty_lines: usize,
|
||||
strip_ansi: bool,
|
||||
}
|
||||
|
||||
impl<'a> InteractivePrinter<'a> {
|
||||
@@ -265,20 +268,41 @@ impl<'a> InteractivePrinter<'a> {
|
||||
.content_type
|
||||
.map_or(false, |c| c.is_binary() && !config.show_nonprintable);
|
||||
|
||||
let highlighter_from_set = if is_printing_binary || !config.colored_output {
|
||||
None
|
||||
} else {
|
||||
// Determine the type of syntax for highlighting
|
||||
let syntax_in_set =
|
||||
match assets.get_syntax(config.language, input, &config.syntax_mapping) {
|
||||
Ok(syntax_in_set) => syntax_in_set,
|
||||
Err(Error::UndetectedSyntax(_)) => assets
|
||||
.find_syntax_by_name("Plain Text")?
|
||||
.expect("A plain text syntax is available"),
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
let needs_to_match_syntax = !is_printing_binary
|
||||
&& (config.colored_output || config.strip_ansi == StripAnsiMode::Auto);
|
||||
|
||||
Some(HighlighterFromSet::new(syntax_in_set, theme))
|
||||
let (is_plain_text, highlighter_from_set) = if needs_to_match_syntax {
|
||||
// Determine the type of syntax for highlighting
|
||||
const PLAIN_TEXT_SYNTAX: &str = "Plain Text";
|
||||
match assets.get_syntax(config.language, input, &config.syntax_mapping) {
|
||||
Ok(syntax_in_set) => (
|
||||
syntax_in_set.syntax.name == PLAIN_TEXT_SYNTAX,
|
||||
Some(HighlighterFromSet::new(syntax_in_set, theme)),
|
||||
),
|
||||
|
||||
Err(Error::UndetectedSyntax(_)) => (
|
||||
true,
|
||||
Some(
|
||||
assets
|
||||
.find_syntax_by_name(PLAIN_TEXT_SYNTAX)?
|
||||
.map(|s| HighlighterFromSet::new(s, theme))
|
||||
.expect("A plain text syntax is available"),
|
||||
),
|
||||
),
|
||||
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
} else {
|
||||
(false, None)
|
||||
};
|
||||
|
||||
// Determine when to strip ANSI sequences
|
||||
let strip_ansi = match config.strip_ansi {
|
||||
_ if config.show_nonprintable => false,
|
||||
StripAnsiMode::Always => true,
|
||||
StripAnsiMode::Auto if is_plain_text => false, // Plain text may already contain escape sequences.
|
||||
StripAnsiMode::Auto => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
Ok(InteractivePrinter {
|
||||
@@ -293,6 +317,7 @@ impl<'a> InteractivePrinter<'a> {
|
||||
highlighter_from_set,
|
||||
background_color_highlight,
|
||||
consecutive_empty_lines: 0,
|
||||
strip_ansi,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -573,7 +598,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
match self.content_type {
|
||||
let mut line = match self.content_type {
|
||||
Some(ContentType::BINARY) | None => {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -590,7 +615,14 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
||||
line
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// If ANSI escape sequences are supposed to be stripped, do it before syntax highlighting.
|
||||
if self.strip_ansi {
|
||||
line = strip_ansi(&line).into()
|
||||
}
|
||||
|
||||
line
|
||||
};
|
||||
|
||||
let regions = self.highlight_regions_for_line(&line)?;
|
||||
|
224
src/style.rs
224
src/style.rs
@@ -138,3 +138,227 @@ impl StyleComponents {
|
||||
self.0.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum ComponentAction {
|
||||
Override,
|
||||
Add,
|
||||
Remove,
|
||||
}
|
||||
|
||||
impl ComponentAction {
|
||||
fn extract_from_str(string: &str) -> (ComponentAction, &str) {
|
||||
match string.chars().next() {
|
||||
Some('-') => (ComponentAction::Remove, string.strip_prefix('-').unwrap()),
|
||||
Some('+') => (ComponentAction::Add, string.strip_prefix('+').unwrap()),
|
||||
_ => (ComponentAction::Override, string),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of [StyleComponent] that can be parsed from a string.
|
||||
pub struct StyleComponentList(Vec<(ComponentAction, StyleComponent)>);
|
||||
|
||||
impl StyleComponentList {
|
||||
fn expand_into(&self, components: &mut HashSet<StyleComponent>, interactive_terminal: bool) {
|
||||
for (action, component) in self.0.iter() {
|
||||
let subcomponents = component.components(interactive_terminal);
|
||||
|
||||
use ComponentAction::*;
|
||||
match action {
|
||||
Override | Add => components.extend(subcomponents),
|
||||
Remove => components.retain(|c| !subcomponents.contains(c)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if any component in the list was not prefixed with `+` or `-`.
|
||||
fn contains_override(&self) -> bool {
|
||||
self.0.iter().any(|(a, _)| *a == ComponentAction::Override)
|
||||
}
|
||||
|
||||
/// Combines multiple [StyleComponentList]s into a single [StyleComponents] set.
|
||||
///
|
||||
/// ## Precedence
|
||||
/// The most recent list will take precedence and override all previous lists
|
||||
/// unless it only contains components prefixed with `-` or `+`. When this
|
||||
/// happens, the list's components will be merged into the previous list.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```text
|
||||
/// [numbers,grid] + [header,changes] -> [header,changes]
|
||||
/// [numbers,grid] + [+header,-grid] -> [numbers,header]
|
||||
/// ```
|
||||
///
|
||||
/// ## Parameters
|
||||
/// - `with_default`: If true, the styles lists will build upon the StyleComponent::Auto style.
|
||||
pub fn to_components(
|
||||
lists: impl IntoIterator<Item = StyleComponentList>,
|
||||
interactive_terminal: bool,
|
||||
with_default: bool,
|
||||
) -> StyleComponents {
|
||||
let mut components: HashSet<StyleComponent> = HashSet::new();
|
||||
if with_default {
|
||||
components.extend(StyleComponent::Auto.components(interactive_terminal))
|
||||
}
|
||||
|
||||
StyleComponents(lists.into_iter().fold(components, |mut components, list| {
|
||||
if list.contains_override() {
|
||||
components.clear();
|
||||
}
|
||||
|
||||
list.expand_into(&mut components, interactive_terminal);
|
||||
components
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StyleComponentList {
|
||||
fn default() -> Self {
|
||||
StyleComponentList(vec![(ComponentAction::Override, StyleComponent::Default)])
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for StyleComponentList {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
Ok(StyleComponentList(
|
||||
s.split(",")
|
||||
.map(|s| ComponentAction::extract_from_str(s)) // If the component starts with "-", it's meant to be removed
|
||||
.map(|(a, s)| Ok((a, StyleComponent::from_str(s)?)))
|
||||
.collect::<Result<Vec<(ComponentAction, StyleComponent)>>>()?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::collections::HashSet;
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::ComponentAction::*;
|
||||
use super::StyleComponent;
|
||||
use super::StyleComponent::*;
|
||||
use super::StyleComponentList;
|
||||
|
||||
#[test]
|
||||
pub fn style_component_list_parse() {
|
||||
assert_eq!(
|
||||
StyleComponentList::from_str("grid,+numbers,snip,-snip,header")
|
||||
.expect("no error")
|
||||
.0,
|
||||
vec![
|
||||
(Override, Grid),
|
||||
(Add, LineNumbers),
|
||||
(Override, Snip),
|
||||
(Remove, Snip),
|
||||
(Override, Header),
|
||||
]
|
||||
);
|
||||
|
||||
assert!(StyleComponentList::from_str("not-a-component").is_err());
|
||||
assert!(StyleComponentList::from_str("grid,not-a-component").is_err());
|
||||
assert!(StyleComponentList::from_str("numbers,-not-a-component").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn style_component_list_to_components() {
|
||||
assert_eq!(
|
||||
StyleComponentList::to_components(
|
||||
vec![StyleComponentList::from_str("grid,numbers").expect("no error")],
|
||||
false,
|
||||
false
|
||||
)
|
||||
.0,
|
||||
HashSet::from([Grid, LineNumbers])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn style_component_list_to_components_removes_negated() {
|
||||
assert_eq!(
|
||||
StyleComponentList::to_components(
|
||||
vec![StyleComponentList::from_str("grid,numbers,-grid").expect("no error")],
|
||||
false,
|
||||
false
|
||||
)
|
||||
.0,
|
||||
HashSet::from([LineNumbers])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn style_component_list_to_components_expands_subcomponents() {
|
||||
assert_eq!(
|
||||
StyleComponentList::to_components(
|
||||
vec![StyleComponentList::from_str("full").expect("no error")],
|
||||
false,
|
||||
false
|
||||
)
|
||||
.0,
|
||||
HashSet::from_iter(Full.components(true).to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn style_component_list_expand_negates_subcomponents() {
|
||||
assert!(!StyleComponentList::to_components(
|
||||
vec![StyleComponentList::from_str("full,-numbers").expect("no error")],
|
||||
true,
|
||||
false
|
||||
)
|
||||
.numbers());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn style_component_list_to_components_precedence_overrides_previous_lists() {
|
||||
assert_eq!(
|
||||
StyleComponentList::to_components(
|
||||
vec![
|
||||
StyleComponentList::from_str("grid").expect("no error"),
|
||||
StyleComponentList::from_str("numbers").expect("no error"),
|
||||
],
|
||||
false,
|
||||
false
|
||||
)
|
||||
.0,
|
||||
HashSet::from([LineNumbers])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn style_component_list_to_components_precedence_merges_previous_lists() {
|
||||
assert_eq!(
|
||||
StyleComponentList::to_components(
|
||||
vec![
|
||||
StyleComponentList::from_str("grid,header").expect("no error"),
|
||||
StyleComponentList::from_str("-grid").expect("no error"),
|
||||
StyleComponentList::from_str("+numbers").expect("no error"),
|
||||
],
|
||||
false,
|
||||
false
|
||||
)
|
||||
.0,
|
||||
HashSet::from([HeaderFilename, LineNumbers])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn style_component_list_default_builds_on_auto() {
|
||||
assert_eq!(
|
||||
StyleComponentList::to_components(
|
||||
vec![StyleComponentList::from_str("-numbers").expect("no error"),],
|
||||
true,
|
||||
true
|
||||
)
|
||||
.0,
|
||||
{
|
||||
let mut expected: HashSet<StyleComponent> = HashSet::new();
|
||||
expected.extend(Auto.components(true));
|
||||
expected.remove(&LineNumbers);
|
||||
expected
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,3 @@
|
||||
# JSON Lines is a simple variation of JSON #2535
|
||||
[mappings]
|
||||
"JSON" = ["*.jsonl", "*.jsonc"]
|
||||
"JSON" = ["*.jsonl", "*.jsonc", "*.jsonld", "*.geojson"]
|
||||
|
2
src/syntax_mapping/builtins/common/50-markdown.toml
Normal file
2
src/syntax_mapping/builtins/common/50-markdown.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[mappings]
|
||||
"Markdown" = ["*.mkd"]
|
@@ -285,7 +285,7 @@ fn join(
|
||||
|
||||
/// A range of indices for a raw ANSI escape sequence.
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum EscapeSequenceOffsets {
|
||||
pub enum EscapeSequenceOffsets {
|
||||
Text {
|
||||
start: usize,
|
||||
end: usize,
|
||||
@@ -320,6 +320,32 @@ enum EscapeSequenceOffsets {
|
||||
},
|
||||
}
|
||||
|
||||
impl EscapeSequenceOffsets {
|
||||
/// Returns the byte-index of the first character in the escape sequence.
|
||||
pub fn index_of_start(&self) -> usize {
|
||||
use EscapeSequenceOffsets::*;
|
||||
match self {
|
||||
Text { start, .. } => *start,
|
||||
Unknown { start, .. } => *start,
|
||||
NF { start_sequence, .. } => *start_sequence,
|
||||
OSC { start_sequence, .. } => *start_sequence,
|
||||
CSI { start_sequence, .. } => *start_sequence,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the byte-index past the last character in the escape sequence.
|
||||
pub fn index_past_end(&self) -> usize {
|
||||
use EscapeSequenceOffsets::*;
|
||||
match self {
|
||||
Text { end, .. } => *end,
|
||||
Unknown { end, .. } => *end,
|
||||
NF { end, .. } => *end,
|
||||
OSC { end, .. } => *end,
|
||||
CSI { end, .. } => *end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the offests of ANSI/VT escape sequences within a string.
|
||||
///
|
||||
/// ## Example
|
||||
@@ -327,7 +353,7 @@ enum EscapeSequenceOffsets {
|
||||
/// ```ignore
|
||||
/// let iter = EscapeSequenceOffsetsIterator::new("\x1B[33mThis is yellow text.\x1B[m");
|
||||
/// ```
|
||||
struct EscapeSequenceOffsetsIterator<'a> {
|
||||
pub struct EscapeSequenceOffsetsIterator<'a> {
|
||||
text: &'a str,
|
||||
chars: Peekable<CharIndices<'a>>,
|
||||
}
|
||||
|
Reference in New Issue
Block a user