Bläddra i källkod

perf: use a hashmap when possible for file extension matching

5225225 11 månader sedan
förälder
incheckning
42ffb5f583
1 ändrade filer med 100 tillägg och 12 borttagningar
  1. 100 12
      src/theme/mod.rs

+ 100 - 12
src/theme/mod.rs

@@ -6,6 +6,8 @@
 // SPDX-License-Identifier: MIT
 use nu_ansi_term::Style;
 
+use std::collections::HashMap;
+
 use crate::fs::File;
 use crate::info::filetype::FileType;
 use crate::options::config::ThemeConfig;
@@ -205,16 +207,86 @@ where
 
 #[derive(PartialEq, Debug, Default)]
 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 {
+    // 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 {
         !self.mappings.is_empty()
     }
 
     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 {
     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());
-                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) => {
@@ -537,7 +624,8 @@ mod customs_test {
 
                 let mut result = UiStyles::default();
                 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);
             }
         };
@@ -682,9 +770,9 @@ mod customs_test {
     // can’t be tested here, because they’ll both be added to the same vec
 
     // 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 [
         ("1*1", Red.normal()), ("2*2", Green.normal()), ("3*3", Yellow.bold()), ("4*4", Blue.bold()), ("5*5", Purple.underline())