diff --git a/Cargo.lock b/Cargo.lock index ab2d2cc83..d34aefd88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -412,9 +412,8 @@ dependencies = [ [[package]] name = "codex" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "724d27a0ee38b700e5e164350e79aba601a0db673ac47fce1cb74c3e38864036" +version = "0.1.0" +source = "git+https://github.com/typst/codex?rev=7157b82d817b362c7d71b223681deef61f294f1d#7157b82d817b362c7d71b223681deef61f294f1d" [[package]] name = "color-print" @@ -2914,6 +2913,7 @@ name = "typst-docs" version = "0.13.1" dependencies = [ "clap", + "codex", "ecow", "heck", "pulldown-cmark", diff --git a/Cargo.toml b/Cargo.toml index 12870b809..fd6011c03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,9 @@ clap = { version = "4.4", features = ["derive", "env", "wrap_help"] } clap_complete = "4.2.1" clap_mangen = "0.2.10" codespan-reporting = "0.11" -codex = "0.1.1" +# TODO: change this when codex#46 gets merged and codex gets released with that +# codex = "0.1.1" +codex = { git = "https://github.com/typst/codex", rev = "7157b82d817b362c7d71b223681deef61f294f1d" } color-print = "0.3.6" comemo = "0.4" csv = "1" diff --git a/crates/typst-library/src/foundations/symbol.rs b/crates/typst-library/src/foundations/symbol.rs index 50fcfb403..8f899cc77 100644 --- a/crates/typst-library/src/foundations/symbol.rs +++ b/crates/typst-library/src/foundations/symbol.rs @@ -1,8 +1,8 @@ -use std::cmp::Reverse; use std::collections::{BTreeSet, HashMap}; use std::fmt::{self, Debug, Display, Formatter, Write}; use std::sync::Arc; +use codex::ModifierSet; use ecow::{eco_format, EcoString}; use serde::{Serialize, Serializer}; use typst_syntax::{is_ident, Span, Spanned}; @@ -54,18 +54,18 @@ enum Repr { /// A native symbol that has no named variant. Single(char), /// A native symbol with multiple named variants. - Complex(&'static [(&'static str, char)]), + Complex(&'static [(ModifierSet<&'static str>, char)]), /// A symbol with multiple named variants, where some modifiers may have /// been applied. Also used for symbols defined at runtime by the user with /// no modifier applied. - Modified(Arc<(List, EcoString)>), + Modified(Arc<(List, ModifierSet)>), } /// A collection of symbols. #[derive(Clone, Eq, PartialEq, Hash)] enum List { - Static(&'static [(&'static str, char)]), - Runtime(Box<[(EcoString, char)]>), + Static(&'static [(ModifierSet<&'static str>, char)]), + Runtime(Box<[(ModifierSet, char)]>), } impl Symbol { @@ -76,24 +76,26 @@ impl Symbol { /// Create a symbol with a static variant list. #[track_caller] - pub const fn list(list: &'static [(&'static str, char)]) -> Self { + pub const fn list(list: &'static [(ModifierSet<&'static str>, char)]) -> Self { debug_assert!(!list.is_empty()); Self(Repr::Complex(list)) } /// Create a symbol with a runtime variant list. #[track_caller] - pub fn runtime(list: Box<[(EcoString, char)]>) -> Self { + pub fn runtime(list: Box<[(ModifierSet, char)]>) -> Self { debug_assert!(!list.is_empty()); - Self(Repr::Modified(Arc::new((List::Runtime(list), EcoString::new())))) + Self(Repr::Modified(Arc::new((List::Runtime(list), ModifierSet::default())))) } /// Get the symbol's character. pub fn get(&self) -> char { match &self.0 { Repr::Single(c) => *c, - Repr::Complex(_) => find(self.variants(), "").unwrap(), - Repr::Modified(arc) => find(self.variants(), &arc.1).unwrap(), + Repr::Complex(_) => ModifierSet::<&'static str>::default() + .best_match_in(self.variants()) + .unwrap(), + Repr::Modified(arc) => arc.1.best_match_in(self.variants()).unwrap(), } } @@ -128,16 +130,14 @@ impl Symbol { /// Apply a modifier to the symbol. pub fn modified(mut self, modifier: &str) -> StrResult { if let Repr::Complex(list) = self.0 { - self.0 = Repr::Modified(Arc::new((List::Static(list), EcoString::new()))); + self.0 = + Repr::Modified(Arc::new((List::Static(list), ModifierSet::default()))); } if let Repr::Modified(arc) = &mut self.0 { let (list, modifiers) = Arc::make_mut(arc); - if !modifiers.is_empty() { - modifiers.push('.'); - } - modifiers.push_str(modifier); - if find(list.variants(), modifiers).is_some() { + modifiers.add_unchecked(modifier); + if modifiers.best_match_in(list.variants()).is_some() { return Ok(self); } } @@ -146,7 +146,7 @@ impl Symbol { } /// The characters that are covered by this symbol. - pub fn variants(&self) -> impl Iterator { + pub fn variants(&self) -> impl Iterator, char)> { match &self.0 { Repr::Single(c) => Variants::Single(Some(*c).into_iter()), Repr::Complex(list) => Variants::Static(list.iter()), @@ -156,17 +156,15 @@ impl Symbol { /// Possible modifiers. pub fn modifiers(&self) -> impl Iterator + '_ { - let mut set = BTreeSet::new(); let modifiers = match &self.0 { - Repr::Modified(arc) => arc.1.as_str(), - _ => "", + Repr::Modified(arc) => arc.1.as_deref(), + _ => ModifierSet::default(), }; - for modifier in self.variants().flat_map(|(name, _)| name.split('.')) { - if !modifier.is_empty() && !contained(modifiers, modifier) { - set.insert(modifier); - } - } - set.into_iter() + self.variants() + .flat_map(|(m, _)| m) + .filter(|modifier| !modifier.is_empty() && !modifiers.contains(modifier)) + .collect::>() + .into_iter() } } @@ -256,7 +254,10 @@ impl Symbol { seen.insert(hash, i); } - let list = variants.into_iter().map(|s| (s.v.0, s.v.1)).collect(); + let list = variants + .into_iter() + .map(|s| (ModifierSet::new_unchecked(s.v.0), s.v.1)) + .collect(); Ok(Symbol::runtime(list)) } } @@ -291,14 +292,23 @@ impl crate::foundations::Repr for Symbol { match &self.0 { Repr::Single(c) => eco_format!("symbol(\"{}\")", *c), Repr::Complex(variants) => { - eco_format!("symbol{}", repr_variants(variants.iter().copied(), "")) + eco_format!( + "symbol{}", + repr_variants(variants.iter().copied(), ModifierSet::default()) + ) } Repr::Modified(arc) => { let (list, modifiers) = arc.as_ref(); if modifiers.is_empty() { - eco_format!("symbol{}", repr_variants(list.variants(), "")) + eco_format!( + "symbol{}", + repr_variants(list.variants(), ModifierSet::default()) + ) } else { - eco_format!("symbol{}", repr_variants(list.variants(), modifiers)) + eco_format!( + "symbol{}", + repr_variants(list.variants(), modifiers.as_deref()) + ) } } } @@ -306,24 +316,24 @@ impl crate::foundations::Repr for Symbol { } fn repr_variants<'a>( - variants: impl Iterator, - applied_modifiers: &str, + variants: impl Iterator, char)>, + applied_modifiers: ModifierSet<&str>, ) -> String { crate::foundations::repr::pretty_array_like( &variants - .filter(|(variant, _)| { + .filter(|(modifiers, _)| { // Only keep variants that can still be accessed, i.e., variants // that contain all applied modifiers. - parts(applied_modifiers).all(|am| variant.split('.').any(|m| m == am)) + applied_modifiers.iter().all(|am| modifiers.contains(am)) }) - .map(|(variant, c)| { - let trimmed_variant = variant - .split('.') - .filter(|&m| parts(applied_modifiers).all(|am| m != am)); - if trimmed_variant.clone().all(|m| m.is_empty()) { + .map(|(modifiers, c)| { + let trimmed_modifiers = + modifiers.into_iter().filter(|&m| !applied_modifiers.contains(m)); + if trimmed_modifiers.clone().all(|m| m.is_empty()) { eco_format!("\"{c}\"") } else { - let trimmed_modifiers = trimmed_variant.collect::>().join("."); + let trimmed_modifiers = + trimmed_modifiers.collect::>().join("."); eco_format!("(\"{}\", \"{}\")", trimmed_modifiers, c) } }) @@ -369,67 +379,22 @@ cast! { /// Iterator over variants. enum Variants<'a> { Single(std::option::IntoIter), - Static(std::slice::Iter<'static, (&'static str, char)>), - Runtime(std::slice::Iter<'a, (EcoString, char)>), + Static(std::slice::Iter<'static, (ModifierSet<&'static str>, char)>), + Runtime(std::slice::Iter<'a, (ModifierSet, char)>), } impl<'a> Iterator for Variants<'a> { - type Item = (&'a str, char); + type Item = (ModifierSet<&'a str>, char); fn next(&mut self) -> Option { match self { - Self::Single(iter) => Some(("", iter.next()?)), + Self::Single(iter) => Some((ModifierSet::default(), iter.next()?)), Self::Static(list) => list.next().copied(), - Self::Runtime(list) => list.next().map(|(s, c)| (s.as_str(), *c)), + Self::Runtime(list) => list.next().map(|(m, c)| (m.as_deref(), *c)), } } } -/// Find the best symbol from the list. -fn find<'a>( - variants: impl Iterator, - modifiers: &str, -) -> Option { - let mut best = None; - let mut best_score = None; - - // Find the best table entry with this name. - 'outer: for candidate in variants { - for modifier in parts(modifiers) { - if !contained(candidate.0, modifier) { - continue 'outer; - } - } - - let mut matching = 0; - let mut total = 0; - for modifier in parts(candidate.0) { - if contained(modifiers, modifier) { - matching += 1; - } - total += 1; - } - - let score = (matching, Reverse(total)); - if best_score.is_none_or(|b| score > b) { - best = Some(candidate.1); - best_score = Some(score); - } - } - - best -} - -/// Split a modifier list into its parts. -fn parts(modifiers: &str) -> impl Iterator { - modifiers.split('.').filter(|s| !s.is_empty()) -} - -/// Whether the modifier string contains the modifier `m`. -fn contained(modifiers: &str, m: &str) -> bool { - parts(modifiers).any(|part| part == m) -} - /// A single character. #[elem(Repr, PlainText)] pub struct SymbolElem { diff --git a/docs/Cargo.toml b/docs/Cargo.toml index acc551754..64e261289 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -22,6 +22,7 @@ typst-utils = { workspace = true } typst-assets = { workspace = true, features = ["fonts"] } typst-dev-assets = { workspace = true } clap = { workspace = true, optional = true } +codex = { workspace = true } ecow = { workspace = true } heck = { workspace = true } pulldown-cmark = { workspace = true } diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 091bb1b24..28cd37d2b 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -712,11 +712,11 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel { let mut list = vec![]; for (name, binding) in group.module().scope().iter() { let Value::Symbol(symbol) = binding.read() else { continue }; - let complete = |variant: &str| { + let complete = |variant: codex::ModifierSet<&str>| { if variant.is_empty() { name.clone() } else { - eco_format!("{}.{}", name, variant) + eco_format!("{}.{}", name, variant.as_str()) } };