Skip to content

Commit d415834

Browse files
authored
Allow explicit autocomplete immediately after comma and colon (typst#6550)
1 parent d446cde commit d415834

File tree

1 file changed

+130
-18
lines changed

1 file changed

+130
-18
lines changed

crates/typst-ide/src/complete.rs

Lines changed: 130 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -709,9 +709,11 @@ fn complete_params(ctx: &mut CompletionContext) -> bool {
709709
return true;
710710
}
711711

712-
// Parameters: "func(|)", "func(hi|)", "func(12,|)".
712+
// Parameters: "func(|)", "func(hi|)", "func(12, |)", "func(12,|)" [explicit mode only]
713713
if let SyntaxKind::LeftParen | SyntaxKind::Comma = deciding.kind()
714-
&& (deciding.kind() != SyntaxKind::Comma || deciding.range().end < ctx.cursor)
714+
&& (deciding.kind() != SyntaxKind::Comma
715+
|| deciding.range().end < ctx.cursor
716+
|| ctx.explicit)
715717
{
716718
if let Some(next) = deciding.next_leaf() {
717719
ctx.from = ctx.cursor.min(next.offset());
@@ -891,7 +893,10 @@ fn complete_code(ctx: &mut CompletionContext) -> bool {
891893
}
892894

893895
// An existing identifier: "{ pa| }".
894-
if ctx.leaf.kind() == SyntaxKind::Ident {
896+
// Ignores named pair keys as they are not variables (as in "(pa|: 23)").
897+
if ctx.leaf.kind() == SyntaxKind::Ident
898+
&& (ctx.leaf.index() > 0 || ctx.leaf.parent_kind() != Some(SyntaxKind::Named))
899+
{
895900
ctx.from = ctx.leaf.offset();
896901
code_completions(ctx, false);
897902
return true;
@@ -904,11 +909,19 @@ fn complete_code(ctx: &mut CompletionContext) -> bool {
904909
return true;
905910
}
906911

907-
// Anywhere: "{ | }".
908-
// But not within or after an expression.
912+
// Anywhere: "{ | }", "(|)", "(1,|)", "(a:|)".
913+
// But not within or after an expression, and also not part of a dictionary
914+
// key (as in "(pa: |,)")
909915
if ctx.explicit
916+
&& ctx.leaf.parent_kind() != Some(SyntaxKind::Dict)
910917
&& (ctx.leaf.kind().is_trivia()
911-
|| matches!(ctx.leaf.kind(), SyntaxKind::LeftParen | SyntaxKind::LeftBrace))
918+
|| matches!(
919+
ctx.leaf.kind(),
920+
SyntaxKind::LeftParen
921+
| SyntaxKind::LeftBrace
922+
| SyntaxKind::Comma
923+
| SyntaxKind::Colon
924+
))
912925
{
913926
ctx.from = ctx.cursor;
914927
code_completions(ctx, false);
@@ -1560,6 +1573,7 @@ mod tests {
15601573
trait ResponseExt {
15611574
fn completions(&self) -> &[Completion];
15621575
fn labels(&self) -> BTreeSet<&str>;
1576+
fn must_be_empty(&self) -> &Self;
15631577
fn must_include<'a>(&self, includes: impl IntoIterator<Item = &'a str>) -> &Self;
15641578
fn must_exclude<'a>(&self, excludes: impl IntoIterator<Item = &'a str>) -> &Self;
15651579
fn must_apply<'a>(&self, label: &str, apply: impl Into<Option<&'a str>>)
@@ -1578,6 +1592,16 @@ mod tests {
15781592
self.completions().iter().map(|c| c.label.as_str()).collect()
15791593
}
15801594

1595+
#[track_caller]
1596+
fn must_be_empty(&self) -> &Self {
1597+
let labels = self.labels();
1598+
assert!(
1599+
labels.is_empty(),
1600+
"expected no suggestions (got {labels:?} instead)"
1601+
);
1602+
self
1603+
}
1604+
15811605
#[track_caller]
15821606
fn must_include<'a>(&self, includes: impl IntoIterator<Item = &'a str>) -> &Self {
15831607
let labels = self.labels();
@@ -1622,7 +1646,15 @@ mod tests {
16221646
let world = world.acquire();
16231647
let world = world.borrow();
16241648
let doc = typst::compile(world).output.ok();
1625-
test_with_doc(world, pos, doc.as_ref())
1649+
test_with_doc(world, pos, doc.as_ref(), true)
1650+
}
1651+
1652+
#[track_caller]
1653+
fn test_implicit(world: impl WorldLike, pos: impl FilePos) -> Response {
1654+
let world = world.acquire();
1655+
let world = world.borrow();
1656+
let doc = typst::compile(world).output.ok();
1657+
test_with_doc(world, pos, doc.as_ref(), false)
16261658
}
16271659

16281660
#[track_caller]
@@ -1635,19 +1667,20 @@ mod tests {
16351667
let doc = typst::compile(&world).output.ok();
16361668
let end = world.main.text().len();
16371669
world.main.edit(end..end, addition);
1638-
test_with_doc(&world, pos, doc.as_ref())
1670+
test_with_doc(&world, pos, doc.as_ref(), true)
16391671
}
16401672

16411673
#[track_caller]
16421674
fn test_with_doc(
16431675
world: impl WorldLike,
16441676
pos: impl FilePos,
16451677
doc: Option<&PagedDocument>,
1678+
explicit: bool,
16461679
) -> Response {
16471680
let world = world.acquire();
16481681
let world = world.borrow();
16491682
let (source, cursor) = pos.resolve(world);
1650-
autocomplete(world, doc, &source, cursor, true)
1683+
autocomplete(world, doc, &source, cursor, explicit)
16511684
}
16521685

16531686
#[test]
@@ -1698,7 +1731,7 @@ mod tests {
16981731
let end = world.main.text().len();
16991732
world.main.edit(end..end, " #cite()");
17001733

1701-
test_with_doc(&world, -2, doc.as_ref())
1734+
test_with_doc(&world, -2, doc.as_ref(), true)
17021735
.must_include(["netwok", "glacier-melt", "supplement"])
17031736
.must_exclude(["bib"]);
17041737
}
@@ -1853,26 +1886,105 @@ mod tests {
18531886
#[test]
18541887
fn test_autocomplete_fonts() {
18551888
test("#text(font:)", -2)
1856-
.must_include(["\"Libertinus Serif\"", "\"New Computer Modern Math\""]);
1889+
.must_include([q!("Libertinus Serif"), q!("New Computer Modern Math")]);
18571890

18581891
test("#show link: set text(font: )", -2)
1859-
.must_include(["\"Libertinus Serif\"", "\"New Computer Modern Math\""]);
1892+
.must_include([q!("Libertinus Serif"), q!("New Computer Modern Math")]);
18601893

18611894
test("#show math.equation: set text(font: )", -2)
1862-
.must_include(["\"New Computer Modern Math\""])
1863-
.must_exclude(["\"Libertinus Serif\""]);
1895+
.must_include([q!("New Computer Modern Math")])
1896+
.must_exclude([q!("Libertinus Serif")]);
18641897

18651898
test("#show math.equation: it => { set text(font: )\nit }", -7)
1866-
.must_include(["\"New Computer Modern Math\""])
1867-
.must_exclude(["\"Libertinus Serif\""]);
1899+
.must_include([q!("New Computer Modern Math")])
1900+
.must_exclude([q!("Libertinus Serif")]);
18681901
}
18691902

18701903
#[test]
18711904
fn test_autocomplete_typed_html() {
18721905
test("#html.div(translate: )", -2)
18731906
.must_include(["true", "false"])
1874-
.must_exclude(["\"yes\"", "\"no\""]);
1907+
.must_exclude([q!("yes"), q!("no")]);
18751908
test("#html.input(value: )", -2).must_include(["float", "string", "red", "blue"]);
1876-
test("#html.div(role: )", -2).must_include(["\"alertdialog\""]);
1909+
test("#html.div(role: )", -2).must_include([q!("alertdialog")]);
1910+
}
1911+
1912+
#[test]
1913+
fn test_autocomplete_in_function_params_after_comma_and_colon() {
1914+
let document = "#text(size: 12pt, [])";
1915+
1916+
// After colon
1917+
test(document, 11).must_include(["length"]);
1918+
test_implicit(document, 11).must_include(["length"]);
1919+
1920+
test(document, 12).must_include(["length"]);
1921+
test_implicit(document, 12).must_include(["length"]);
1922+
1923+
// After comma
1924+
test(document, 17).must_include(["font"]);
1925+
test_implicit(document, 17).must_be_empty();
1926+
1927+
test(document, 18).must_include(["font"]);
1928+
test_implicit(document, 18).must_include(["font"]);
1929+
}
1930+
1931+
#[test]
1932+
fn test_autocomplete_in_list_literal() {
1933+
let document = "#let val = 0\n#(1, \"one\")";
1934+
1935+
// After opening paren
1936+
test(document, 15).must_include(["color", "val"]);
1937+
test_implicit(document, 15).must_be_empty();
1938+
1939+
// After first element
1940+
test(document, 16).must_be_empty();
1941+
test_implicit(document, 16).must_be_empty();
1942+
1943+
// After comma
1944+
test(document, 17).must_include(["color", "val"]);
1945+
test_implicit(document, 17).must_be_empty();
1946+
1947+
test(document, 18).must_include(["color", "val"]);
1948+
test_implicit(document, 18).must_be_empty();
1949+
}
1950+
1951+
#[test]
1952+
fn test_autocomplete_in_dict_literal() {
1953+
let document = "#let first = 0\n#(first: 1, second: one)";
1954+
1955+
// After opening paren
1956+
test(document, 17).must_be_empty();
1957+
test_implicit(document, 17).must_be_empty();
1958+
1959+
// After first key
1960+
test(document, 22).must_be_empty();
1961+
test_implicit(document, 22).must_be_empty();
1962+
1963+
// After colon
1964+
test(document, 23).must_include(["align", "first"]);
1965+
test_implicit(document, 23).must_be_empty();
1966+
1967+
test(document, 24).must_include(["align", "first"]);
1968+
test_implicit(document, 24).must_be_empty();
1969+
1970+
// After first value
1971+
test(document, 25).must_be_empty();
1972+
test_implicit(document, 25).must_be_empty();
1973+
1974+
// After comma
1975+
test(document, 26).must_be_empty();
1976+
test_implicit(document, 26).must_be_empty();
1977+
1978+
test(document, 27).must_be_empty();
1979+
test_implicit(document, 27).must_be_empty();
1980+
}
1981+
1982+
#[test]
1983+
fn test_autocomplete_in_destructuring() {
1984+
let document = "#let value = 20\n#let (va: value) = (va: 10)";
1985+
1986+
// At destructuring rename pattern source
1987+
test(document, 24).must_be_empty();
1988+
test_implicit(document, 24).must_be_empty();
18771989
}
18781990
}

0 commit comments

Comments
 (0)