diff --git a/crates/typst-ide/src/analyze.rs b/crates/typst-ide/src/analyze.rs index c493da81a..e7627f611 100644 --- a/crates/typst-ide/src/analyze.rs +++ b/crates/typst-ide/src/analyze.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use comemo::Track; use ecow::{eco_vec, EcoString, EcoVec}; use typst::foundations::{Label, Styles, Value}; @@ -66,14 +68,22 @@ pub fn analyze_import(world: &dyn IdeWorld, source: &LinkedNode) -> Option (Vec<(Label, Option)>, usize) { let mut output = vec![]; + let mut seen_labels = HashSet::new(); // Labels in the document. for elem in document.introspector.all() { let Some(label) = elem.label() else { continue }; + if !seen_labels.insert(label) { + continue; + } + let details = elem .get_by_name("caption") .or_else(|_| elem.get_by_name("body")) diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index bc5b3e10e..bb8f8e78c 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -1709,6 +1709,27 @@ mod tests { .must_exclude(["bib"]); } + #[test] + fn test_autocomplete_ref_function() { + let mut world = TestWorld::new("x"); + let doc = typst::compile(&world).output.ok(); + + let end = world.main.text().len(); + world.main.edit(end..end, " #ref(<)"); + + test_with_doc(&world, -2, doc.as_ref()).must_include(["test"]); + } + + #[test] + fn test_autocomplete_ref_shorthand() { + let mut world = TestWorld::new("x"); + let doc = typst::compile(&world).output.ok(); + + let end = world.main.text().len(); + world.main.edit(end..end, " @"); + + test_with_doc(&world, -1, doc.as_ref()).must_include(["test"]); + } /// Test what kind of brackets we autocomplete for function calls depending /// on the function and existing parens. #[test]