|
@@ -6,6 +6,8 @@
|
|
|
// SPDX-License-Identifier: MIT
|
|
// SPDX-License-Identifier: MIT
|
|
|
use nu_ansi_term::Style;
|
|
use nu_ansi_term::Style;
|
|
|
|
|
|
|
|
|
|
+use std::collections::HashMap;
|
|
|
|
|
+
|
|
|
use crate::fs::File;
|
|
use crate::fs::File;
|
|
|
use crate::info::filetype::FileType;
|
|
use crate::info::filetype::FileType;
|
|
|
use crate::options::config::ThemeConfig;
|
|
use crate::options::config::ThemeConfig;
|
|
@@ -205,16 +207,86 @@ where
|
|
|
|
|
|
|
|
#[derive(PartialEq, Debug, Default)]
|
|
#[derive(PartialEq, Debug, Default)]
|
|
|
struct ExtensionMappings {
|
|
struct ExtensionMappings {
|
|
|
- mappings: Vec<(glob::Pattern, Style)>,
|
|
|
|
|
|
|
+ mappings: Vec<GlobPattern>,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#[derive(PartialEq, Debug)]
|
|
|
|
|
+enum GlobPattern {
|
|
|
|
|
+ Complex(glob::Pattern, Style),
|
|
|
|
|
+
|
|
|
|
|
+ // TODO: the String here should probably be an inline string, since we're likely working with
|
|
|
|
|
+ // small strings (file extensions).
|
|
|
|
|
+ Simple(HashMap<String, Style>),
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
impl ExtensionMappings {
|
|
impl ExtensionMappings {
|
|
|
|
|
+ // helper for test suite
|
|
|
|
|
+ #[cfg(all(test, unix))]
|
|
|
|
|
+ fn to_vec_pat_style(&self) -> Vec<(glob::Pattern, Style)> {
|
|
|
|
|
+ let mut out = Vec::new();
|
|
|
|
|
+ for map in &self.mappings {
|
|
|
|
|
+ match map {
|
|
|
|
|
+ GlobPattern::Complex(p, s) => {
|
|
|
|
|
+ out.push((p.clone(), *s));
|
|
|
|
|
+ }
|
|
|
|
|
+ GlobPattern::Simple(h) => {
|
|
|
|
|
+ let mut simple_pats = h
|
|
|
|
|
+ .iter()
|
|
|
|
|
+ .map(|(k, v)| (glob::Pattern::new(&format!("*.{}", k)).unwrap(), *v))
|
|
|
|
|
+ .collect::<Vec<(glob::Pattern, Style)>>();
|
|
|
|
|
+
|
|
|
|
|
+ simple_pats.sort_by_key(|x| x.0.clone());
|
|
|
|
|
+
|
|
|
|
|
+ out.extend(simple_pats);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ out
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
fn is_non_empty(&self) -> bool {
|
|
fn is_non_empty(&self) -> bool {
|
|
|
!self.mappings.is_empty()
|
|
!self.mappings.is_empty()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
fn add(&mut self, pattern: glob::Pattern, style: Style) {
|
|
fn add(&mut self, pattern: glob::Pattern, style: Style) {
|
|
|
- self.mappings.push((pattern, style));
|
|
|
|
|
|
|
+ match self.mappings.last_mut() {
|
|
|
|
|
+ None => match is_simple_pattern(pattern) {
|
|
|
|
|
+ Ok(s) => {
|
|
|
|
|
+ self.mappings
|
|
|
|
|
+ .push(GlobPattern::Simple(HashMap::from([(s, style)])));
|
|
|
|
|
+ }
|
|
|
|
|
+ Err(p) => {
|
|
|
|
|
+ self.mappings.push(GlobPattern::Complex(p, style));
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ Some(gp) => match (gp, is_simple_pattern(pattern)) {
|
|
|
|
|
+ (GlobPattern::Simple(h), Ok(s)) => {
|
|
|
|
|
+ h.insert(s, style);
|
|
|
|
|
+ }
|
|
|
|
|
+ (_, Ok(s)) => {
|
|
|
|
|
+ self.mappings
|
|
|
|
|
+ .push(GlobPattern::Simple(HashMap::from([(s, style)])));
|
|
|
|
|
+ }
|
|
|
|
|
+ (_, Err(p)) => {
|
|
|
|
|
+ self.mappings.push(GlobPattern::Complex(p, style));
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+fn is_simple_pattern(pattern: glob::Pattern) -> Result<String, glob::Pattern> {
|
|
|
|
|
+ if let Some(ext) = pattern.as_str().strip_prefix("*.") {
|
|
|
|
|
+ // Maybe too pessimistic here, some of these might be valid.
|
|
|
|
|
+ // For example, '*.[*]' is a simple pattern.
|
|
|
|
|
+ // Ideally we'd inspect pattern.tokens, but it's not public.
|
|
|
|
|
+ if ext.contains(['?', '*', '[', ']', '.']) {
|
|
|
|
|
+ Err(pattern)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Ok(ext.to_string())
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Err(pattern)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -223,11 +295,26 @@ impl ExtensionMappings {
|
|
|
|
|
|
|
|
impl FileStyle for ExtensionMappings {
|
|
impl FileStyle for ExtensionMappings {
|
|
|
fn get_style(&self, file: &File<'_>, _theme: &Theme) -> Option<Style> {
|
|
fn get_style(&self, file: &File<'_>, _theme: &Theme) -> Option<Style> {
|
|
|
- self.mappings
|
|
|
|
|
- .iter()
|
|
|
|
|
- .rev()
|
|
|
|
|
- .find(|t| t.0.matches(&file.name))
|
|
|
|
|
- .map(|t| t.1)
|
|
|
|
|
|
|
+ let maybe_ext = file.name.rsplit_once('.').map(|x| x.1);
|
|
|
|
|
+
|
|
|
|
|
+ for mapping in self.mappings.iter().rev() {
|
|
|
|
|
+ match mapping {
|
|
|
|
|
+ GlobPattern::Complex(pat, style) => {
|
|
|
|
|
+ if pat.matches(&file.name) {
|
|
|
|
|
+ return Some(*style);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ GlobPattern::Simple(map) => {
|
|
|
|
|
+ if let Some(ext) = maybe_ext {
|
|
|
|
|
+ if let Some(style) = map.get(ext) {
|
|
|
|
|
+ return Some(*style);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ None
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -516,7 +603,7 @@ mod customs_test {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
let (result, _) = definitions.parse_color_vars(&mut UiStyles::default());
|
|
let (result, _) = definitions.parse_color_vars(&mut UiStyles::default());
|
|
|
- assert_eq!(ExtensionMappings { mappings }, result);
|
|
|
|
|
|
|
+ assert_eq!(mappings, result.to_vec_pat_style());
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
($name:ident: ls $ls:expr, exa $exa:expr => colours $expected:ident -> $process_expected:expr, exts $mappings:expr) => {
|
|
($name:ident: ls $ls:expr, exa $exa:expr => colours $expected:ident -> $process_expected:expr, exts $mappings:expr) => {
|
|
@@ -537,7 +624,8 @@ mod customs_test {
|
|
|
|
|
|
|
|
let mut result = UiStyles::default();
|
|
let mut result = UiStyles::default();
|
|
|
let (exts, _) = definitions.parse_color_vars(&mut result);
|
|
let (exts, _) = definitions.parse_color_vars(&mut result);
|
|
|
- assert_eq!(ExtensionMappings { mappings }, exts);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ assert_eq!(mappings, exts.to_vec_pat_style());
|
|
|
assert_eq!($expected, result);
|
|
assert_eq!($expected, result);
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
@@ -682,9 +770,9 @@ mod customs_test {
|
|
|
// can’t be tested here, because they’ll both be added to the same vec
|
|
// can’t be tested here, because they’ll both be added to the same vec
|
|
|
|
|
|
|
|
// Values get separated by colons:
|
|
// Values get separated by colons:
|
|
|
- test!(ls_multi: ls "*.txt=31:*.rtf=32", exa "" => exts [ ("*.txt", Red.normal()), ("*.rtf", Green.normal()) ]);
|
|
|
|
|
- test!(exa_multi: ls "", exa "*.tmp=37:*.log=37" => exts [ ("*.tmp", White.normal()), ("*.log", White.normal()) ]);
|
|
|
|
|
- test!(ls_exa_multi: ls "*.txt=31", exa "*.rtf=32" => exts [ ("*.txt", Red.normal()), ("*.rtf", Green.normal())]);
|
|
|
|
|
|
|
+ test!(ls_multi: ls "*.txt=31:*.rtf=32", exa "" => exts [ ("*.rtf", Green.normal()), ("*.txt", Red.normal()) ]);
|
|
|
|
|
+ test!(exa_multi: ls "", exa "*.tmp=37:*.log=37" => exts [ ("*.log", White.normal()), ("*.tmp", White.normal()) ]);
|
|
|
|
|
+ test!(ls_exa_multi: ls "*.txt=31", exa "*.rtf=32" => exts [ ("*.rtf", Green.normal()), ("*.txt", Red.normal())]);
|
|
|
|
|
|
|
|
test!(ls_five: ls "1*1=31:2*2=32:3*3=1;33:4*4=34;1:5*5=35;4", exa "" => exts [
|
|
test!(ls_five: ls "1*1=31:2*2=32:3*3=1;33:4*4=34;1:5*5=35;4", exa "" => exts [
|
|
|
("1*1", Red.normal()), ("2*2", Green.normal()), ("3*3", Yellow.bold()), ("4*4", Blue.bold()), ("5*5", Purple.underline())
|
|
("1*1", Red.normal()), ("2*2", Green.normal()), ("3*3", Yellow.bold()), ("4*4", Blue.bold()), ("5*5", Purple.underline())
|