Skip to main content

typst_ide/
complete.rs

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