From a2f158c413253e0ee6199b4ac468e1c1e39b76f6 Mon Sep 17 00:00:00 2001 From: Said Aroua Date: Fri, 27 Jun 2025 18:09:07 +0200 Subject: [PATCH 1/4] Dedpulicate labels for code completion --- crates/typst-ide/src/analyze.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) 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")) From 6b3c6658e2f7f58e9df46cbc1c0b6713b0155899 Mon Sep 17 00:00:00 2001 From: Said Aroua Date: Thu, 3 Jul 2025 22:23:48 +0200 Subject: [PATCH 2/4] Add passing and failing autocomplete test --- crates/typst-ide/src/complete.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) 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] From ecb0be4db46466e5a67f553489941252db54a50c Mon Sep 17 00:00:00 2001 From: Said Aroua Date: Mon, 7 Jul 2025 12:32:01 +0200 Subject: [PATCH 3/4] Fix regression in autocomplete of references --- crates/typst-ide/src/complete.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index bb8f8e78c..6a489f27a 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -130,7 +130,14 @@ fn complete_markup(ctx: &mut CompletionContext) -> bool { return true; } - // Start of a reference: "@|" or "@he|". + // Start of a reference: "@|". + if ctx.leaf.kind() == SyntaxKind::Text && ctx.before.ends_with("@") { + ctx.from = ctx.cursor; + ctx.label_completions(); + return true; + } + + // An existing reference: "@he|". if ctx.leaf.kind() == SyntaxKind::RefMarker { ctx.from = ctx.leaf.offset() + 1; ctx.label_completions(); From 7acb4e203d93e711e2bf25e6fffac909faeac8ed Mon Sep 17 00:00:00 2001 From: Said Aroua Date: Mon, 7 Jul 2025 12:45:53 +0200 Subject: [PATCH 4/4] Add autocomplete tests --- crates/typst-ide/src/complete.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index 6a489f27a..ff137586a 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -76,7 +76,7 @@ pub struct Completion { } /// A kind of item that can be completed. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum CompletionKind { /// A syntactical structure. @@ -1571,7 +1571,7 @@ mod tests { use typst::layout::PagedDocument; - use super::{autocomplete, Completion}; + use super::{autocomplete, Completion, CompletionKind}; use crate::tests::{FilePos, TestWorld, WorldLike}; /// Quote a string. @@ -1737,6 +1737,33 @@ mod tests { test_with_doc(&world, -1, doc.as_ref()).must_include(["test"]); } + + #[test] + fn test_autocomplete_ref_shorthand_with_partial_identifier() { + let mut world = TestWorld::new("x"); + let doc = typst::compile(&world).output.ok(); + + let end = world.main.text().len(); + world.main.edit(end..end, " @te"); + + test_with_doc(&world, -1, doc.as_ref()).must_include(["test"]); + } + + #[test] + fn test_autocomplete_ref_identical_labels_returns_single_completion() { + let mut world = TestWorld::new("x y"); + let doc = typst::compile(&world).output.ok(); + + let end = world.main.text().len(); + world.main.edit(end..end, " @"); + + let result = test_with_doc(&world, -1, doc.as_ref()); + let completions = result.completions(); + let label_count = + completions.iter().filter(|c| c.kind == CompletionKind::Label).count(); + assert_eq!(label_count, 1); + } + /// Test what kind of brackets we autocomplete for function calls depending /// on the function and existing parens. #[test]