perf: switch to globset to reduce CPU time spent on matching icons (#908)

This commit is contained in:
三咲雅 · Misaki Masa 2024-04-14 11:11:31 +08:00 committed by GitHub
parent 0cc14f44d7
commit a70374fced
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 53 additions and 77 deletions

15
Cargo.lock generated
View file

@ -852,10 +852,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "glob"
version = "0.3.1"
name = "globset"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "half"
@ -2719,7 +2726,7 @@ dependencies = [
"anyhow",
"arc-swap",
"crossterm",
"glob",
"globset",
"indexmap",
"ratatui",
"serde",

View file

@ -1 +1 @@
{"language":"en","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG"],"version":"0.2"}
{"flagWords":[],"language":"en","version":"0.2","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset"]}

View file

@ -15,7 +15,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
anyhow = "1.0.82"
arc-swap = "1.7.1"
crossterm = "0.27.0"
glob = "0.3.1"
globset = "0.4.14"
indexmap = "2.2.6"
ratatui = "0.26.1"
serde = { version = "1.0.197", features = [ "derive" ] }

View file

@ -173,22 +173,14 @@ rules = [
{ mime = "image/*", fg = "cyan" },
# Media
{ mime = "video/*", fg = "yellow" },
{ mime = "audio/*", fg = "yellow" },
{ mime = "{audio,video}/*", fg = "yellow" },
# Archives
{ mime = "application/*zip", fg = "magenta" },
{ mime = "application/x-tar", fg = "magenta" },
{ mime = "application/x-bzip*", fg = "magenta" },
{ mime = "application/x-7z-compressed", fg = "magenta" },
{ mime = "application/x-rar", fg = "magenta" },
{ mime = "application/x-xz", fg = "magenta" },
{ mime = "application/*zip", fg = "magenta" },
{ mime = "application/x-{tar,bzip*,7z-compressed,xz,rar}", fg = "magenta" },
# Documents
{ mime = "application/doc", fg = "green" },
{ mime = "application/pdf", fg = "green" },
{ mime = "application/rtf", fg = "green" },
{ mime = "application/vnd.*", fg = "green" },
{ mime = "application/{pdf,doc,rtf,vnd.*}", fg = "green" },
# Fallback
# { name = "*", fg = "white" },

View file

@ -55,22 +55,17 @@ play = [
rules = [
{ name = "*/", use = [ "edit", "open", "reveal" ] },
{ mime = "text/*", use = [ "edit", "reveal" ] },
{ mime = "image/*", use = [ "open", "reveal" ] },
{ mime = "video/*", use = [ "play", "reveal" ] },
{ mime = "audio/*", use = [ "play", "reveal" ] },
{ mime = "inode/x-empty", use = [ "edit", "reveal" ] },
{ mime = "text/*", use = [ "edit", "reveal" ] },
{ mime = "image/*", use = [ "open", "reveal" ] },
{ mime = "{audio,video}/*", use = [ "play", "reveal" ] },
{ mime = "inode/x-empty", use = [ "edit", "reveal" ] },
{ mime = "application/*zip", use = [ "extract", "reveal" ] },
{ mime = "application/x-{tar,bzip*,7z-compressed,xz,rar}", use = [ "extract", "reveal" ] },
{ mime = "application/json", use = [ "edit", "reveal" ] },
{ mime = "*/javascript", use = [ "edit", "reveal" ] },
{ mime = "application/*zip", use = [ "extract", "reveal" ] },
{ mime = "application/x-tar", use = [ "extract", "reveal" ] },
{ mime = "application/x-bzip*", use = [ "extract", "reveal" ] },
{ mime = "application/x-7z-compressed", use = [ "extract", "reveal" ] },
{ mime = "application/x-rar", use = [ "extract", "reveal" ] },
{ mime = "application/x-xz", use = [ "extract", "reveal" ] },
{ mime = "*", use = [ "open", "reveal" ] },
]
@ -96,10 +91,8 @@ preloaders = [
previewers = [
{ name = "*/", run = "folder", sync = true },
# Code
{ mime = "text/*", run = "code" },
{ mime = "*/xml", run = "code" },
{ mime = "*/javascript", run = "code" },
{ mime = "*/x-wine-extension-ini", run = "code" },
{ mime = "text/*", run = "code" },
{ mime = "*/{xml,javascript,x-wine-extension-ini}", run = "code" },
# JSON
{ mime = "application/json", run = "json" },
# Image
@ -110,12 +103,8 @@ previewers = [
# PDF
{ mime = "application/pdf", run = "pdf" },
# Archive
{ mime = "application/*zip", run = "archive" },
{ mime = "application/x-tar", run = "archive" },
{ mime = "application/x-bzip*", run = "archive" },
{ mime = "application/x-7z-compressed", run = "archive" },
{ mime = "application/x-rar", run = "archive" },
{ mime = "application/x-xz", run = "archive" },
{ mime = "application/*zip", run = "archive" },
{ mime = "application/x-{tar,bzip*,7z-compressed,xz,rar}", run = "archive" },
# Fallback
{ name = "*", run = "file" },
]

View file

@ -25,8 +25,8 @@ impl Open {
{
let is_folder = mime.as_ref() == MIME_DIR;
self.rules.iter().find_map(|rule| {
if rule.mime.as_ref().is_some_and(|m| m.matches(&mime))
|| rule.name.as_ref().is_some_and(|n| n.match_path(&path, is_folder))
if rule.mime.as_ref().is_some_and(|p| p.match_mime(&mime))
|| rule.name.as_ref().is_some_and(|p| p.match_path(&path, is_folder))
{
let openers = rule
.use_

View file

@ -1,46 +1,30 @@
use std::path::Path;
use glob::MatchOptions;
use globset::GlobBuilder;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[serde(try_from = "String")]
pub struct Pattern {
inner: glob::Pattern,
sensitive: bool,
inner: globset::GlobMatcher,
is_star: bool,
is_folder: bool,
full_path: bool,
}
impl Pattern {
#[inline]
pub fn matches(&self, str: impl AsRef<str>) -> bool {
self.inner.matches_with(str.as_ref(), MatchOptions {
case_sensitive: self.sensitive,
require_literal_separator: self.full_path,
require_literal_leading_dot: false,
})
}
pub fn match_mime(&self, str: impl AsRef<str>) -> bool { self.inner.is_match(str.as_ref()) }
#[inline]
pub fn match_path(&self, path: impl AsRef<Path>, is_folder: bool) -> bool {
if is_folder != self.is_folder {
return false;
}
let path = path.as_ref();
self.matches(if self.full_path {
path.to_string_lossy()
} else {
path.file_name().map_or_else(|| path.to_string_lossy(), |n| n.to_string_lossy())
})
is_folder == self.is_folder && (self.is_star || self.inner.is_match(path))
}
#[inline]
pub fn any_file(&self) -> bool { !self.is_folder && self.inner.as_str() == "*" }
pub fn any_file(&self) -> bool { self.is_star && !self.is_folder }
#[inline]
pub fn any_dir(&self) -> bool { self.is_folder && self.inner.as_str() == "*" }
pub fn any_dir(&self) -> bool { self.is_star && self.is_folder }
}
impl TryFrom<&str> for Pattern {
@ -49,12 +33,16 @@ impl TryFrom<&str> for Pattern {
fn try_from(s: &str) -> Result<Self, Self::Error> {
let a = s.trim_start_matches("\\s");
let b = a.trim_end_matches('/');
Ok(Self {
inner: glob::Pattern::new(b)?,
sensitive: a.len() < s.len(),
is_folder: b.len() < a.len(),
full_path: b.contains('/'),
})
let inner = GlobBuilder::new(b)
.case_insensitive(a.len() == s.len())
.literal_separator(b.contains('/'))
.backslash_escape(false)
.empty_alternates(false)
.build()?
.compile_matcher();
Ok(Self { inner, is_star: b == "*", is_folder: b.len() < a.len() })
}
}

View file

@ -73,8 +73,8 @@ impl Plugin {
.iter()
.filter(|&rule| {
rule.cond.as_ref().and_then(|c| c.eval(f)) != Some(false)
&& (rule.mime.as_ref().zip(mime).map_or(false, |(m, s)| m.matches(s))
|| rule.name.as_ref().is_some_and(|n| n.match_path(path, is_folder)))
&& (rule.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m))
|| rule.name.as_ref().is_some_and(|p| p.match_path(path, is_folder)))
})
.collect()
}
@ -82,8 +82,8 @@ impl Plugin {
pub fn previewer(&self, path: &Path, mime: &str) -> Option<&PluginRule> {
let is_folder = mime == MIME_DIR;
self.previewers.iter().find(|&rule| {
rule.mime.as_ref().is_some_and(|m| m.matches(mime))
|| rule.name.as_ref().is_some_and(|n| n.match_path(path, is_folder))
rule.mime.as_ref().is_some_and(|p| p.match_mime(mime))
|| rule.name.as_ref().is_some_and(|p| p.match_path(path, is_folder))
})
}
}

View file

@ -17,8 +17,8 @@ impl Filetype {
return false;
}
self.name.as_ref().is_some_and(|n| n.match_path(&file.url, file.is_dir()))
|| self.mime.as_ref().zip(mime).map_or(false, |(m, s)| m.matches(s))
self.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m))
|| self.name.as_ref().is_some_and(|n| n.match_path(&file.url, file.is_dir()))
}
}