From 299eb5c0432ce57dbdb7894aa53da2c2395f3df6 Mon Sep 17 00:00:00 2001 From: Igor Khanin Date: Wed, 21 May 2025 23:40:58 +0300 Subject: [PATCH] More precise math font autocomplete suggestions Try to detect a `math.equation` show-set rule via the AST rather than by heuristics, and determine if a font is a math font based on the presence of a MATH table rather than the family name. --- crates/typst-ide/src/complete.rs | 43 +++++++++++++++++++--- crates/typst-ide/src/tooltip.rs | 2 +- crates/typst-ide/src/utils.rs | 11 ++---- crates/typst-library/src/text/font/book.rs | 3 ++ 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index 91fa53f9a..5c1e7bf3d 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -15,7 +15,7 @@ use typst::syntax::{ ast, is_id_continue, is_id_start, is_ident, FileId, LinkedNode, Side, Source, SyntaxKind, }; -use typst::text::RawElem; +use typst::text::{FontFlags, RawElem}; use typst::visualize::Color; use unscanny::Scanner; @@ -827,7 +827,24 @@ fn param_value_completions<'a>( param: &'a ParamInfo, ) { if param.name == "font" { - ctx.font_completions(); + // See if we are in a show-set rule that applies on equations + let equation = if_chain! { + if let Some(parent) = ctx.leaf.parent(); + if let Some(grand) = parent.parent(); + if let Some(grandgrand) = grand.parent(); + if let Some(expr) = grandgrand.get().cast::(); + if let ast::Expr::ShowRule(show) = expr; + if let Some(selector) = show.selector(); + if let ast::Expr::FieldAccess(field) = selector; + if field.field().as_str() == "equation"; + then { + true + } else { + false + } + }; + + ctx.font_completions(equation); } else if let Some(extensions) = path_completion(func, param) { ctx.file_completions_with_extensions(extensions); } else if func.name() == Some("figure") && param.name == "body" { @@ -1151,11 +1168,12 @@ impl<'a> CompletionContext<'a> { } /// Add completions for all font families. - fn font_completions(&mut self) { - let equation = self.before_window(25).contains("equation"); + fn font_completions(&mut self, equation: bool) { for (family, iter) in self.world.book().families() { - let detail = summarize_font_family(iter); - if !equation || family.contains("Math") { + let variants: Vec<_> = iter.collect(); + let is_math = variants.iter().any(|f| f.flags.contains(FontFlags::MATH)); + let detail = summarize_font_family(variants); + if !equation || is_math { self.str_completion( family, Some(CompletionKind::Font), @@ -1790,4 +1808,17 @@ mod tests { .must_include(["r", "dashed"]) .must_exclude(["cases"]); } + + #[test] + fn test_autocomplete_fonts() { + test("#text(font:)", -1) + .must_include(["\"Libertinus Serif\"", "\"New Computer Modern Math\""]); + + test("#show link: set text(font: )", -1) + .must_include(["\"Libertinus Serif\"", "\"New Computer Modern Math\""]); + + test("#show math.equation: set text(font: )", -1) + .must_include(["\"New Computer Modern Math\""]) + .must_exclude(["\"Libertinus Serif\""]); + } } diff --git a/crates/typst-ide/src/tooltip.rs b/crates/typst-ide/src/tooltip.rs index 2638ce51b..e5e4cc19a 100644 --- a/crates/typst-ide/src/tooltip.rs +++ b/crates/typst-ide/src/tooltip.rs @@ -269,7 +269,7 @@ fn font_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option { .find(|&(family, _)| family.to_lowercase().as_str() == lower.as_str()); then { - let detail = summarize_font_family(iter); + let detail = summarize_font_family(iter.collect()); return Some(Tooltip::Text(detail)); } }; diff --git a/crates/typst-ide/src/utils.rs b/crates/typst-ide/src/utils.rs index d5d584e2b..d298d30a7 100644 --- a/crates/typst-ide/src/utils.rs +++ b/crates/typst-ide/src/utils.rs @@ -77,23 +77,20 @@ pub fn plain_docs_sentence(docs: &str) -> EcoString { } /// Create a short description of a font family. -pub fn summarize_font_family<'a>( - variants: impl Iterator, -) -> EcoString { - let mut infos: Vec<_> = variants.collect(); - infos.sort_by_key(|info| info.variant); +pub fn summarize_font_family<'a>(mut variants: Vec<&'a FontInfo>) -> EcoString { + variants.sort_by_key(|info| info.variant); let mut has_italic = false; let mut min_weight = u16::MAX; let mut max_weight = 0; - for info in &infos { + for info in &variants { let weight = info.variant.weight.to_number(); has_italic |= info.variant.style == FontStyle::Italic; min_weight = min_weight.min(weight); max_weight = min_weight.max(weight); } - let count = infos.len(); + let count = variants.len(); let mut detail = eco_format!("{count} variant{}.", if count == 1 { "" } else { "s" }); if min_weight == max_weight { diff --git a/crates/typst-library/src/text/font/book.rs b/crates/typst-library/src/text/font/book.rs index 9f8acce87..cd90a08fe 100644 --- a/crates/typst-library/src/text/font/book.rs +++ b/crates/typst-library/src/text/font/book.rs @@ -194,6 +194,8 @@ bitflags::bitflags! { const MONOSPACE = 1 << 0; /// Glyphs have short strokes at their stems. const SERIF = 1 << 1; + /// Font face has a MATH table + const MATH = 1 << 2; } } @@ -272,6 +274,7 @@ impl FontInfo { let mut flags = FontFlags::empty(); flags.set(FontFlags::MONOSPACE, ttf.is_monospaced()); + flags.set(FontFlags::MATH, ttf.tables().math.is_some()); // Determine whether this is a serif or sans-serif font. if let Some(panose) = ttf