diff --git a/config/docs/yazi.md b/config/docs/yazi.md index 0c609692..fabc07b2 100644 --- a/config/docs/yazi.md +++ b/config/docs/yazi.md @@ -81,6 +81,9 @@ rules = [ # { mime = "application/json", use = "text" }, { name = "*.json", use = "text" }, + + # Multiple openers for a single rule + { name = "*.html", use = [ "browser", "text" ] }, ] ``` diff --git a/config/src/open/mod.rs b/config/src/open/mod.rs index 5aea8dfe..085ff005 100644 --- a/config/src/open/mod.rs +++ b/config/src/open/mod.rs @@ -1,5 +1,7 @@ mod open; mod opener; +mod rule; pub use open::*; pub use opener::*; +use rule::*; diff --git a/config/src/open/open.rs b/config/src/open/open.rs index 8575301c..879b6b3b 100644 --- a/config/src/open/open.rs +++ b/config/src/open/open.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Deserializer}; use shared::MIME_DIR; use super::Opener; -use crate::{Pattern, MERGED_YAZI}; +use crate::{open::OpenRule, MERGED_YAZI}; #[derive(Debug)] pub struct Open { @@ -13,20 +13,12 @@ pub struct Open { rules: Vec, } -#[derive(Debug, Deserialize)] -struct OpenRule { - name: Option, - mime: Option, - #[serde(rename = "use")] - use_: String, -} - impl Default for Open { fn default() -> Self { toml::from_str(&MERGED_YAZI).unwrap() } } impl Open { - pub fn openers(&self, path: P, mime: M) -> Option<&IndexSet> + pub fn openers(&self, path: P, mime: M) -> Option> where P: AsRef, M: AsRef, @@ -36,7 +28,14 @@ impl Open { if rule.mime.as_ref().map_or(false, |m| m.matches(&mime)) || rule.name.as_ref().map_or(false, |n| n.match_path(&path, is_folder)) { - self.openers.get(&rule.use_) + let openers = rule + .use_ + .iter() + .filter_map(|use_| self.openers.get(use_)) + .flatten() + .collect::>(); + + if openers.is_empty() { None } else { Some(openers) } } else { None } @@ -49,12 +48,12 @@ impl Open { P: AsRef, M: AsRef, { - self.openers(path, mime).and_then(|o| o.iter().find(|o| o.block)) + self.openers(path, mime).and_then(|o| o.into_iter().find(|o| o.block)) } pub fn common_openers(&self, targets: &[(impl AsRef, impl AsRef)]) -> Vec<&Opener> { - let grouped = targets.iter().filter_map(|(p, m)| self.openers(p, m)).collect::>(); - let flat = grouped.iter().flat_map(|&g| g).collect::>(); + let grouped: Vec<_> = targets.iter().filter_map(|(p, m)| self.openers(p, m)).collect(); + let flat: IndexSet<_> = grouped.iter().flatten().copied().collect(); flat.into_iter().filter(|&o| grouped.iter().all(|g| g.contains(o))).collect() } } diff --git a/config/src/open/rule.rs b/config/src/open/rule.rs new file mode 100644 index 00000000..f4ce3daa --- /dev/null +++ b/config/src/open/rule.rs @@ -0,0 +1,58 @@ +use std::fmt; + +use serde::{de::{self, Visitor}, Deserialize, Deserializer}; + +use crate::pattern::Pattern; + +#[derive(Debug, Deserialize)] +pub(super) struct OpenRule { + pub(super) name: Option, + pub(super) mime: Option, + #[serde(rename = "use")] + #[serde(deserialize_with = "OpenRule::deserialize")] + pub(super) use_: Vec, +} + +impl OpenRule { + fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + struct UseVisitor; + + impl<'de> Visitor<'de> for UseVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string, or array of strings") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: de::SeqAccess<'de>, + { + let mut uses = Vec::new(); + while let Some(use_) = seq.next_element::()? { + uses.push(use_); + } + Ok(uses) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(vec![value.to_owned()]) + } + + fn visit_string(self, v: String) -> Result + where + E: de::Error, + { + Ok(vec![v]) + } + } + + deserializer.deserialize_any(UseVisitor) + } +} diff --git a/core/src/tasks/tasks.rs b/core/src/tasks/tasks.rs index c75ec048..f11de926 100644 --- a/core/src/tasks/tasks.rs +++ b/core/src/tasks/tasks.rs @@ -134,7 +134,7 @@ impl Tasks { pub fn file_open(&self, targets: &[(impl AsRef, impl AsRef)]) -> bool { let mut openers = BTreeMap::new(); for (path, mime) in targets { - if let Some(opener) = OPEN.openers(path, mime).and_then(|o| o.first()) { + if let Some(opener) = OPEN.openers(path, mime).and_then(|o| o.first().copied()) { openers.entry(opener).or_insert_with(Vec::new).push(path.as_ref().as_os_str()); } }