@@ -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: )\n it }" , -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