summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorT0mstone <39707032+T0mstone@users.noreply.github.com>2025-06-11 15:28:03 +0200
committerGitHub <noreply@github.com>2025-06-11 13:28:03 +0000
commitd7e0c52dd5b6b34861c4104925d2440f5a770a47 (patch)
tree27feecc4ea6e5a19bf50daa7a1f59fe4897cb206
parentd1c7757da8e84ac68dcf58546a7bbaebceea8277 (diff)
Use `codex::ModifierSet` (#6159)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
-rw-r--r--Cargo.lock4
-rw-r--r--Cargo.toml2
-rw-r--r--crates/typst-library/src/foundations/symbol.rs145
-rw-r--r--docs/Cargo.toml1
-rw-r--r--docs/src/lib.rs4
-rw-r--r--tests/suite/symbols/symbol.typ2
6 files changed, 63 insertions, 95 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b699d245..49e5203f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -413,8 +413,7 @@ dependencies = [
[[package]]
name = "codex"
version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "724d27a0ee38b700e5e164350e79aba601a0db673ac47fce1cb74c3e38864036"
+source = "git+https://github.com/typst/codex?rev=56eb2172fc0670f4c1c8b79a63d11f9354e5babe#56eb2172fc0670f4c1c8b79a63d11f9354e5babe"
[[package]]
name = "color-print"
@@ -2921,6 +2920,7 @@ name = "typst-docs"
version = "0.13.1"
dependencies = [
"clap",
+ "codex",
"ecow",
"heck",
"pulldown-cmark",
diff --git a/Cargo.toml b/Cargo.toml
index b548245f..03141cbb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -47,7 +47,7 @@ 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"
+codex = { git = "https://github.com/typst/codex", rev = "56eb217" }
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 50fcfb40..0f503edd 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<EcoString>)>),
}
/// 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<EcoString>, 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<EcoString>, 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<Self> {
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.insert_raw(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<Item = (&str, char)> {
+ pub fn variants(&self) -> impl Iterator<Item = (ModifierSet<&str>, 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<Item = &str> + '_ {
- 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::<BTreeSet<_>>()
+ .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::from_raw_dotted(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<Item = (&'a str, char)>,
- applied_modifiers: &str,
+ variants: impl Iterator<Item = (ModifierSet<&'a str>, 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::<Vec<_>>().join(".");
+ let trimmed_modifiers =
+ trimmed_modifiers.collect::<Vec<_>>().join(".");
eco_format!("(\"{}\", \"{}\")", trimmed_modifiers, c)
}
})
@@ -369,67 +379,22 @@ cast! {
/// Iterator over variants.
enum Variants<'a> {
Single(std::option::IntoIter<char>),
- 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<EcoString>, char)>),
}
impl<'a> Iterator for Variants<'a> {
- type Item = (&'a str, char);
+ type Item = (ModifierSet<&'a str>, char);
fn next(&mut self) -> Option<Self::Item> {
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<Item = (&'a str, char)>,
- modifiers: &str,
-) -> Option<char> {
- 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<Item = &str> {
- 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 acc55175..64e26128 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 d848c59b..b81f0dc6 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())
}
};
diff --git a/tests/suite/symbols/symbol.typ b/tests/suite/symbols/symbol.typ
index 5bc2cafa..9fdda929 100644
--- a/tests/suite/symbols/symbol.typ
+++ b/tests/suite/symbols/symbol.typ
@@ -104,9 +104,11 @@
("long", "⟹"),
("long.bar", "⟾"),
("not", "⇏"),
+ ("struck", "⤃"),
("l", "⇔"),
("l.long", "⟺"),
("l.not", "⇎"),
+ ("l.struck", "⤄"),
)
```.text,
)