Compare commits

...

5 Commits

Author SHA1 Message Date
Said Aroua
7acb4e203d Add autocomplete tests 2025-07-07 12:46:23 +02:00
Said Aroua
ecb0be4db4 Fix regression in autocomplete of references 2025-07-07 12:32:01 +02:00
Said A.
d2747b457f
Merge branch 'typst:main' into main 2025-07-04 16:10:45 +02:00
Said Aroua
6b3c6658e2 Add passing and failing autocomplete test 2025-07-03 22:23:48 +02:00
Said Aroua
a2f158c413 Dedpulicate labels for code completion 2025-06-27 18:09:07 +02:00
2 changed files with 68 additions and 3 deletions

View File

@ -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<Value
/// - All labels and descriptions for them, if available
/// - A split offset: All labels before this offset belong to nodes, all after
/// belong to a bibliography.
///
/// Note: When multiple labels in the document have the same identifier,
/// this only returns the first one.
pub fn analyze_labels(
document: &PagedDocument,
) -> (Vec<(Label, Option<EcoString>)>, 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"))

View File

@ -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.
@ -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();
@ -1564,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.
@ -1709,6 +1716,54 @@ mod tests {
.must_exclude(["bib"]);
}
#[test]
fn test_autocomplete_ref_function() {
let mut world = TestWorld::new("x<test>");
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<test>");
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]
fn test_autocomplete_ref_shorthand_with_partial_identifier() {
let mut world = TestWorld::new("x<test>");
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<test> y<test>");
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]