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.
This commit is contained in:
Igor Khanin 2025-05-21 23:40:58 +03:00
parent df89a0e85b
commit 299eb5c043
4 changed files with 45 additions and 14 deletions

View File

@ -15,7 +15,7 @@ use typst::syntax::{
ast, is_id_continue, is_id_start, is_ident, FileId, LinkedNode, Side, Source, ast, is_id_continue, is_id_start, is_ident, FileId, LinkedNode, Side, Source,
SyntaxKind, SyntaxKind,
}; };
use typst::text::RawElem; use typst::text::{FontFlags, RawElem};
use typst::visualize::Color; use typst::visualize::Color;
use unscanny::Scanner; use unscanny::Scanner;
@ -827,7 +827,24 @@ fn param_value_completions<'a>(
param: &'a ParamInfo, param: &'a ParamInfo,
) { ) {
if param.name == "font" { 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::<ast::Expr>();
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) { } else if let Some(extensions) = path_completion(func, param) {
ctx.file_completions_with_extensions(extensions); ctx.file_completions_with_extensions(extensions);
} else if func.name() == Some("figure") && param.name == "body" { } else if func.name() == Some("figure") && param.name == "body" {
@ -1151,11 +1168,12 @@ impl<'a> CompletionContext<'a> {
} }
/// Add completions for all font families. /// Add completions for all font families.
fn font_completions(&mut self) { fn font_completions(&mut self, equation: bool) {
let equation = self.before_window(25).contains("equation");
for (family, iter) in self.world.book().families() { for (family, iter) in self.world.book().families() {
let detail = summarize_font_family(iter); let variants: Vec<_> = iter.collect();
if !equation || family.contains("Math") { 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( self.str_completion(
family, family,
Some(CompletionKind::Font), Some(CompletionKind::Font),
@ -1790,4 +1808,17 @@ mod tests {
.must_include(["r", "dashed"]) .must_include(["r", "dashed"])
.must_exclude(["cases"]); .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\""]);
}
} }

View File

@ -269,7 +269,7 @@ fn font_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option<Tooltip> {
.find(|&(family, _)| family.to_lowercase().as_str() == lower.as_str()); .find(|&(family, _)| family.to_lowercase().as_str() == lower.as_str());
then { then {
let detail = summarize_font_family(iter); let detail = summarize_font_family(iter.collect());
return Some(Tooltip::Text(detail)); return Some(Tooltip::Text(detail));
} }
}; };

View File

@ -77,23 +77,20 @@ pub fn plain_docs_sentence(docs: &str) -> EcoString {
} }
/// Create a short description of a font family. /// Create a short description of a font family.
pub fn summarize_font_family<'a>( pub fn summarize_font_family<'a>(mut variants: Vec<&'a FontInfo>) -> EcoString {
variants: impl Iterator<Item = &'a FontInfo>, variants.sort_by_key(|info| info.variant);
) -> EcoString {
let mut infos: Vec<_> = variants.collect();
infos.sort_by_key(|info| info.variant);
let mut has_italic = false; let mut has_italic = false;
let mut min_weight = u16::MAX; let mut min_weight = u16::MAX;
let mut max_weight = 0; let mut max_weight = 0;
for info in &infos { for info in &variants {
let weight = info.variant.weight.to_number(); let weight = info.variant.weight.to_number();
has_italic |= info.variant.style == FontStyle::Italic; has_italic |= info.variant.style == FontStyle::Italic;
min_weight = min_weight.min(weight); min_weight = min_weight.min(weight);
max_weight = min_weight.max(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" }); let mut detail = eco_format!("{count} variant{}.", if count == 1 { "" } else { "s" });
if min_weight == max_weight { if min_weight == max_weight {

View File

@ -194,6 +194,8 @@ bitflags::bitflags! {
const MONOSPACE = 1 << 0; const MONOSPACE = 1 << 0;
/// Glyphs have short strokes at their stems. /// Glyphs have short strokes at their stems.
const SERIF = 1 << 1; 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(); let mut flags = FontFlags::empty();
flags.set(FontFlags::MONOSPACE, ttf.is_monospaced()); 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. // Determine whether this is a serif or sans-serif font.
if let Some(panose) = ttf if let Some(panose) = ttf