typst_ide/
complete.rs

1use std::cmp::Reverse;
2use std::collections::BTreeMap;
3use std::ffi::OsStr;
4
5use ecow::{EcoString, eco_format};
6use rustc_hash::FxHashSet;
7use serde::{Deserialize, Serialize};
8use typst::foundations::{
9    AutoValue, CastInfo, Func, Label, NativeElement, NoneValue, ParamInfo, Repr,
10    StyleChain, Styles, Type, Value, fields_on, repr,
11};
12use typst::layout::{Alignment, Dir, PagedDocument};
13use typst::syntax::ast::AstNode;
14use typst::syntax::{
15    FileId, LinkedNode, Side, Source, SyntaxKind, ast, is_id_continue, is_id_start,
16    is_ident,
17};
18use typst::text::{FontFlags, RawElem};
19use typst::visualize::Color;
20use unscanny::Scanner;
21
22use crate::utils::{
23    check_value_recursively, globals, plain_docs_sentence, summarize_font_family,
24};
25use crate::{IdeWorld, analyze_expr, analyze_import, analyze_labels, named_items};
26
27/// Autocomplete a cursor position in a source file.
28///
29/// Returns the position from which the completions apply and a list of
30/// completions.
31///
32/// When `explicit` is `true`, the user requested the completion by pressing
33/// control and space or something similar.
34///
35/// Passing a `document` (from a previous compilation) is optional, but enhances
36/// the autocompletions. Label completions, for instance, are only generated
37/// when the document is available.
38pub fn autocomplete(
39    world: &dyn IdeWorld,
40    document: Option<&PagedDocument>,
41    source: &Source,
42    cursor: usize,
43    explicit: bool,
44) -> Option<(usize, Vec<Completion>)> {
45    let leaf = LinkedNode::new(source.root()).leaf_at(cursor, Side::Before)?;
46    let mut ctx =
47        CompletionContext::new(world, document, source, &leaf, cursor, explicit)?;
48
49    let _ = complete_comments(&mut ctx)
50        || complete_field_accesses(&mut ctx)
51        || complete_open_labels(&mut ctx)
52        || complete_imports(&mut ctx)
53        || complete_rules(&mut ctx)
54        || complete_params(&mut ctx)
55        || complete_markup(&mut ctx)
56        || complete_math(&mut ctx)
57        || complete_code(&mut ctx);
58
59    Some((ctx.from, ctx.completions))
60}
61
62/// An autocompletion option.
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct Completion {
65    /// The kind of item this completes to.
66    pub kind: CompletionKind,
67    /// The label the completion is shown with.
68    pub label: EcoString,
69    /// The completed version of the input, possibly described with snippet
70    /// syntax like `${lhs} + ${rhs}`.
71    ///
72    /// Should default to the `label` if `None`.
73    pub apply: Option<EcoString>,
74    /// An optional short description, at most one sentence.
75    pub detail: Option<EcoString>,
76}
77
78/// A kind of item that can be completed.
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
80#[serde(rename_all = "kebab-case")]
81pub enum CompletionKind {
82    /// A syntactical structure.
83    Syntax,
84    /// A function.
85    Func,
86    /// A type.
87    Type,
88    /// A function parameter.
89    Param,
90    /// A constant.
91    Constant,
92    /// A file path.
93    Path,
94    /// A package.
95    Package,
96    /// A label.
97    Label,
98    /// A font family.
99    Font,
100    /// A symbol.
101    Symbol(EcoString),
102}
103
104/// Complete in comments. Or rather, don't!
105fn complete_comments(ctx: &mut CompletionContext) -> bool {
106    matches!(ctx.leaf.kind(), SyntaxKind::LineComment | SyntaxKind::BlockComment)
107}
108
109/// Complete in markup mode.
110fn complete_markup(ctx: &mut CompletionContext) -> bool {
111    // Bail if we aren't even in markup.
112    if !matches!(
113        ctx.leaf.parent_kind(),
114        None | Some(SyntaxKind::Markup) | Some(SyntaxKind::Ref)
115    ) {
116        return false;
117    }
118
119    // Start of an interpolated identifier: "#|".
120    if ctx.leaf.kind() == SyntaxKind::Hash {
121        ctx.from = ctx.cursor;
122        code_completions(ctx, true);
123        return true;
124    }
125
126    // An existing identifier: "#pa|".
127    if ctx.leaf.kind() == SyntaxKind::Ident {
128        ctx.from = ctx.leaf.offset();
129        code_completions(ctx, true);
130        return true;
131    }
132
133    // Start of a reference: "@|".
134    if ctx.leaf.kind() == SyntaxKind::Text && ctx.before.ends_with("@") {
135        ctx.from = ctx.cursor;
136        ctx.label_completions();
137        return true;
138    }
139
140    // An existing reference: "@he|".
141    if ctx.leaf.kind() == SyntaxKind::RefMarker {
142        ctx.from = ctx.leaf.offset() + 1;
143        ctx.label_completions();
144        return true;
145    }
146
147    // Behind a half-completed binding: "#let x = |".
148    if let Some(prev) = ctx.leaf.prev_leaf()
149        && prev.kind() == SyntaxKind::Eq
150        && prev.parent_kind() == Some(SyntaxKind::LetBinding)
151    {
152        ctx.from = ctx.cursor;
153        code_completions(ctx, false);
154        return true;
155    }
156
157    // Behind a half-completed context block: "#context |".
158    if let Some(prev) = ctx.leaf.prev_leaf()
159        && prev.kind() == SyntaxKind::Context
160    {
161        ctx.from = ctx.cursor;
162        code_completions(ctx, false);
163        return true;
164    }
165
166    // Directly after a raw block.
167    let mut s = Scanner::new(ctx.text);
168    s.jump(ctx.leaf.offset());
169    if s.eat_if("```") {
170        s.eat_while('`');
171        let start = s.cursor();
172        if s.eat_if(is_id_start) {
173            s.eat_while(is_id_continue);
174        }
175        if s.cursor() == ctx.cursor {
176            ctx.from = start;
177            ctx.raw_completions();
178        }
179        return true;
180    }
181
182    // Anywhere: "|".
183    if ctx.explicit {
184        ctx.from = ctx.cursor;
185        markup_completions(ctx);
186        return true;
187    }
188
189    false
190}
191
192/// Add completions for markup snippets.
193#[rustfmt::skip]
194fn markup_completions(ctx: &mut CompletionContext) {
195    ctx.snippet_completion(
196        "expression",
197        "#${}",
198        "Variables, function calls, blocks, and more.",
199    );
200
201    ctx.snippet_completion(
202        "linebreak",
203        "\\\n${}",
204        "Inserts a forced linebreak.",
205    );
206
207    ctx.snippet_completion(
208        "strong text",
209        "*${strong}*",
210        "Strongly emphasizes content by increasing the font weight.",
211    );
212
213    ctx.snippet_completion(
214        "emphasized text",
215        "_${emphasized}_",
216        "Emphasizes content by setting it in italic font style.",
217    );
218
219    ctx.snippet_completion(
220        "raw text",
221        "`${text}`",
222        "Displays text verbatim, in monospace.",
223    );
224
225    ctx.snippet_completion(
226        "code listing",
227        "```${lang}\n${code}\n```",
228        "Inserts computer code with syntax highlighting.",
229    );
230
231    ctx.snippet_completion(
232        "hyperlink",
233        "https://${example.com}",
234        "Links to a URL.",
235    );
236
237    ctx.snippet_completion(
238        "label",
239        "<${name}>",
240        "Makes the preceding element referenceable.",
241    );
242
243    ctx.snippet_completion(
244        "reference",
245        "@${name}",
246        "Inserts a reference to a label.",
247    );
248
249    ctx.snippet_completion(
250        "heading",
251        "= ${title}",
252        "Inserts a section heading.",
253    );
254
255    ctx.snippet_completion(
256        "list item",
257        "- ${item}",
258        "Inserts an item of a bullet list.",
259    );
260
261    ctx.snippet_completion(
262        "enumeration item",
263        "+ ${item}",
264        "Inserts an item of a numbered list.",
265    );
266
267    ctx.snippet_completion(
268        "enumeration item (numbered)",
269        "${number}. ${item}",
270        "Inserts an explicitly numbered list item.",
271    );
272
273    ctx.snippet_completion(
274        "term list item",
275        "/ ${term}: ${description}",
276        "Inserts an item of a term list.",
277    );
278
279    ctx.snippet_completion(
280        "math (inline)",
281        "$${x}$",
282        "Inserts an inline-level mathematical equation.",
283    );
284
285    ctx.snippet_completion(
286        "math (block)",
287        "$ ${sum_x^2} $",
288        "Inserts a block-level mathematical equation.",
289    );
290}
291
292/// Complete in math mode.
293fn complete_math(ctx: &mut CompletionContext) -> bool {
294    if !matches!(
295        ctx.leaf.parent_kind(),
296        Some(SyntaxKind::Equation)
297            | Some(SyntaxKind::Math)
298            | Some(SyntaxKind::MathFrac)
299            | Some(SyntaxKind::MathAttach)
300    ) {
301        return false;
302    }
303
304    // Start of an interpolated identifier: "$#|$".
305    if ctx.leaf.kind() == SyntaxKind::Hash {
306        ctx.from = ctx.cursor;
307        code_completions(ctx, true);
308        return true;
309    }
310
311    // Behind existing interpolated identifier: "$#pa|$".
312    if ctx.leaf.kind() == SyntaxKind::Ident {
313        ctx.from = ctx.leaf.offset();
314        code_completions(ctx, true);
315        return true;
316    }
317
318    // Behind existing atom or identifier: "$a|$" or "$abc|$".
319    if matches!(
320        ctx.leaf.kind(),
321        SyntaxKind::Text | SyntaxKind::MathText | SyntaxKind::MathIdent
322    ) {
323        ctx.from = ctx.leaf.offset();
324        math_completions(ctx);
325        return true;
326    }
327
328    // Anywhere: "$|$".
329    if ctx.explicit {
330        ctx.from = ctx.cursor;
331        math_completions(ctx);
332        return true;
333    }
334
335    false
336}
337
338/// Add completions for math snippets.
339#[rustfmt::skip]
340fn math_completions(ctx: &mut CompletionContext) {
341    ctx.scope_completions(true, |_| true);
342
343    ctx.snippet_completion(
344        "subscript",
345        "${x}_${2:2}",
346        "Sets something in subscript.",
347    );
348
349    ctx.snippet_completion(
350        "superscript",
351        "${x}^${2:2}",
352        "Sets something in superscript.",
353    );
354
355    ctx.snippet_completion(
356        "fraction",
357        "${x}/${y}",
358        "Inserts a fraction.",
359    );
360}
361
362/// Complete field accesses.
363fn complete_field_accesses(ctx: &mut CompletionContext) -> bool {
364    // Used to determine whether trivia nodes are allowed before '.'.
365    // During an inline expression in markup mode trivia nodes exit the inline expression.
366    let in_markup: bool = matches!(
367        ctx.leaf.parent_kind(),
368        None | Some(SyntaxKind::Markup) | Some(SyntaxKind::Ref)
369    );
370
371    // Behind an expression plus dot: "emoji.|".
372    if (ctx.leaf.kind() == SyntaxKind::Dot
373        || (matches!(ctx.leaf.kind(), SyntaxKind::Text | SyntaxKind::MathText)
374            && ctx.leaf.text() == "."))
375        && ctx.leaf.range().end == ctx.cursor
376        && let Some(prev) = ctx.leaf.prev_sibling()
377        && (!in_markup || prev.range().end == ctx.leaf.range().start)
378        && prev.is::<ast::Expr>()
379        && (prev.parent_kind() != Some(SyntaxKind::Markup)
380            || prev.prev_sibling_kind() == Some(SyntaxKind::Hash))
381        && let Some((value, styles)) = analyze_expr(ctx.world, &prev).into_iter().next()
382    {
383        ctx.from = ctx.cursor;
384        field_access_completions(ctx, &value, &styles);
385        return true;
386    }
387
388    // Behind a started field access: "emoji.fa|".
389    if ctx.leaf.kind() == SyntaxKind::Ident
390        && let Some(prev) = ctx.leaf.prev_sibling()
391        && prev.kind() == SyntaxKind::Dot
392        && let Some(prev_prev) = prev.prev_sibling()
393        && prev_prev.is::<ast::Expr>()
394        && let Some((value, styles)) =
395            analyze_expr(ctx.world, &prev_prev).into_iter().next()
396    {
397        ctx.from = ctx.leaf.offset();
398        field_access_completions(ctx, &value, &styles);
399        return true;
400    }
401
402    false
403}
404
405/// Add completions for all fields on a value.
406fn field_access_completions(
407    ctx: &mut CompletionContext,
408    value: &Value,
409    styles: &Option<Styles>,
410) {
411    let scopes = {
412        let ty = value.ty().scope();
413        let elem = match value {
414            Value::Content(content) => Some(content.elem().scope()),
415            _ => None,
416        };
417        elem.into_iter().chain(Some(ty))
418    };
419
420    // Autocomplete methods from the element's or type's scope. We only complete
421    // those which have a `self` parameter.
422    for (name, binding) in scopes.flat_map(|scope| scope.iter()) {
423        let Ok(func) = binding.read().clone().cast::<Func>() else { continue };
424        if func
425            .params()
426            .and_then(|params| params.first())
427            .is_some_and(|param| param.name == "self")
428        {
429            ctx.call_completion(name.clone(), binding.read());
430        }
431    }
432
433    if let Some(scope) = value.scope() {
434        for (name, binding) in scope.iter() {
435            ctx.call_completion(name.clone(), binding.read());
436        }
437    }
438
439    for &field in fields_on(value.ty()) {
440        // Complete the field name along with its value. Notes:
441        // 1. No parentheses since function fields cannot currently be called
442        // with method syntax;
443        // 2. We can unwrap the field's value since it's a field belonging to
444        // this value's type, so accessing it should not fail.
445        ctx.value_completion(field, &value.field(field, ()).unwrap());
446    }
447
448    match value {
449        Value::Symbol(symbol) => {
450            for modifier in symbol.modifiers() {
451                if let Ok(modified) = symbol.clone().modified((), modifier) {
452                    ctx.completions.push(Completion {
453                        kind: CompletionKind::Symbol(modified.get().into()),
454                        label: modifier.into(),
455                        apply: None,
456                        detail: None,
457                    });
458                }
459            }
460        }
461        Value::Content(content) => {
462            for (name, value) in content.fields() {
463                ctx.value_completion(name, &value);
464            }
465        }
466        Value::Dict(dict) => {
467            for (name, value) in dict.iter() {
468                ctx.value_completion(name.clone(), value);
469            }
470        }
471        Value::Func(func) => {
472            // Autocomplete get rules.
473            if let Some((elem, styles)) = func.element().zip(styles.as_ref()) {
474                for param in elem.params().iter().filter(|param| !param.required) {
475                    if let Some(value) = elem.field_id(param.name).and_then(|id| {
476                        elem.field_from_styles(id, StyleChain::new(styles)).ok()
477                    }) {
478                        ctx.value_completion(param.name, &value);
479                    }
480                }
481            }
482        }
483        _ => {}
484    }
485}
486
487/// Complete half-finished labels.
488fn complete_open_labels(ctx: &mut CompletionContext) -> bool {
489    // A label anywhere in code: "(<la|".
490    if ctx.leaf.kind().is_error() && ctx.leaf.text().starts_with('<') {
491        ctx.from = ctx.leaf.offset() + 1;
492        ctx.label_completions();
493        return true;
494    }
495
496    false
497}
498
499/// Complete imports.
500fn complete_imports(ctx: &mut CompletionContext) -> bool {
501    // In an import path for a file or package:
502    // "#import "|",
503    if let Some(SyntaxKind::ModuleImport | SyntaxKind::ModuleInclude) =
504        ctx.leaf.parent_kind()
505        && let Some(ast::Expr::Str(str)) = ctx.leaf.cast()
506    {
507        let value = str.get();
508        ctx.from = ctx.leaf.offset();
509        if value.starts_with('@') {
510            let all_versions = value.contains(':');
511            ctx.package_completions(all_versions);
512        } else {
513            ctx.file_completions_with_extensions(&["typ"]);
514        }
515        return true;
516    }
517
518    // Behind an import list:
519    // "#import "path.typ": |",
520    // "#import "path.typ": a, b, |".
521    if let Some(prev) = ctx.leaf.prev_sibling()
522        && let Some(ast::Expr::ModuleImport(import)) = prev.get().cast()
523        && let Some(ast::Imports::Items(items)) = import.imports()
524        && let Some(source) = prev.children().find(|child| child.is::<ast::Expr>())
525    {
526        ctx.from = ctx.cursor;
527        import_item_completions(ctx, items, &source);
528        return true;
529    }
530
531    // Behind a half-started identifier in an import list:
532    // "#import "path.typ": thi|",
533    if ctx.leaf.kind() == SyntaxKind::Ident
534        && let Some(parent) = ctx.leaf.parent()
535        && parent.kind() == SyntaxKind::ImportItemPath
536        && let Some(grand) = parent.parent()
537        && grand.kind() == SyntaxKind::ImportItems
538        && let Some(great) = grand.parent()
539        && let Some(ast::Expr::ModuleImport(import)) = great.get().cast()
540        && let Some(ast::Imports::Items(items)) = import.imports()
541        && let Some(source) = great.children().find(|child| child.is::<ast::Expr>())
542    {
543        ctx.from = ctx.leaf.offset();
544        import_item_completions(ctx, items, &source);
545        return true;
546    }
547
548    false
549}
550
551/// Add completions for all exports of a module.
552fn import_item_completions<'a>(
553    ctx: &mut CompletionContext<'a>,
554    existing: ast::ImportItems<'a>,
555    source: &LinkedNode,
556) {
557    let Some(value) = analyze_import(ctx.world, source) else { return };
558    let Some(scope) = value.scope() else { return };
559
560    if existing.iter().next().is_none() {
561        ctx.snippet_completion("*", "*", "Import everything.");
562    }
563
564    for (name, binding) in scope.iter() {
565        if existing.iter().all(|item| item.original_name().as_str() != name) {
566            ctx.value_completion(name.clone(), binding.read());
567        }
568    }
569}
570
571/// Complete set and show rules.
572fn complete_rules(ctx: &mut CompletionContext) -> bool {
573    // We don't want to complete directly behind the keyword.
574    if !ctx.leaf.kind().is_trivia() {
575        return false;
576    }
577
578    let Some(prev) = ctx.leaf.prev_leaf() else { return false };
579
580    // Behind the set keyword: "set |".
581    if matches!(prev.kind(), SyntaxKind::Set) {
582        ctx.from = ctx.cursor;
583        set_rule_completions(ctx);
584        return true;
585    }
586
587    // Behind the show keyword: "show |".
588    if matches!(prev.kind(), SyntaxKind::Show) {
589        ctx.from = ctx.cursor;
590        show_rule_selector_completions(ctx);
591        return true;
592    }
593
594    // Behind a half-completed show rule: "show strong: |".
595    if let Some(prev) = ctx.leaf.prev_leaf()
596        && matches!(prev.kind(), SyntaxKind::Colon)
597        && matches!(prev.parent_kind(), Some(SyntaxKind::ShowRule))
598    {
599        ctx.from = ctx.cursor;
600        show_rule_recipe_completions(ctx);
601        return true;
602    }
603
604    false
605}
606
607/// Add completions for all functions from the global scope.
608fn set_rule_completions(ctx: &mut CompletionContext) {
609    ctx.scope_completions(true, |value| {
610        matches!(
611            value,
612            Value::Func(func) if func.params()
613                .unwrap_or_default()
614                .iter()
615                .any(|param| param.settable),
616        )
617    });
618}
619
620/// Add completions for selectors.
621fn show_rule_selector_completions(ctx: &mut CompletionContext) {
622    ctx.scope_completions(
623        false,
624        |value| matches!(value, Value::Func(func) if func.element().is_some()),
625    );
626
627    ctx.enrich("", ": ");
628
629    ctx.snippet_completion(
630        "text selector",
631        "\"${text}\": ${}",
632        "Replace occurrences of specific text.",
633    );
634
635    ctx.snippet_completion(
636        "regex selector",
637        "regex(\"${regex}\"): ${}",
638        "Replace matches of a regular expression.",
639    );
640}
641
642/// Add completions for recipes.
643fn show_rule_recipe_completions(ctx: &mut CompletionContext) {
644    ctx.snippet_completion(
645        "replacement",
646        "[${content}]",
647        "Replace the selected element with content.",
648    );
649
650    ctx.snippet_completion(
651        "replacement (string)",
652        "\"${text}\"",
653        "Replace the selected element with a string of text.",
654    );
655
656    ctx.snippet_completion(
657        "transformation",
658        "element => [${content}]",
659        "Transform the element with a function.",
660    );
661
662    ctx.scope_completions(false, |value| matches!(value, Value::Func(_)));
663}
664
665/// Complete call and set rule parameters.
666fn complete_params(ctx: &mut CompletionContext) -> bool {
667    // Ensure that we are in a function call or set rule's argument list.
668    let (callee, set, args, args_linked) = if let Some(parent) = ctx.leaf.parent()
669        && let Some(parent) = match parent.kind() {
670            SyntaxKind::Named => parent.parent(),
671            _ => Some(parent),
672        }
673        && let Some(args) = parent.get().cast::<ast::Args>()
674        && let Some(grand) = parent.parent()
675        && let Some(expr) = grand.get().cast::<ast::Expr>()
676        && let set = matches!(expr, ast::Expr::SetRule(_))
677        && let Some(callee) = match expr {
678            ast::Expr::FuncCall(call) => Some(call.callee()),
679            ast::Expr::SetRule(set) => Some(set.target()),
680            _ => None,
681        } {
682        (callee, set, args, parent)
683    } else {
684        return false;
685    };
686
687    // Find the piece of syntax that decides what we're completing.
688    let mut deciding = ctx.leaf.clone();
689    while !matches!(
690        deciding.kind(),
691        SyntaxKind::LeftParen
692            | SyntaxKind::RightParen
693            | SyntaxKind::Comma
694            | SyntaxKind::Colon
695    ) {
696        let Some(prev) = deciding.prev_leaf() else { break };
697        deciding = prev;
698    }
699
700    // Parameter values: "func(param:|)", "func(param: |)".
701    if let SyntaxKind::Colon = deciding.kind()
702        && let Some(prev) = deciding.prev_leaf()
703        && let Some(param) = prev.get().cast::<ast::Ident>()
704    {
705        if let Some(next) = deciding.next_leaf() {
706            ctx.from = ctx.cursor.min(next.offset());
707        }
708
709        named_param_value_completions(ctx, callee, &param);
710        return true;
711    }
712
713    // Parameters: "func(|)", "func(hi|)", "func(12, |)", "func(12,|)" [explicit mode only]
714    if let SyntaxKind::LeftParen | SyntaxKind::Comma = deciding.kind()
715        && (deciding.kind() != SyntaxKind::Comma
716            || deciding.range().end < ctx.cursor
717            || ctx.explicit)
718    {
719        if let Some(next) = deciding.next_leaf() {
720            ctx.from = ctx.cursor.min(next.offset());
721        }
722
723        param_completions(ctx, callee, set, args, args_linked);
724        return true;
725    }
726
727    false
728}
729
730/// Add completions for the parameters of a function.
731fn param_completions<'a>(
732    ctx: &mut CompletionContext<'a>,
733    callee: ast::Expr<'a>,
734    set: bool,
735    args: ast::Args<'a>,
736    args_linked: &'a LinkedNode<'a>,
737) {
738    let Some(func) = resolve_global_callee(ctx, callee) else { return };
739    let Some(params) = func.params() else { return };
740
741    // Determine which arguments are already present.
742    let mut existing_positional = 0;
743    let mut existing_named = FxHashSet::default();
744    for arg in args.items() {
745        match arg {
746            ast::Arg::Pos(_) => {
747                let Some(node) = args_linked.find(arg.span()) else { continue };
748                if node.range().end < ctx.cursor {
749                    existing_positional += 1;
750                }
751            }
752            ast::Arg::Named(named) => {
753                existing_named.insert(named.name().as_str());
754            }
755            _ => {}
756        }
757    }
758
759    let mut skipped_positional = 0;
760    for param in params {
761        if set && !param.settable {
762            continue;
763        }
764
765        if param.positional {
766            if skipped_positional < existing_positional && !param.variadic {
767                skipped_positional += 1;
768                continue;
769            }
770
771            param_value_completions(ctx, func, param);
772        }
773
774        if param.named {
775            if existing_named.contains(&param.name) {
776                continue;
777            }
778
779            let apply = if param.name == "caption" {
780                eco_format!("{}: [${{}}]", param.name)
781            } else {
782                eco_format!("{}: ${{}}", param.name)
783            };
784
785            ctx.completions.push(Completion {
786                kind: CompletionKind::Param,
787                label: param.name.into(),
788                apply: Some(apply),
789                detail: Some(plain_docs_sentence(param.docs)),
790            });
791        }
792    }
793
794    if ctx.before.ends_with(',') {
795        ctx.enrich(" ", "");
796    }
797}
798
799/// Add completions for the values of a named function parameter.
800fn named_param_value_completions<'a>(
801    ctx: &mut CompletionContext<'a>,
802    callee: ast::Expr<'a>,
803    name: &str,
804) {
805    let Some(func) = resolve_global_callee(ctx, callee) else { return };
806    let Some(param) = func.param(name) else { return };
807    if !param.named {
808        return;
809    }
810
811    param_value_completions(ctx, func, param);
812
813    if ctx.before.ends_with(':') {
814        ctx.enrich(" ", "");
815    }
816}
817
818/// Add completions for the values of a parameter.
819fn param_value_completions<'a>(
820    ctx: &mut CompletionContext<'a>,
821    func: &Func,
822    param: &'a ParamInfo,
823) {
824    if param.name == "font" {
825        ctx.font_completions();
826    } else if let Some(extensions) = path_completion(func, param) {
827        ctx.file_completions_with_extensions(extensions);
828    } else if func.name() == Some("figure") && param.name == "body" {
829        ctx.snippet_completion("image", "image(\"${}\"),", "An image in a figure.");
830        ctx.snippet_completion("table", "table(\n  ${}\n),", "A table in a figure.");
831    }
832
833    ctx.cast_completions(&param.input);
834}
835
836/// Returns which file extensions to complete for the given parameter if any.
837fn path_completion(func: &Func, param: &ParamInfo) -> Option<&'static [&'static str]> {
838    Some(match (func.name(), param.name) {
839        (Some("image"), "source") => {
840            &["png", "jpg", "jpeg", "gif", "svg", "svgz", "webp", "pdf"]
841        }
842        (Some("csv"), "source") => &["csv"],
843        (Some("plugin"), "source") => &["wasm"],
844        (Some("cbor"), "source") => &["cbor"],
845        (Some("json"), "source") => &["json"],
846        (Some("toml"), "source") => &["toml"],
847        (Some("xml"), "source") => &["xml"],
848        (Some("yaml"), "source") => &["yml", "yaml"],
849        (Some("bibliography"), "sources") => &["bib", "yml", "yaml"],
850        (Some("bibliography"), "style") => &["csl"],
851        (Some("cite"), "style") => &["csl"],
852        (Some("raw"), "syntaxes") => &["sublime-syntax"],
853        (Some("raw"), "theme") => &["tmtheme"],
854        (Some("embed"), "path") => &[],
855        (Some("attach"), "path") if *func == typst::pdf::AttachElem::ELEM => &[],
856        (None, "path") => &[],
857        _ => return None,
858    })
859}
860
861/// Resolve a callee expression to a global function.
862fn resolve_global_callee<'a>(
863    ctx: &CompletionContext<'a>,
864    callee: ast::Expr<'a>,
865) -> Option<&'a Func> {
866    let globals = globals(ctx.world, ctx.leaf);
867    let value = match callee {
868        ast::Expr::Ident(ident) => globals.get(&ident)?.read(),
869        ast::Expr::FieldAccess(access) => match access.target() {
870            ast::Expr::Ident(target) => {
871                globals.get(&target)?.read().scope()?.get(&access.field())?.read()
872            }
873            _ => return None,
874        },
875        _ => return None,
876    };
877
878    match value {
879        Value::Func(func) => Some(func),
880        _ => None,
881    }
882}
883
884/// Complete in code mode.
885fn complete_code(ctx: &mut CompletionContext) -> bool {
886    if matches!(
887        ctx.leaf.parent_kind(),
888        None | Some(SyntaxKind::Markup)
889            | Some(SyntaxKind::Math)
890            | Some(SyntaxKind::MathFrac)
891            | Some(SyntaxKind::MathAttach)
892            | Some(SyntaxKind::MathRoot)
893    ) {
894        return false;
895    }
896
897    // An existing identifier: "{ pa| }".
898    // Ignores named pair keys as they are not variables (as in "(pa|: 23)").
899    if ctx.leaf.kind() == SyntaxKind::Ident
900        && (ctx.leaf.index() > 0 || ctx.leaf.parent_kind() != Some(SyntaxKind::Named))
901    {
902        ctx.from = ctx.leaf.offset();
903        code_completions(ctx, false);
904        return true;
905    }
906
907    // A potential label (only at the start of an argument list): "(<|".
908    if ctx.before.ends_with("(<") {
909        ctx.from = ctx.cursor;
910        ctx.label_completions();
911        return true;
912    }
913
914    // Anywhere: "{ | }", "(|)", "(1,|)", "(a:|)".
915    // But not within or after an expression, and also not part of a dictionary
916    // key (as in "(pa: |,)")
917    if ctx.explicit
918        && ctx.leaf.parent_kind() != Some(SyntaxKind::Dict)
919        && (ctx.leaf.kind().is_trivia()
920            || matches!(
921                ctx.leaf.kind(),
922                SyntaxKind::LeftParen
923                    | SyntaxKind::LeftBrace
924                    | SyntaxKind::Comma
925                    | SyntaxKind::Colon
926            ))
927    {
928        ctx.from = ctx.cursor;
929        code_completions(ctx, false);
930        return true;
931    }
932
933    false
934}
935
936/// Add completions for expression snippets.
937#[rustfmt::skip]
938fn code_completions(ctx: &mut CompletionContext, hash: bool) {
939    if hash {
940        ctx.scope_completions(true, |value| {
941            // If we are in markup, ignore colors, directions, and alignments.
942            // They are useless and bloat the autocomplete results.
943            let ty = value.ty();
944            ty != Type::of::<Color>()
945                && ty != Type::of::<Dir>()
946                && ty != Type::of::<Alignment>()
947        });
948    } else {
949        ctx.scope_completions(true, |_| true);
950    }
951
952    ctx.snippet_completion(
953        "function call",
954        "${function}(${arguments})[${body}]",
955        "Evaluates a function.",
956    );
957
958    ctx.snippet_completion(
959        "code block",
960        "{ ${} }",
961        "Inserts a nested code block.",
962    );
963
964    ctx.snippet_completion(
965        "content block",
966        "[${content}]",
967        "Switches into markup mode.",
968    );
969
970    ctx.snippet_completion(
971        "set rule",
972        "set ${}",
973        "Sets style properties on an element.",
974    );
975
976    ctx.snippet_completion(
977        "show rule",
978        "show ${}",
979        "Redefines the look of an element.",
980    );
981
982    ctx.snippet_completion(
983        "show rule (everything)",
984        "show: ${}",
985        "Transforms everything that follows.",
986    );
987
988    ctx.snippet_completion(
989        "context expression",
990        "context ${}",
991        "Provides contextual data.",
992    );
993
994    ctx.snippet_completion(
995        "let binding",
996        "let ${name} = ${value}",
997        "Saves a value in a variable.",
998    );
999
1000    ctx.snippet_completion(
1001        "let binding (function)",
1002        "let ${name}(${params}) = ${output}",
1003        "Defines a function.",
1004    );
1005
1006    ctx.snippet_completion(
1007        "if conditional",
1008        "if ${1 < 2} {\n\t${}\n}",
1009        "Computes or inserts something conditionally.",
1010    );
1011
1012    ctx.snippet_completion(
1013        "if-else conditional",
1014        "if ${1 < 2} {\n\t${}\n} else {\n\t${}\n}",
1015        "Computes or inserts different things based on a condition.",
1016    );
1017
1018    ctx.snippet_completion(
1019        "while loop",
1020        "while ${1 < 2} {\n\t${}\n}",
1021        "Computes or inserts something while a condition is met.",
1022    );
1023
1024    ctx.snippet_completion(
1025        "for loop",
1026        "for ${value} in ${(1, 2, 3)} {\n\t${}\n}",
1027        "Computes or inserts something for each value in a collection.",
1028    );
1029
1030    ctx.snippet_completion(
1031        "for loop (with key)",
1032        "for (${key}, ${value}) in ${(a: 1, b: 2)} {\n\t${}\n}",
1033        "Computes or inserts something for each key and value in a collection.",
1034    );
1035
1036    ctx.snippet_completion(
1037        "break",
1038        "break",
1039        "Exits early from a loop.",
1040    );
1041
1042    ctx.snippet_completion(
1043        "continue",
1044        "continue",
1045        "Continues with the next iteration of a loop.",
1046    );
1047
1048    ctx.snippet_completion(
1049        "return",
1050        "return ${output}",
1051        "Returns early from a function.",
1052    );
1053
1054    ctx.snippet_completion(
1055        "import (file)",
1056        "import \"${}\": ${}",
1057        "Imports variables from another file.",
1058    );
1059
1060    ctx.snippet_completion(
1061        "import (package)",
1062        "import \"@${}\": ${}",
1063        "Imports variables from a package.",
1064    );
1065
1066    ctx.snippet_completion(
1067        "include (file)",
1068        "include \"${}\"",
1069        "Includes content from another file.",
1070    );
1071
1072    ctx.snippet_completion(
1073        "array literal",
1074        "(${1, 2, 3})",
1075        "Creates a sequence of values.",
1076    );
1077
1078    ctx.snippet_completion(
1079        "dictionary literal",
1080        "(${a: 1, b: 2})",
1081        "Creates a mapping from names to value.",
1082    );
1083
1084    if !hash {
1085        ctx.snippet_completion(
1086            "function",
1087            "(${params}) => ${output}",
1088            "Creates an unnamed function.",
1089        );
1090    }
1091}
1092
1093/// See if the AST node is somewhere within a show rule applying to equations
1094fn is_in_equation_show_rule(leaf: &LinkedNode<'_>) -> bool {
1095    let mut node = leaf;
1096    while let Some(parent) = node.parent() {
1097        if let Some(expr) = parent.get().cast::<ast::Expr>()
1098            && let ast::Expr::ShowRule(show) = expr
1099            && let Some(ast::Expr::FieldAccess(field)) = show.selector()
1100            && field.field().as_str() == "equation"
1101        {
1102            return true;
1103        }
1104        node = parent;
1105    }
1106    false
1107}
1108
1109/// Context for autocompletion.
1110struct CompletionContext<'a> {
1111    world: &'a (dyn IdeWorld + 'a),
1112    document: Option<&'a PagedDocument>,
1113    text: &'a str,
1114    before: &'a str,
1115    after: &'a str,
1116    leaf: &'a LinkedNode<'a>,
1117    cursor: usize,
1118    explicit: bool,
1119    from: usize,
1120    completions: Vec<Completion>,
1121    seen_casts: FxHashSet<u128>,
1122}
1123
1124impl<'a> CompletionContext<'a> {
1125    /// Create a new autocompletion context.
1126    fn new(
1127        world: &'a (dyn IdeWorld + 'a),
1128        document: Option<&'a PagedDocument>,
1129        source: &'a Source,
1130        leaf: &'a LinkedNode<'a>,
1131        cursor: usize,
1132        explicit: bool,
1133    ) -> Option<Self> {
1134        let text = source.text();
1135        Some(Self {
1136            world,
1137            document,
1138            text,
1139            before: &text[..cursor],
1140            after: &text[cursor..],
1141            leaf,
1142            cursor,
1143            explicit,
1144            from: cursor,
1145            completions: vec![],
1146            seen_casts: FxHashSet::default(),
1147        })
1148    }
1149
1150    /// A small window of context before the cursor.
1151    fn before_window(&self, size: usize) -> &str {
1152        Scanner::new(self.before).get(self.cursor.saturating_sub(size)..self.cursor)
1153    }
1154
1155    /// Add a prefix and suffix to all applications.
1156    fn enrich(&mut self, prefix: &str, suffix: &str) {
1157        for Completion { label, apply, .. } in &mut self.completions {
1158            let current = apply.as_ref().unwrap_or(label);
1159            *apply = Some(eco_format!("{prefix}{current}{suffix}"));
1160        }
1161    }
1162
1163    /// Add a snippet completion.
1164    fn snippet_completion(
1165        &mut self,
1166        label: &'static str,
1167        snippet: &'static str,
1168        docs: &'static str,
1169    ) {
1170        self.completions.push(Completion {
1171            kind: CompletionKind::Syntax,
1172            label: label.into(),
1173            apply: Some(snippet.into()),
1174            detail: Some(docs.into()),
1175        });
1176    }
1177
1178    /// Add completions for all font families.
1179    fn font_completions(&mut self) {
1180        let equation = is_in_equation_show_rule(self.leaf);
1181        for (family, iter) in self.world.book().families() {
1182            let variants: Vec<_> = iter.collect();
1183            let is_math = variants.iter().any(|f| f.flags.contains(FontFlags::MATH));
1184            let detail = summarize_font_family(variants);
1185            if !equation || is_math {
1186                self.str_completion(
1187                    family,
1188                    Some(CompletionKind::Font),
1189                    Some(detail.as_str()),
1190                );
1191            }
1192        }
1193    }
1194
1195    /// Add completions for all available packages.
1196    fn package_completions(&mut self, all_versions: bool) {
1197        let mut packages: Vec<_> = self.world.packages().iter().collect();
1198        packages.sort_by_key(|(spec, _)| {
1199            (&spec.namespace, &spec.name, Reverse(spec.version))
1200        });
1201        if !all_versions {
1202            packages.dedup_by_key(|(spec, _)| (&spec.namespace, &spec.name));
1203        }
1204        for (package, description) in packages {
1205            self.str_completion(
1206                eco_format!("{package}"),
1207                Some(CompletionKind::Package),
1208                description.as_deref(),
1209            );
1210        }
1211    }
1212
1213    /// Add completions for all available files.
1214    fn file_completions(&mut self, mut filter: impl FnMut(FileId) -> bool) {
1215        let Some(base_id) = self.leaf.span().id() else { return };
1216        let Some(base_path) = base_id.vpath().as_rooted_path().parent() else { return };
1217
1218        let mut paths: Vec<EcoString> = self
1219            .world
1220            .files()
1221            .iter()
1222            .filter(|&&file_id| file_id != base_id && filter(file_id))
1223            .filter_map(|file_id| {
1224                let file_path = file_id.vpath().as_rooted_path();
1225                pathdiff::diff_paths(file_path, base_path)
1226            })
1227            .map(|path| path.to_string_lossy().replace('\\', "/").into())
1228            .collect();
1229
1230        paths.sort();
1231
1232        for path in paths {
1233            self.str_completion(path, Some(CompletionKind::Path), None);
1234        }
1235    }
1236
1237    /// Add completions for all files with any of the given extensions.
1238    ///
1239    /// If the array is empty, all extensions are allowed.
1240    fn file_completions_with_extensions(&mut self, extensions: &[&str]) {
1241        if extensions.is_empty() {
1242            self.file_completions(|_| true);
1243        }
1244        self.file_completions(|id| {
1245            let ext = id
1246                .vpath()
1247                .as_rooted_path()
1248                .extension()
1249                .and_then(OsStr::to_str)
1250                .map(EcoString::from)
1251                .unwrap_or_default()
1252                .to_lowercase();
1253            extensions.contains(&ext.as_str())
1254        });
1255    }
1256
1257    /// Add completions for raw block tags.
1258    fn raw_completions(&mut self) {
1259        for (name, mut tags) in RawElem::languages() {
1260            let lower = name.to_lowercase();
1261            if !tags.contains(&lower.as_str()) {
1262                tags.push(lower.as_str());
1263            }
1264
1265            tags.retain(|tag| is_ident(tag));
1266            if tags.is_empty() {
1267                continue;
1268            }
1269
1270            self.completions.push(Completion {
1271                kind: CompletionKind::Constant,
1272                label: name.into(),
1273                apply: Some(tags[0].into()),
1274                detail: Some(repr::separated_list(&tags, " or ").into()),
1275            });
1276        }
1277    }
1278
1279    /// Add completions for labels and references.
1280    fn label_completions(&mut self) {
1281        let Some(document) = self.document else { return };
1282        let (labels, split) = analyze_labels(document);
1283
1284        let head = &self.text[..self.from];
1285        let at = head.ends_with('@');
1286        let open = !at && !head.ends_with('<');
1287        let close = !at && !self.after.starts_with('>');
1288        let citation = !at && self.before_window(15).contains("cite");
1289
1290        let (skip, take) = if at {
1291            (0, usize::MAX)
1292        } else if citation {
1293            (split, usize::MAX)
1294        } else {
1295            (0, split)
1296        };
1297
1298        for (label, detail) in labels.into_iter().skip(skip).take(take) {
1299            self.completions.push(Completion {
1300                kind: CompletionKind::Label,
1301                apply: (open || close).then(|| {
1302                    eco_format!(
1303                        "{}{}{}",
1304                        if open { "<" } else { "" },
1305                        label.resolve(),
1306                        if close { ">" } else { "" }
1307                    )
1308                }),
1309                label: label.resolve().as_str().into(),
1310                detail,
1311            });
1312        }
1313    }
1314
1315    /// Add a completion for an arbitrary value.
1316    fn value_completion(&mut self, label: impl Into<EcoString>, value: &Value) {
1317        self.value_completion_full(Some(label.into()), value, false, None, None);
1318    }
1319
1320    /// Add a completion for an arbitrary value, adding parentheses if it's a function.
1321    fn call_completion(&mut self, label: impl Into<EcoString>, value: &Value) {
1322        self.value_completion_full(Some(label.into()), value, true, None, None);
1323    }
1324
1325    /// Add a completion for a specific string literal.
1326    fn str_completion(
1327        &mut self,
1328        string: impl Into<EcoString>,
1329        kind: Option<CompletionKind>,
1330        detail: Option<&str>,
1331    ) {
1332        let string = string.into();
1333        self.value_completion_full(None, &Value::Str(string.into()), false, kind, detail);
1334    }
1335
1336    /// Add a completion for a specific value.
1337    fn value_completion_full(
1338        &mut self,
1339        label: Option<EcoString>,
1340        value: &Value,
1341        parens: bool,
1342        kind: Option<CompletionKind>,
1343        detail: Option<&str>,
1344    ) {
1345        let at = label.as_deref().is_some_and(|field| !is_ident(field));
1346        let label = label.unwrap_or_else(|| value.repr());
1347
1348        let detail = detail.map(Into::into).or_else(|| match value {
1349            Value::Symbol(_) => None,
1350            Value::Func(func) => func.docs().map(plain_docs_sentence),
1351            Value::Type(ty) => Some(plain_docs_sentence(ty.docs())),
1352            v => {
1353                let repr = v.repr();
1354                (repr.as_str() != label).then_some(repr)
1355            }
1356        });
1357
1358        let mut apply = None;
1359        if parens
1360            && matches!(value, Value::Func(_))
1361            && !self.after.starts_with(['(', '['])
1362        {
1363            if let Value::Func(func) = value {
1364                apply = Some(match BracketMode::of(func) {
1365                    BracketMode::RoundAfter => eco_format!("{label}()${{}}"),
1366                    BracketMode::RoundWithin => eco_format!("{label}(${{}})"),
1367                    BracketMode::RoundNewline => eco_format!("{label}(\n  ${{}}\n)"),
1368                    BracketMode::SquareWithin => eco_format!("{label}[${{}}]"),
1369                });
1370            }
1371        } else if at {
1372            apply = Some(eco_format!("at(\"{label}\")"));
1373        } else if label.starts_with('"')
1374            && self.after.starts_with('"')
1375            && let Some(trimmed) = label.strip_suffix('"')
1376        {
1377            apply = Some(trimmed.into());
1378        }
1379
1380        self.completions.push(Completion {
1381            kind: kind.unwrap_or_else(|| match value {
1382                Value::Func(_) => CompletionKind::Func,
1383                Value::Type(_) => CompletionKind::Type,
1384                Value::Symbol(s) => CompletionKind::Symbol(s.get().into()),
1385                _ => CompletionKind::Constant,
1386            }),
1387            label,
1388            apply,
1389            detail,
1390        });
1391    }
1392
1393    /// Add completions for a castable.
1394    fn cast_completions(&mut self, cast: &'a CastInfo) {
1395        // Prevent duplicate completions from appearing.
1396        if !self.seen_casts.insert(typst::utils::hash128(cast)) {
1397            return;
1398        }
1399
1400        match cast {
1401            CastInfo::Any => {}
1402            CastInfo::Value(value, docs) => {
1403                self.value_completion_full(None, value, false, None, Some(docs));
1404            }
1405            CastInfo::Type(ty) => {
1406                if *ty == Type::of::<NoneValue>() {
1407                    self.snippet_completion("none", "none", "Nothing.")
1408                } else if *ty == Type::of::<AutoValue>() {
1409                    self.snippet_completion("auto", "auto", "A smart default.");
1410                } else if *ty == Type::of::<bool>() {
1411                    self.snippet_completion("false", "false", "No / Disabled.");
1412                    self.snippet_completion("true", "true", "Yes / Enabled.");
1413                } else if *ty == Type::of::<Color>() {
1414                    self.snippet_completion(
1415                        "luma()",
1416                        "luma(${v})",
1417                        "A custom grayscale color.",
1418                    );
1419                    self.snippet_completion(
1420                        "rgb()",
1421                        "rgb(${r}, ${g}, ${b}, ${a})",
1422                        "A custom RGBA color.",
1423                    );
1424                    self.snippet_completion(
1425                        "cmyk()",
1426                        "cmyk(${c}, ${m}, ${y}, ${k})",
1427                        "A custom CMYK color.",
1428                    );
1429                    self.snippet_completion(
1430                        "oklab()",
1431                        "oklab(${l}, ${a}, ${b}, ${alpha})",
1432                        "A custom Oklab color.",
1433                    );
1434                    self.snippet_completion(
1435                        "oklch()",
1436                        "oklch(${l}, ${chroma}, ${hue}, ${alpha})",
1437                        "A custom Oklch color.",
1438                    );
1439                    self.snippet_completion(
1440                        "color.linear-rgb()",
1441                        "color.linear-rgb(${r}, ${g}, ${b}, ${a})",
1442                        "A custom linear RGBA color.",
1443                    );
1444                    self.snippet_completion(
1445                        "color.hsv()",
1446                        "color.hsv(${h}, ${s}, ${v}, ${a})",
1447                        "A custom HSVA color.",
1448                    );
1449                    self.snippet_completion(
1450                        "color.hsl()",
1451                        "color.hsl(${h}, ${s}, ${l}, ${a})",
1452                        "A custom HSLA color.",
1453                    );
1454                    self.scope_completions(false, |value| value.ty() == *ty);
1455                } else if *ty == Type::of::<Label>() {
1456                    self.label_completions()
1457                } else if *ty == Type::of::<Func>() {
1458                    self.snippet_completion(
1459                        "function",
1460                        "(${params}) => ${output}",
1461                        "A custom function.",
1462                    );
1463                } else {
1464                    self.completions.push(Completion {
1465                        kind: CompletionKind::Syntax,
1466                        label: ty.long_name().into(),
1467                        apply: Some(eco_format!("${{{ty}}}")),
1468                        detail: Some(eco_format!("A value of type {ty}.")),
1469                    });
1470                    self.scope_completions(false, |value| value.ty() == *ty);
1471                }
1472            }
1473            CastInfo::Union(union) => {
1474                for info in union {
1475                    self.cast_completions(info);
1476                }
1477            }
1478        }
1479    }
1480
1481    /// Add completions for definitions that are available at the cursor.
1482    ///
1483    /// Filters the global/math scope with the given filter.
1484    fn scope_completions(&mut self, parens: bool, filter: impl Fn(&Value) -> bool) {
1485        // When any of the constituent parts of the value matches the filter,
1486        // that's ok as well. For example, when autocompleting `#rect(fill: |)`,
1487        // we propose colors, but also dictionaries and modules that contain
1488        // colors.
1489        let filter = |value: &Value| check_value_recursively(value, &filter);
1490
1491        let mut defined = BTreeMap::<EcoString, Option<Value>>::new();
1492        named_items(self.world, self.leaf.clone(), |item| {
1493            let name = item.name();
1494            if !name.is_empty() && item.value().as_ref().is_none_or(filter) {
1495                defined.insert(name.clone(), item.value());
1496            }
1497
1498            None::<()>
1499        });
1500
1501        for (name, value) in &defined {
1502            if let Some(value) = value {
1503                self.value_completion(name.clone(), value);
1504            } else {
1505                self.completions.push(Completion {
1506                    kind: CompletionKind::Constant,
1507                    label: name.clone(),
1508                    apply: None,
1509                    detail: None,
1510                });
1511            }
1512        }
1513
1514        for (name, binding) in globals(self.world, self.leaf).iter() {
1515            let value = binding.read();
1516            if filter(value) && !defined.contains_key(name) {
1517                self.value_completion_full(Some(name.clone()), value, parens, None, None);
1518            }
1519        }
1520    }
1521}
1522
1523/// What kind of parentheses to autocomplete for a function.
1524enum BracketMode {
1525    /// Round parenthesis, with the cursor within: `(|)`.
1526    RoundWithin,
1527    /// Round parenthesis, with the cursor after them: `()|`.
1528    RoundAfter,
1529    /// Round parenthesis, with newlines and indent.
1530    RoundNewline,
1531    /// Square brackets, with the cursor within: `[|]`.
1532    SquareWithin,
1533}
1534
1535impl BracketMode {
1536    fn of(func: &Func) -> Self {
1537        if func
1538            .params()
1539            .is_some_and(|params| params.iter().all(|param| param.name == "self"))
1540        {
1541            return Self::RoundAfter;
1542        }
1543
1544        match func.name() {
1545            Some(
1546                "emph" | "footnote" | "quote" | "strong" | "highlight" | "overline"
1547                | "underline" | "smallcaps" | "strike" | "sub" | "super",
1548            ) => Self::SquareWithin,
1549            Some("colbreak" | "parbreak" | "linebreak" | "pagebreak") => Self::RoundAfter,
1550            Some("figure" | "table" | "grid" | "stack") => Self::RoundNewline,
1551            _ => Self::RoundWithin,
1552        }
1553    }
1554}
1555
1556#[cfg(test)]
1557mod tests {
1558    use std::borrow::Borrow;
1559    use std::collections::BTreeSet;
1560
1561    use typst::layout::PagedDocument;
1562
1563    use super::{Completion, CompletionKind, autocomplete};
1564    use crate::tests::{FilePos, TestWorld, WorldLike};
1565
1566    /// Quote a string.
1567    macro_rules! q {
1568        ($s:literal) => {
1569            concat!("\"", $s, "\"")
1570        };
1571    }
1572
1573    type Response = Option<(usize, Vec<Completion>)>;
1574
1575    trait ResponseExt {
1576        fn completions(&self) -> &[Completion];
1577        fn labels(&self) -> BTreeSet<&str>;
1578        fn must_be_empty(&self) -> &Self;
1579        fn must_include<'a>(&self, includes: impl IntoIterator<Item = &'a str>) -> &Self;
1580        fn must_exclude<'a>(&self, excludes: impl IntoIterator<Item = &'a str>) -> &Self;
1581        fn must_apply<'a>(&self, label: &str, apply: impl Into<Option<&'a str>>)
1582        -> &Self;
1583    }
1584
1585    impl ResponseExt for Response {
1586        fn completions(&self) -> &[Completion] {
1587            match self {
1588                Some((_, completions)) => completions.as_slice(),
1589                None => &[],
1590            }
1591        }
1592
1593        fn labels(&self) -> BTreeSet<&str> {
1594            self.completions().iter().map(|c| c.label.as_str()).collect()
1595        }
1596
1597        #[track_caller]
1598        fn must_be_empty(&self) -> &Self {
1599            let labels = self.labels();
1600            assert!(
1601                labels.is_empty(),
1602                "expected no suggestions (got {labels:?} instead)"
1603            );
1604            self
1605        }
1606
1607        #[track_caller]
1608        fn must_include<'a>(&self, includes: impl IntoIterator<Item = &'a str>) -> &Self {
1609            let labels = self.labels();
1610            for item in includes {
1611                assert!(
1612                    labels.contains(item),
1613                    "{item:?} was not contained in {labels:?}",
1614                );
1615            }
1616            self
1617        }
1618
1619        #[track_caller]
1620        fn must_exclude<'a>(&self, excludes: impl IntoIterator<Item = &'a str>) -> &Self {
1621            let labels = self.labels();
1622            for item in excludes {
1623                assert!(
1624                    !labels.contains(item),
1625                    "{item:?} was wrongly contained in {labels:?}",
1626                );
1627            }
1628            self
1629        }
1630
1631        #[track_caller]
1632        fn must_apply<'a>(
1633            &self,
1634            label: &str,
1635            apply: impl Into<Option<&'a str>>,
1636        ) -> &Self {
1637            let Some(completion) = self.completions().iter().find(|c| c.label == label)
1638            else {
1639                panic!("found no completion for {label:?}");
1640            };
1641            assert_eq!(completion.apply.as_deref(), apply.into());
1642            self
1643        }
1644    }
1645
1646    #[track_caller]
1647    fn test(world: impl WorldLike, pos: impl FilePos) -> Response {
1648        let world = world.acquire();
1649        let world = world.borrow();
1650        let doc = typst::compile(world).output.ok();
1651        test_with_doc(world, pos, doc.as_ref(), true)
1652    }
1653
1654    #[track_caller]
1655    fn test_implicit(world: impl WorldLike, pos: impl FilePos) -> Response {
1656        let world = world.acquire();
1657        let world = world.borrow();
1658        let doc = typst::compile(world).output.ok();
1659        test_with_doc(world, pos, doc.as_ref(), false)
1660    }
1661
1662    #[track_caller]
1663    fn test_with_addition(
1664        initial_text: &str,
1665        addition: &str,
1666        pos: impl FilePos,
1667    ) -> Response {
1668        let mut world = TestWorld::new(initial_text);
1669        let doc = typst::compile(&world).output.ok();
1670        let end = world.main.text().len();
1671        world.main.edit(end..end, addition);
1672        test_with_doc(&world, pos, doc.as_ref(), true)
1673    }
1674
1675    #[track_caller]
1676    fn test_with_doc(
1677        world: impl WorldLike,
1678        pos: impl FilePos,
1679        doc: Option<&PagedDocument>,
1680        explicit: bool,
1681    ) -> Response {
1682        let world = world.acquire();
1683        let world = world.borrow();
1684        let (source, cursor) = pos.resolve(world);
1685        autocomplete(world, doc, &source, cursor, explicit)
1686    }
1687
1688    #[test]
1689    fn test_autocomplete_hash_expr() {
1690        test("#i", -1).must_include(["int", "if conditional"]);
1691    }
1692
1693    #[test]
1694    fn test_autocomplete_array_method() {
1695        test("#().", -1).must_include(["insert", "remove", "len", "all"]);
1696        test("#{ let x = (1, 2, 3); x. }", -3).must_include(["at", "push", "pop"]);
1697    }
1698
1699    /// Test that extra space before '.' is handled correctly.
1700    #[test]
1701    fn test_autocomplete_whitespace() {
1702        test("#() .", -1).must_exclude(["insert", "remove", "len", "all"]);
1703        test("#{() .}", -2).must_include(["insert", "remove", "len", "all"]);
1704        test("#() .a", -1).must_exclude(["insert", "remove", "len", "all"]);
1705        test("#{() .a}", -2).must_include(["at", "any", "all"]);
1706    }
1707
1708    /// Test that autocomplete in math uses the correct global scope.
1709    #[test]
1710    fn test_autocomplete_math_scope() {
1711        test("$#col$", -2).must_include(["colbreak"]).must_exclude(["colon"]);
1712        test("$col$", -2).must_include(["colon"]).must_exclude(["colbreak"]);
1713    }
1714
1715    /// Test that the `before_window` doesn't slice into invalid byte
1716    /// boundaries.
1717    #[test]
1718    fn test_autocomplete_before_window_char_boundary() {
1719        test("😀😀     #text(font: \"\")", -3);
1720    }
1721
1722    /// Ensure that autocompletion for `#cite(|)` completes bibligraphy labels,
1723    /// but no other labels.
1724    #[test]
1725    fn test_autocomplete_cite_function() {
1726        // First compile a working file to get a document.
1727        let mut world =
1728            TestWorld::new("#bibliography(\"works.bib\") <bib>").with_asset("works.bib");
1729        let doc = typst::compile(&world).output.ok();
1730
1731        // Then, add the invalid `#cite` call. Had the document been invalid
1732        // initially, we would have no populated document to autocomplete with.
1733        let end = world.main.text().len();
1734        world.main.edit(end..end, " #cite()");
1735
1736        test_with_doc(&world, -2, doc.as_ref(), true)
1737            .must_include(["netwok", "glacier-melt", "supplement"])
1738            .must_exclude(["bib"]);
1739    }
1740
1741    #[test]
1742    fn test_autocomplete_ref_function() {
1743        test_with_addition("x<test>", " #ref(<)", -2).must_include(["test"]);
1744    }
1745
1746    #[test]
1747    fn test_autocomplete_ref_shorthand() {
1748        test_with_addition("x<test>", " @", -1).must_include(["test"]);
1749    }
1750
1751    #[test]
1752    fn test_autocomplete_ref_shorthand_with_partial_identifier() {
1753        test_with_addition("x<test>", " @te", -1).must_include(["test"]);
1754    }
1755
1756    #[test]
1757    fn test_autocomplete_ref_identical_labels_returns_single_completion() {
1758        let result = test_with_addition("x<test> y<test>", " @t", -1);
1759        let completions = result.completions();
1760        let label_count =
1761            completions.iter().filter(|c| c.kind == CompletionKind::Label).count();
1762        assert_eq!(label_count, 1);
1763    }
1764
1765    /// Test what kind of brackets we autocomplete for function calls depending
1766    /// on the function and existing parens.
1767    #[test]
1768    fn test_autocomplete_bracket_mode() {
1769        test("#", 1).must_apply("list", "list(${})");
1770        test("#", 1).must_apply("linebreak", "linebreak()${}");
1771        test("#", 1).must_apply("strong", "strong[${}]");
1772        test("#", 1).must_apply("footnote", "footnote[${}]");
1773        test("#", 1).must_apply("figure", "figure(\n  ${}\n)");
1774        test("#", 1).must_apply("table", "table(\n  ${}\n)");
1775        test("#()", 1).must_apply("list", None);
1776        test("#[]", 1).must_apply("strong", None);
1777    }
1778
1779    /// Test that we only complete positional parameters if they aren't
1780    /// already present.
1781    #[test]
1782    fn test_autocomplete_positional_param() {
1783        // No string given yet.
1784        test("#numbering()", -2).must_include(["string", "integer"]);
1785        // String is already given.
1786        test("#numbering(\"foo\", )", -2)
1787            .must_include(["integer"])
1788            .must_exclude(["string"]);
1789        // Integer is already given, but numbering is variadic.
1790        test("#numbering(\"foo\", 1, )", -2)
1791            .must_include(["integer"])
1792            .must_exclude(["string"]);
1793        // After argument list no completions.
1794        test("#numbering()", -1).must_exclude(["string"]);
1795    }
1796
1797    /// Test that autocompletion for values of known type picks up nested
1798    /// values.
1799    #[test]
1800    fn test_autocomplete_value_filter() {
1801        let world = TestWorld::new("#import \"design.typ\": clrs; #rect(fill: )")
1802            .with_source(
1803                "design.typ",
1804                "#let clrs = (a: red, b: blue); #let nums = (a: 1, b: 2)",
1805            );
1806
1807        test(&world, -2)
1808            .must_include(["clrs", "aqua"])
1809            .must_exclude(["nums", "a", "b"]);
1810    }
1811
1812    #[test]
1813    fn test_autocomplete_packages() {
1814        test("#import \"@\"", -2).must_include([q!("@preview/example:0.1.0")]);
1815    }
1816
1817    #[test]
1818    fn test_autocomplete_file_path() {
1819        let world = TestWorld::new("#include \"\"")
1820            .with_source("utils.typ", "")
1821            .with_source("content/a.typ", "#image()")
1822            .with_source("content/b.typ", "#csv(\"\")")
1823            .with_source("content/c.typ", "#include \"\"")
1824            .with_source("content/d.typ", "#pdf.attach(\"\")")
1825            .with_source("content/e.typ", "#math.attach(\"\")")
1826            .with_asset_at("assets/tiger.jpg", "tiger.jpg")
1827            .with_asset_at("assets/rhino.png", "rhino.png")
1828            .with_asset_at("data/example.csv", "example.csv");
1829
1830        test(&world, -2)
1831            .must_include([q!("content/a.typ"), q!("content/b.typ"), q!("utils.typ")])
1832            .must_exclude([q!("assets/tiger.jpg")]);
1833
1834        test(&world, ("content/a.typ", -2))
1835            .must_include([q!("../assets/tiger.jpg"), q!("../assets/rhino.png")])
1836            .must_exclude([q!("../data/example.csv"), q!("b.typ")]);
1837
1838        test(&world, ("content/b.typ", -3)).must_include([q!("../data/example.csv")]);
1839
1840        test(&world, ("content/c.typ", -2))
1841            .must_include([q!("../main.typ"), q!("a.typ"), q!("b.typ")])
1842            .must_exclude([q!("c.typ")]);
1843
1844        test(&world, ("content/d.typ", -2))
1845            .must_include([q!("../assets/tiger.jpg"), q!("../data/example.csv")]);
1846
1847        test(&world, ("content/e.typ", -2)).must_exclude([q!("data/example.csv")]);
1848    }
1849
1850    #[test]
1851    fn test_autocomplete_figure_snippets() {
1852        test("#figure()", -2)
1853            .must_apply("image", "image(\"${}\"),")
1854            .must_apply("table", "table(\n  ${}\n),");
1855
1856        test("#figure(cap)", -2).must_apply("caption", "caption: [${}]");
1857    }
1858
1859    #[test]
1860    fn test_autocomplete_import_items() {
1861        let world = TestWorld::new("#import \"other.typ\": ")
1862            .with_source("second.typ", "#import \"other.typ\": th")
1863            .with_source("other.typ", "#let this = 1; #let that = 2");
1864
1865        test(&world, ("main.typ", 21))
1866            .must_include(["*", "this", "that"])
1867            .must_exclude(["figure"]);
1868        test(&world, ("second.typ", 23))
1869            .must_include(["this", "that"])
1870            .must_exclude(["*", "figure"]);
1871    }
1872
1873    #[test]
1874    fn test_autocomplete_type_methods() {
1875        test("#\"hello\".", -1).must_include(["len", "contains"]);
1876        test("#table().", -1).must_exclude(["cell"]);
1877    }
1878
1879    #[test]
1880    fn test_autocomplete_content_methods() {
1881        test("#show outline.entry: it => it.\n#outline()\n= Hi", 30)
1882            .must_include(["indented", "body", "page"]);
1883    }
1884
1885    #[test]
1886    fn test_autocomplete_symbol_variants() {
1887        test("#sym.arrow.", -1)
1888            .must_include(["r", "dashed"])
1889            .must_exclude(["cases"]);
1890        test("$ arrow. $", -3)
1891            .must_include(["r", "dashed"])
1892            .must_exclude(["cases"]);
1893    }
1894
1895    #[test]
1896    fn test_autocomplete_fonts() {
1897        test("#text(font:)", -2)
1898            .must_include([q!("Libertinus Serif"), q!("New Computer Modern Math")]);
1899
1900        test("#show link: set text(font: )", -2)
1901            .must_include([q!("Libertinus Serif"), q!("New Computer Modern Math")]);
1902
1903        test("#show math.equation: set text(font: )", -2)
1904            .must_include([q!("New Computer Modern Math")])
1905            .must_exclude([q!("Libertinus Serif")]);
1906
1907        test("#show math.equation: it => { set text(font: )\nit }", -7)
1908            .must_include([q!("New Computer Modern Math")])
1909            .must_exclude([q!("Libertinus Serif")]);
1910    }
1911
1912    #[test]
1913    fn test_autocomplete_typed_html() {
1914        test("#html.div(translate: )", -2)
1915            .must_include(["true", "false"])
1916            .must_exclude([q!("yes"), q!("no")]);
1917        test("#html.input(value: )", -2).must_include(["float", "string", "red", "blue"]);
1918        test("#html.div(role: )", -2).must_include([q!("alertdialog")]);
1919    }
1920
1921    #[test]
1922    fn test_autocomplete_in_function_params_after_comma_and_colon() {
1923        let document = "#text(size: 12pt, [])";
1924
1925        // After colon
1926        test(document, 11).must_include(["length"]);
1927        test_implicit(document, 11).must_include(["length"]);
1928
1929        test(document, 12).must_include(["length"]);
1930        test_implicit(document, 12).must_include(["length"]);
1931
1932        // After comma
1933        test(document, 17).must_include(["font"]);
1934        test_implicit(document, 17).must_be_empty();
1935
1936        test(document, 18).must_include(["font"]);
1937        test_implicit(document, 18).must_include(["font"]);
1938    }
1939
1940    #[test]
1941    fn test_autocomplete_in_list_literal() {
1942        let document = "#let val = 0\n#(1, \"one\")";
1943
1944        // After opening paren
1945        test(document, 15).must_include(["color", "val"]);
1946        test_implicit(document, 15).must_be_empty();
1947
1948        // After first element
1949        test(document, 16).must_be_empty();
1950        test_implicit(document, 16).must_be_empty();
1951
1952        // After comma
1953        test(document, 17).must_include(["color", "val"]);
1954        test_implicit(document, 17).must_be_empty();
1955
1956        test(document, 18).must_include(["color", "val"]);
1957        test_implicit(document, 18).must_be_empty();
1958    }
1959
1960    #[test]
1961    fn test_autocomplete_in_dict_literal() {
1962        let document = "#let first = 0\n#(first: 1, second: one)";
1963
1964        // After opening paren
1965        test(document, 17).must_be_empty();
1966        test_implicit(document, 17).must_be_empty();
1967
1968        // After first key
1969        test(document, 22).must_be_empty();
1970        test_implicit(document, 22).must_be_empty();
1971
1972        // After colon
1973        test(document, 23).must_include(["align", "first"]);
1974        test_implicit(document, 23).must_be_empty();
1975
1976        test(document, 24).must_include(["align", "first"]);
1977        test_implicit(document, 24).must_be_empty();
1978
1979        // After first value
1980        test(document, 25).must_be_empty();
1981        test_implicit(document, 25).must_be_empty();
1982
1983        // After comma
1984        test(document, 26).must_be_empty();
1985        test_implicit(document, 26).must_be_empty();
1986
1987        test(document, 27).must_be_empty();
1988        test_implicit(document, 27).must_be_empty();
1989    }
1990
1991    #[test]
1992    fn test_autocomplete_in_destructuring() {
1993        let document = "#let value = 20\n#let (va: value) = (va: 10)";
1994
1995        // At destructuring rename pattern source
1996        test(document, 24).must_be_empty();
1997        test_implicit(document, 24).must_be_empty();
1998    }
1999}