ink_analyzer/analysis/
completions.rs

1//! ink! attribute completions.
2
3use ink_analyzer_ir::ast::HasAttrs;
4use ink_analyzer_ir::syntax::{AstNode, AstToken, SyntaxKind, SyntaxToken, TextRange, TextSize};
5use ink_analyzer_ir::{ast, ChainExtension, Contract, Event, EventV2, InkImpl, TraitDefinition};
6use ink_analyzer_ir::{
7    InkArg, InkArgKind, InkArgValueKind, InkAttributeKind, InkEntity, InkFile, InkMacroKind,
8    Version,
9};
10
11use super::{
12    text_edit::{self, TextEdit},
13    utils,
14};
15
16/// An ink! attribute completion item.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Completion {
19    /// Label which identifies the completion.
20    pub label: String,
21    /// Range of identifier that is being completed.
22    pub range: TextRange,
23    /// Replacement text for the completion.
24    pub edit: TextEdit,
25    /// Descriptive information about the completion.
26    pub detail: Option<String>,
27    /// Type of item being completed.
28    pub kind: CompletionKind,
29}
30
31/// An ink! attribute completion item kind.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum CompletionKind {
34    Attr,
35    Enum,
36    Field,
37    Fn,
38    Mod,
39    Struct,
40    Trait,
41}
42
43/// Computes ink! attribute completions at the given offset.
44pub fn completions(file: &InkFile, offset: TextSize, version: Version) -> Vec<Completion> {
45    let mut results = Vec::new();
46
47    // Compute ink! attribute macro completions.
48    macro_completions(&mut results, file, offset, version);
49
50    // Compute ink! attribute argument completions.
51    argument_completions(&mut results, file, offset, version);
52
53    // Compute ink! entity completions.
54    entity_completions(&mut results, file, offset, version);
55
56    results
57}
58
59/// Computes ink! attribute macro completions at the given offset.
60pub fn macro_completions(
61    results: &mut Vec<Completion>,
62    file: &InkFile,
63    offset: TextSize,
64    version: Version,
65) {
66    let item_at_offset = file.item_at_offset(offset);
67
68    // Only computes completions if a focused token can be determined.
69    if let Some(focused_token) = item_at_offset.focused_token() {
70        // Only computes completions for attributes.
71        if let Some((attr, ..)) = item_at_offset.normalized_parent_attr() {
72            let focused_token_is_left_bracket = focused_token.kind() == SyntaxKind::L_BRACK;
73            let prev_token_is_left_bracket = item_at_offset
74                .prev_non_trivia_token()
75                .is_some_and(|prev_token| prev_token.kind() == SyntaxKind::L_BRACK);
76            let focused_token_is_ink_crate_name = matches!(focused_token.text(), "ink" | "ink_e2e");
77            let focused_token_is_ink_crate_name_or_colon_prefix =
78                focused_token_is_ink_crate_name || matches!(focused_token.text(), "::" | ":");
79            let focused_token_is_in_ink_crate_path_segment =
80                (matches!(focused_token.text(), "ink" | "ink_e2e")
81                    && item_at_offset
82                        .prev_non_trivia_token()
83                        .is_some_and(|prev_token| prev_token.kind() == SyntaxKind::L_BRACK))
84                    || (matches!(focused_token.text(), "::" | ":")
85                        && item_at_offset
86                            .prev_non_trivia_token()
87                            .as_ref()
88                            .is_some_and(|token| matches!(token.text(), "ink" | "ink_e2e")))
89                    || (item_at_offset
90                        .prev_non_trivia_token()
91                        .as_ref()
92                        .is_some_and(|token| token.text() == "::")
93                        && item_at_offset
94                            .prev_non_trivia_token()
95                            .as_ref()
96                            .and_then(|prev_token| {
97                                ink_analyzer_ir::closest_non_trivia_token(
98                                    prev_token,
99                                    SyntaxToken::prev_token,
100                                )
101                            })
102                            .as_ref()
103                            .is_some_and(|token| matches!(token.text(), "ink" | "ink_e2e")));
104
105            // Only computes completions if the focused token is in an attribute macro path context.
106            if focused_token_is_left_bracket
107                || prev_token_is_left_bracket
108                || focused_token_is_in_ink_crate_path_segment
109            {
110                // Removes the delimiter (i.e `[`) from text range if it's the focused token.
111                let edit_range = if focused_token_is_left_bracket {
112                    let focused_token_end = focused_token.text_range().end();
113                    TextRange::new(focused_token_end, focused_token_end)
114                } else {
115                    focused_token.text_range()
116                };
117
118                // Only suggest ink! attribute macros if the AST item has no other ink! attributes.
119                let mut ink_macro_suggestions = Vec::new();
120                let ast_item_option = ink_analyzer_ir::parent_ast_item(attr.syntax());
121                let has_other_ink_siblings = ast_item_option.as_ref().is_some_and(|item| {
122                    ink_analyzer_ir::ink_attrs(item.syntax()).any(|it| it.syntax() != attr.syntax())
123                });
124                let has_other_ink_macro_siblings = ast_item_option.as_ref().is_some_and(|item| {
125                    ink_analyzer_ir::ink_attrs(item.syntax()).any(|it| {
126                        it.syntax() != attr.syntax()
127                            && matches!(it.kind(), InkAttributeKind::Macro(_))
128                    })
129                });
130                if !has_other_ink_siblings {
131                    // Suggests ink! attribute macros based on the context (if any).
132                    ink_macro_suggestions = match item_at_offset
133                        .normalized_parent_ast_item_keyword()
134                    {
135                        // Returns suggestions based on the AST item type keyword.
136                        Some((ast_item_keyword, ..)) => {
137                            utils::valid_ink_macros_by_syntax_kind(ast_item_keyword.kind(), version)
138                        }
139                        // Handles the case where the AST item type is unknown.
140                        None => {
141                            // Returns all valid ink! attribute macro suggestions if focused token is part of an ink! path segment.
142                            if focused_token_is_in_ink_crate_path_segment {
143                                if version.is_legacy() {
144                                    vec![
145                                        InkMacroKind::ChainExtension,
146                                        InkMacroKind::Contract,
147                                        InkMacroKind::StorageItem,
148                                        InkMacroKind::Test,
149                                        InkMacroKind::TraitDefinition,
150                                        InkMacroKind::E2ETest,
151                                    ]
152                                } else if version.is_v5() {
153                                    vec![
154                                        InkMacroKind::ChainExtension,
155                                        InkMacroKind::Contract,
156                                        InkMacroKind::Event,
157                                        InkMacroKind::ScaleDerive,
158                                        InkMacroKind::StorageItem,
159                                        InkMacroKind::Test,
160                                        InkMacroKind::TraitDefinition,
161                                        InkMacroKind::E2ETest,
162                                    ]
163                                } else {
164                                    vec![
165                                        InkMacroKind::Contract,
166                                        InkMacroKind::ContractRef,
167                                        InkMacroKind::Error,
168                                        InkMacroKind::Event,
169                                        InkMacroKind::ScaleDerive,
170                                        InkMacroKind::StorageItem,
171                                        InkMacroKind::Test,
172                                        InkMacroKind::TraitDefinition,
173                                        InkMacroKind::E2ETest,
174                                    ]
175                                }
176                            } else {
177                                // Returns nothing if the ink! context can't be determined.
178                                Vec::new()
179                            }
180                        }
181                    };
182
183                    // Filters suggestions by the matching ink! macro crate
184                    // if a complete `ink` or `ink_e2e` path segment is already present before the focused token.
185                    if focused_token_is_in_ink_crate_path_segment
186                        && !focused_token_is_ink_crate_name
187                    {
188                        if let Some(ink_crate_name) = attr
189                            .path()
190                            .and_then(|it| it.first_segment())
191                            .map(|it| it.to_string())
192                        {
193                            ink_macro_suggestions
194                                .retain(|macro_kind| macro_kind.crate_name() == ink_crate_name);
195                        }
196                    }
197
198                    // Filters suggestions by the focused prefix if the focused token is
199                    // not a delimiter nor in the `ink::` or `ink_e2e::` path segment position.
200                    if !focused_token_is_left_bracket
201                        && !prev_token_is_left_bracket
202                        && !focused_token_is_ink_crate_name_or_colon_prefix
203                    {
204                        if let Some(prefix) = item_at_offset.focused_token_prefix() {
205                            ink_macro_suggestions
206                                .retain(|macro_kind| macro_kind.macro_name().starts_with(prefix));
207                        }
208                    }
209
210                    // Filters out invalid ink! attribute macro suggestions based on parent ink! scope (if any).
211                    if let Some(attr_parent) = attr.syntax().parent() {
212                        utils::remove_invalid_ink_macro_suggestions_for_parent_ink_scope(
213                            &mut ink_macro_suggestions,
214                            &attr_parent,
215                            version,
216                        );
217                    }
218                }
219
220                // Add context-specific completions to accumulator (if any).
221                if !ink_macro_suggestions.is_empty() {
222                    for macro_kind in ink_macro_suggestions {
223                        let edit = format!(
224                            "{}{}{}",
225                            // Only includes `ink` if the focused token is either the `[` delimiter,
226                            // the next token right after the `[` delimiter, the `ink` path segment.
227                            if focused_token_is_left_bracket
228                                || prev_token_is_left_bracket
229                                || matches!(focused_token.text(), "ink" | "ink_e2e")
230                            {
231                                macro_kind.crate_name()
232                            } else {
233                                ""
234                            },
235                            // Only includes `ink` if the focused token is either the `[` delimiter,
236                            // the next token right after the `[` delimiter or
237                            // anything in the `ink::` path segment position
238                            if focused_token_is_left_bracket
239                                || prev_token_is_left_bracket
240                                || focused_token_is_ink_crate_name_or_colon_prefix
241                            {
242                                "::"
243                            } else {
244                                ""
245                            },
246                            macro_kind.macro_name()
247                        );
248                        results.push(Completion {
249                            label: edit.clone(),
250                            range: edit_range,
251                            edit: TextEdit::replace(edit, edit_range),
252                            detail: Some(format!("ink! {macro_kind} attribute macro.")),
253                            kind: CompletionKind::Attr,
254                        });
255                    }
256                } else if prev_token_is_left_bracket && !has_other_ink_macro_siblings {
257                    // Suggests the `ink` and `ink_e2e` path segments if
258                    // the focused token is an `ink` or `ink_e2e` prefix and is also
259                    // the next token right after the `[` delimiter.
260                    let focused_token_prefix = item_at_offset.focused_token_prefix();
261                    let ink_path_suggestions = if has_other_ink_siblings {
262                        vec![("ink()", Some("ink($1)"), "ink! attribute")]
263                    } else {
264                        vec![
265                            ("ink", None, "ink! attribute macro"),
266                            ("ink_e2e", None, "ink! e2e attribute macro"),
267                        ]
268                    };
269                    for (ink_macro_crate_name, ink_macro_crate_name_snippet, detail) in
270                        ink_path_suggestions
271                    {
272                        if focused_token_prefix
273                            .is_some_and(|prefix| ink_macro_crate_name.starts_with(prefix))
274                        {
275                            results.push(Completion {
276                                label: ink_macro_crate_name.to_owned(),
277                                range: edit_range,
278                                edit: TextEdit::replace_with_snippet(
279                                    ink_macro_crate_name.to_owned(),
280                                    edit_range,
281                                    ink_macro_crate_name_snippet.map(ToString::to_string),
282                                ),
283                                detail: Some(detail.to_owned()),
284                                kind: CompletionKind::Attr,
285                            });
286                        }
287                    }
288                }
289            }
290        }
291    }
292}
293
294/// Computes ink! attribute argument completions at the given offset.
295pub fn argument_completions(
296    results: &mut Vec<Completion>,
297    file: &InkFile,
298    offset: TextSize,
299    version: Version,
300) {
301    let item_at_offset = file.item_at_offset(offset);
302
303    // Only computes completions if a focused token can be determined.
304    if let Some(focused_token) = item_at_offset.focused_token() {
305        // Only computes completions for ink! attributes.
306        if let Some((ink_attr, ..)) = item_at_offset.normalized_parent_ink_attr() {
307            let focused_token_is_left_parenthesis = focused_token.kind() == SyntaxKind::L_PAREN;
308            let prev_non_trivia_token_is_left_parenthesis = item_at_offset
309                .prev_non_trivia_token()
310                .is_some_and(|prev_token| prev_token.kind() == SyntaxKind::L_PAREN);
311            let focused_token_is_comma = focused_token.kind() == SyntaxKind::COMMA;
312            let prev_non_trivia_token_is_comma = item_at_offset
313                .prev_non_trivia_token()
314                .is_some_and(|prev_token| prev_token.kind() == SyntaxKind::COMMA);
315            let prev_token_is_whitespace = focused_token
316                .prev_token()
317                .is_none_or(|prev_token| prev_token.kind() == SyntaxKind::WHITESPACE);
318
319            // Only computes completions if the focused token is in an argument context.
320            if focused_token_is_left_parenthesis
321                || prev_non_trivia_token_is_left_parenthesis
322                || focused_token_is_comma
323                || prev_non_trivia_token_is_comma
324            {
325                // Suggestions ink! attribute arguments based on the context (if any).
326                let mut ink_arg_suggestions = Vec::new();
327                let mut edit_range = None;
328                let mut is_nested = false;
329                let mut parent_arg_name = None;
330
331                // Suggests "nested" ink! attribute arguments (if appropriate).
332                let is_valid_focused_arg = |arg: &InkArg| {
333                    *arg.kind() != InkArgKind::Unknown
334                        && arg.text_range().contains_inclusive(offset)
335                        && !arg.meta().is_empty()
336                };
337                if let Some(top_arg) = ink_attr.args().iter().find(|arg| is_valid_focused_arg(arg))
338                {
339                    let mut nested_arg = None;
340                    while let Some(arg) = nested_arg
341                        .as_ref()
342                        .unwrap_or(top_arg)
343                        .nested()
344                        .filter(is_valid_focused_arg)
345                    {
346                        nested_arg = Some(arg);
347                    }
348
349                    let focused_arg = nested_arg.as_ref().unwrap_or(top_arg);
350                    let nested_arg_suggestions = match InkArgValueKind::from(*focused_arg.kind()) {
351                        InkArgValueKind::Arg(kind, _) => vec![kind],
352                        InkArgValueKind::Choice(kind_1, kind_2, _) => vec![kind_1, kind_2],
353                        _ => Vec::new(),
354                    };
355                    if !nested_arg_suggestions.is_empty() {
356                        ink_arg_suggestions = nested_arg_suggestions;
357                        // Unknown args are filtered out by `is_valid_focused_arg`, so the arg must have a name at this point.
358                        let meta_name = focused_arg
359                            .name()
360                            .expect("Valid ink! args must have a name");
361                        let name_end = meta_name.syntax().text_range().end();
362                        edit_range = Some(TextRange::new(name_end, focused_arg.text_range().end()));
363                        is_nested = true;
364                        parent_arg_name = Some(meta_name.to_string());
365                    }
366                }
367
368                // Suggests "normal" ink! attribute arguments (if any).
369                if ink_arg_suggestions.is_empty() {
370                    // Suggests ink! attribute arguments based on the context (if any).
371                    ink_arg_suggestions = match ink_attr.kind() {
372                        // For unknown ink! attributes, suggestions are based on the parent item (if any).
373                        InkAttributeKind::Macro(InkMacroKind::Unknown)
374                        | InkAttributeKind::Arg(InkArgKind::Unknown) => {
375                            match item_at_offset.normalized_parent_item_syntax_kind() {
376                                // Returns suggestions based on the parent item kind.
377                                Some(parent_item_kind) => {
378                                    utils::valid_ink_args_by_syntax_kind(parent_item_kind, version)
379                                }
380                                // Handles cases where either the parent item kind is unknown.
381                                // Returns all attribute arguments that don't require a macro
382                                // if the AST item type is unknown.
383                                None => {
384                                    if version.is_legacy() {
385                                        vec![
386                                            InkArgKind::Anonymous,
387                                            InkArgKind::Constructor,
388                                            InkArgKind::Default,
389                                            InkArgKind::Event,
390                                            InkArgKind::Extension,
391                                            InkArgKind::HandleStatus,
392                                            InkArgKind::Impl,
393                                            InkArgKind::Message,
394                                            InkArgKind::Namespace,
395                                            InkArgKind::Payable,
396                                            InkArgKind::Selector,
397                                            InkArgKind::Storage,
398                                            InkArgKind::Topic,
399                                        ]
400                                    } else if version.is_v5() {
401                                        vec![
402                                            InkArgKind::Anonymous,
403                                            InkArgKind::Constructor,
404                                            InkArgKind::Default,
405                                            InkArgKind::Event,
406                                            InkArgKind::Function,
407                                            InkArgKind::HandleStatus,
408                                            InkArgKind::Impl,
409                                            InkArgKind::Message,
410                                            InkArgKind::Namespace,
411                                            InkArgKind::Payable,
412                                            InkArgKind::Selector,
413                                            InkArgKind::SignatureTopic,
414                                            InkArgKind::Storage,
415                                            InkArgKind::Topic,
416                                        ]
417                                    } else {
418                                        vec![
419                                            InkArgKind::Abi,
420                                            InkArgKind::Anonymous,
421                                            InkArgKind::Constructor,
422                                            InkArgKind::Default,
423                                            InkArgKind::Event,
424                                            InkArgKind::Impl,
425                                            InkArgKind::Message,
426                                            InkArgKind::Name,
427                                            InkArgKind::Namespace,
428                                            InkArgKind::Payable,
429                                            InkArgKind::Selector,
430                                            InkArgKind::SignatureTopic,
431                                            InkArgKind::Storage,
432                                            InkArgKind::Topic,
433                                        ]
434                                    }
435                                }
436                            }
437                        }
438                        // For known/valid primary ink! attribute kinds, only suggest valid ink! attribute siblings.
439                        kind => utils::valid_sibling_ink_args(*kind, version),
440                    };
441
442                    // Filters out duplicates, conflicting and invalidly scoped ink! arguments.
443                    utils::remove_duplicate_conflicting_and_invalid_scope_ink_arg_suggestions(
444                        &mut ink_arg_suggestions,
445                        &ink_attr,
446                        version,
447                    );
448                }
449
450                // Filters suggestions by the focused prefix if the focused token is not a delimiter.
451                if !focused_token_is_left_parenthesis && !focused_token_is_comma {
452                    if let Some(prefix) = item_at_offset.focused_token_prefix() {
453                        ink_arg_suggestions.retain(|arg_kind| {
454                            format!("{arg_kind}").starts_with(prefix)
455                                || parent_arg_name.as_ref().is_some_and(|name| name == prefix)
456                        });
457                    }
458                }
459
460                // Add completions to accumulator.
461                for arg_kind in ink_arg_suggestions {
462                    let (prefix, suffix) = if is_nested {
463                        ("(", ")")
464                    } else if focused_token_is_comma
465                        || (prev_non_trivia_token_is_comma && !prev_token_is_whitespace)
466                    {
467                        // Inserts some space between the comma and the argument.
468                        (" ", "")
469                    } else {
470                        ("", "")
471                    };
472
473                    let range = edit_range.unwrap_or(
474                        if focused_token_is_left_parenthesis || focused_token_is_comma {
475                            // Removes the delimiter (i.e `(` and `,`) from text range if it's the focused token.
476                            let focused_token_end = focused_token.text_range().end();
477                            TextRange::new(focused_token_end, focused_token_end)
478                        } else {
479                            focused_token.text_range()
480                        },
481                    );
482                    let (edit, snippet) = utils::ink_arg_insert_text(
483                        arg_kind,
484                        version,
485                        Some(range.end()),
486                        Some(&ink_attr),
487                    );
488                    results.push(Completion {
489                        label: edit.clone(),
490                        range,
491                        edit: TextEdit::replace_with_snippet(
492                            format!("{prefix}{edit}{suffix}"),
493                            range,
494                            snippet.map(|snippet| format!("{prefix}{snippet}{suffix}")),
495                        ),
496                        detail: Some(format!("ink! {arg_kind} attribute argument.")),
497                        kind: CompletionKind::Attr,
498                    });
499                }
500            }
501        }
502    }
503}
504
505/// Computes ink! entity completions at the given offset.
506pub fn entity_completions(
507    results: &mut Vec<Completion>,
508    file: &InkFile,
509    offset: TextSize,
510    version: Version,
511) {
512    let item_at_offset = file.item_at_offset(offset);
513
514    // Only computes completions if a focused token can be determined, and it's not a comment.
515    let Some(focused_token) = item_at_offset
516        .focused_token()
517        .filter(|token| token.kind() != SyntaxKind::COMMENT)
518    else {
519        return;
520    };
521
522    // Bail if focused token is part of an attribute.
523    if item_at_offset.parent_attr().is_some() {
524        return;
525    }
526
527    let normalized_focused_text = if focused_token.kind().is_trivia() {
528        ""
529    } else {
530        focused_token.text()
531    };
532
533    // Computes the edit range as the entire line for the offset,
534    // except if the focused token is whitespace with a new line, in which case the passed offset is used.
535    let mut prev_non_trivia_line_siblings = Vec::new();
536    let mut next_non_trivia_line_siblings = Vec::new();
537    let mut last_line_sibling = None;
538    let is_inline_token =
539        |token: &SyntaxToken| !token.kind().is_trivia() || !token.text().contains('\n');
540    let mut line_start = || {
541        std::iter::successors(
542            focused_token.prev_token().filter(is_inline_token),
543            |token| token.prev_token().filter(is_inline_token),
544        )
545        .inspect(|token| {
546            if !token.kind().is_trivia() {
547                prev_non_trivia_line_siblings.push(token.text().to_owned());
548            }
549        })
550        .last()
551        .as_ref()
552        .map(SyntaxToken::text_range)
553        .unwrap_or_else(|| focused_token.text_range())
554        .start()
555    };
556    let mut line_end = || {
557        std::iter::successors(
558            focused_token.next_token().filter(is_inline_token),
559            |token| token.next_token().filter(is_inline_token),
560        )
561        .inspect(|token| {
562            if !token.kind().is_trivia() {
563                next_non_trivia_line_siblings.push(token.text().to_owned());
564            }
565            last_line_sibling = Some(token.clone());
566        })
567        .last()
568        .as_ref()
569        .map(SyntaxToken::text_range)
570        .unwrap_or_else(|| focused_token.text_range())
571        .end()
572    };
573    let (mut start, end) =
574        if focused_token.kind() == SyntaxKind::WHITESPACE && focused_token.text().contains('\n') {
575            let ws_offset = offset - focused_token.text_range().start();
576            let (ws_before, ws_after) = focused_token.text().split_at(ws_offset.into());
577            let is_cursor_after_newline = ws_before.contains('\n');
578            let is_cursor_before_newline = ws_after.contains('\n');
579            match (is_cursor_after_newline, is_cursor_before_newline) {
580                // Between newlines.
581                (true, true) => (offset, offset),
582                // After newline.
583                (true, false) => (offset, line_end()),
584                // Before newline.
585                (false, true) => (line_start(), offset),
586                // No newlines, should be unreachable.
587                (false, false) => (line_start(), line_end()),
588            }
589        } else {
590            // Not focused on/in whitespace.
591            (line_start(), line_end())
592        };
593    // Normalize start offset to remove/ignore unnecessary indenting.
594    if let Some(start_token) = file
595        .item_at_offset(start)
596        .token_at_offset()
597        .clone()
598        .left_biased()
599    {
600        let ws_token = if start_token.kind() == SyntaxKind::WHITESPACE {
601            Some(start_token)
602        } else {
603            start_token.prev_token()
604        };
605        if let Some(ws_token) = ws_token.filter(|token| token.kind() == SyntaxKind::WHITESPACE) {
606            if ws_token.text_range().end() <= start {
607                let indent = utils::end_indenting(ws_token.text());
608                if !indent.is_empty() {
609                    start = ws_token.text_range().end() - TextSize::from(indent.len() as u32);
610                }
611            } else {
612                let ws_offset = start - ws_token.text_range().start();
613                let (ws_before, _) = ws_token.text().split_at(ws_offset.into());
614                let indent = utils::end_indenting(ws_before);
615                if !indent.is_empty() {
616                    start -= TextSize::from(indent.len() as u32);
617                }
618            }
619        }
620    }
621    let range = TextRange::new(start, end);
622
623    // Checks if the focused text or another word on the line matches one of the options.
624    let is_line_affix_of = |options: &[&str]| {
625        // Empty line.
626        (normalized_focused_text.is_empty()
627            && prev_non_trivia_line_siblings.is_empty()
628            && next_non_trivia_line_siblings.is_empty())
629            // Focused text is a substring of one of the keywords.
630            || (!normalized_focused_text.is_empty()
631                && options
632                    .iter()
633                    .any(|option| option.contains(normalized_focused_text)))
634            // At least one word on the line is a substring of one of the keywords.
635            || prev_non_trivia_line_siblings
636                .iter()
637                .chain(next_non_trivia_line_siblings.iter())
638                .any(|focused_sibling_text| {
639                    !focused_sibling_text.is_empty()
640                        && options
641                            .iter()
642                            .any(|option| option.contains(focused_sibling_text))
643                })
644    };
645
646    // Checks if the next non-trivia token is wrapped in an error node.
647    // Useful for determining whether to offer suggestions more conservatively
648    // (e.g. when adding items that shouldn't be duplicated).
649    let is_next_node_error = || {
650        ink_analyzer_ir::closest_non_trivia_token(focused_token, SyntaxToken::next_token)
651            .as_ref()
652            .and_then(SyntaxToken::parent)
653            .is_some_and(|token| token.kind() == SyntaxKind::ERROR)
654    };
655
656    macro_rules! add_event_v2 {
657        ($indent: expr, $module: expr) => {
658            if version.is_gte_v5() && is_line_affix_of(&["struct", "event"]) {
659                results.push(Completion {
660                    label: "#[ink::event]..pub struct Event {...}".to_owned(),
661                    range,
662                    edit: text_edit::add_event_v2(range, $indent, $module),
663                    detail: Some("ink! event 2.0".to_owned()),
664                    kind: CompletionKind::Struct,
665                });
666            }
667        };
668        () => {
669            add_event_v2!(None, None)
670        };
671    }
672
673    // Determines the "focused item" for the completion context (if any).
674    let is_record_field =
675        ink_analyzer_ir::closest_ancestor_ast_type::<_, ast::RecordField>(focused_token).is_some()
676            || ink_analyzer_ir::closest_ancestor_ast_type::<_, ast::RecordFieldList>(focused_token)
677                .is_some();
678    let is_record_field_or_ws_only_line = is_record_field
679        || (focused_token.kind() == SyntaxKind::WHITESPACE
680            && prev_non_trivia_line_siblings.is_empty()
681            && next_non_trivia_line_siblings.is_empty());
682    let focused_item = if is_record_field_or_ws_only_line {
683        // Record fields (e.g. `struct` fields) and whitespace-only lines have no wrapping "focused" item.
684        None
685    } else {
686        item_at_offset.parent_ast_item()
687    };
688    // Bail if focused token is part of an item that spans multiple lines,
689    // except if the next token is an error (as that likely just indicates an intermittent join
690    // of the current incomplete item to the next one by the parser)
691    // or the focused token is fully enclosed by a `mod` or `impl` parent.
692    let is_multi_line_focused_item = focused_item.as_ref().is_some_and(|focused_item| {
693        focused_item.syntax().text_range().end()
694            > last_line_sibling
695                .as_ref()
696                .map(SyntaxToken::text_range)
697                .unwrap_or_else(|| focused_token.text_range())
698                .end()
699    });
700    let is_parent_mod_or_impl = || {
701        focused_item.as_ref().is_some_and(|focused_item| {
702            matches!(
703                focused_item.syntax().kind(),
704                SyntaxKind::MODULE | SyntaxKind::IMPL
705            ) && utils::ast_item_declaration_range(focused_item)
706                .as_ref()
707                .is_some_and(|decl_range| {
708                    let focus_range = focused_token.text_range();
709                    focus_range.start() > decl_range.end()
710                        && focus_range.end() < focused_item.syntax().text_range().end()
711                })
712        })
713    };
714    if is_multi_line_focused_item && (!is_next_node_error() && !is_parent_mod_or_impl()) {
715        return;
716    }
717
718    // Determines the "parent item" for the completion context (if any).
719    let parent_item = if is_record_field_or_ws_only_line {
720        // Completion context "parent item" for record fields (e.g. `struct` fields) and
721        // whitespace-only lines is the direct parent item.
722        item_at_offset.parent_ast_item()
723    } else if is_parent_mod_or_impl() {
724        focused_item
725    } else {
726        // Otherwise, the completion context "parent item" is the parent of the "focused" item.
727        focused_item
728            .and_then(|focused_item| ink_analyzer_ir::parent_ast_item(focused_item.syntax()))
729    };
730    if let Some(parent_item) = parent_item {
731        // Computes completions based on parent item.
732        match parent_item {
733            ast::Item::Module(module_item) => {
734                if let Some(contract) = Contract::cast(module_item.syntax().clone()) {
735                    if contract.storage().is_none()
736                        && is_line_affix_of(&["struct", "storage"])
737                        && !is_next_node_error()
738                    {
739                        // Adds ink! storage.
740                        results.push(Completion {
741                            label: "#[ink(storage)]..pub struct Storage {...}".to_owned(),
742                            range,
743                            edit: text_edit::add_storage(&contract, range),
744                            detail: Some("ink! storage".to_owned()),
745                            kind: CompletionKind::Struct,
746                        });
747                    }
748
749                    // Adds ink! event 2.0.
750                    let indent = utils::item_children_indenting(module_item.syntax());
751                    add_event_v2!((!indent.is_empty()).then_some(&indent), Some(&module_item));
752
753                    // Adds ink! event.
754                    if is_line_affix_of(&["struct", "event"]) {
755                        results.push(Completion {
756                            label: "#[ink(event)]..pub struct Event {...}".to_owned(),
757                            range,
758                            edit: text_edit::add_event_v1(&module_item, range),
759                            detail: Some("ink! event".to_owned()),
760                            kind: CompletionKind::Struct,
761                        });
762                    }
763                } else {
764                    let is_cfg_test = module_item
765                        .attrs()
766                        .any(|attr| utils::is_cfg_test_attr(&attr));
767                    if is_cfg_test && is_line_affix_of(&["fn", "test"]) {
768                        // Adds ink! test.
769                        results.push(Completion {
770                            label: "#[ink::test]..fn test(..) {...}".to_owned(),
771                            range,
772                            edit: text_edit::add_test(&module_item, range),
773                            detail: Some("ink! test".to_owned()),
774                            kind: CompletionKind::Fn,
775                        });
776                    }
777
778                    let is_cfg_e2e_tests = module_item
779                        .attrs()
780                        .any(|attr| utils::is_cfg_e2e_tests_attr(&attr));
781                    if is_cfg_e2e_tests && is_line_affix_of(&["async", "fn", "test", "e2e"]) {
782                        // Adds ink! e2e test.
783                        results.push(Completion {
784                            label: "#[ink_e2e::test]..async fn test(..) {...}".to_owned(),
785                            range,
786                            edit: text_edit::add_e2e_test(&module_item, range, version),
787                            detail: Some("ink! e2e test".to_owned()),
788                            kind: CompletionKind::Fn,
789                        });
790                    }
791                }
792            }
793            ast::Item::Impl(impl_item) => {
794                // Ignore trait implementations.
795                if impl_item.trait_().is_some() {
796                    return;
797                }
798                // Bail if not an ink! impl and parent isn't an ink! contract.
799                if !InkImpl::can_cast(impl_item.syntax())
800                    && ink_analyzer_ir::ink_parent::<Contract>(impl_item.syntax()).is_none()
801                {
802                    return;
803                };
804
805                // Adds ink! constructor.
806                if is_line_affix_of(&["fn", "constructor", "new"]) {
807                    results.push(Completion {
808                        label: "#[ink(constructor)]..pub fn new(..) {...}".to_owned(),
809                        range,
810                        edit: text_edit::add_constructor_to_impl(&impl_item, range),
811                        detail: Some("ink! constructor".to_owned()),
812                        kind: CompletionKind::Fn,
813                    });
814                }
815
816                // Adds ink! message.
817                if is_line_affix_of(&["fn", "message"]) {
818                    results.push(Completion {
819                        label: "#[ink(message)]..pub fn message(..) {...}".to_owned(),
820                        range,
821                        edit: text_edit::add_message_to_impl(&impl_item, range),
822                        detail: Some("ink! message".to_owned()),
823                        kind: CompletionKind::Fn,
824                    });
825                }
826            }
827            ast::Item::Trait(trait_item) => {
828                if let Some(trait_def) = TraitDefinition::cast(trait_item.syntax().clone()) {
829                    // Adds ink! message declaration.
830                    if is_line_affix_of(&["fn", "message"]) {
831                        results.push(Completion {
832                            label: "#[ink(message)]..fn message(..);".to_owned(),
833                            range,
834                            edit: text_edit::add_message_to_trait_def(&trait_def, range),
835                            detail: Some("ink! message".to_owned()),
836                            kind: CompletionKind::Fn,
837                        });
838                    }
839                } else if let Some(chain_extension) =
840                    ChainExtension::cast(trait_item.syntax().clone())
841                {
842                    // Add `ErrorCode` type.
843                    if chain_extension.error_code().is_none()
844                        && is_line_affix_of(&["type", "error", "code", "ErrorCode"])
845                    {
846                        results.push(Completion {
847                            label: "type ErrorCode = ..;".to_owned(),
848                            range,
849                            edit: text_edit::add_error_code_type(&chain_extension, range),
850                            detail: Some("`ErrorCode` type for ink! chain extension".to_owned()),
851                            kind: CompletionKind::Fn,
852                        });
853                    }
854
855                    // Adds ink! extension.
856                    if is_line_affix_of(&["fn", "extension", "function"]) {
857                        results.push(Completion {
858                            label: format!(
859                                "#[ink({}=..)]..pub fn extension(..);",
860                                if version.is_legacy() {
861                                    "extension"
862                                } else {
863                                    "function"
864                                }
865                            ),
866                            range,
867                            edit: text_edit::add_extension(&chain_extension, range, version),
868                            detail: Some("ink! extension `fn`".to_owned()),
869                            kind: CompletionKind::Fn,
870                        });
871                    }
872                }
873            }
874            ast::Item::Struct(struct_item) => {
875                // Bail if not an ink! event `struct`.
876                if !Event::can_cast(struct_item.syntax())
877                    && !EventV2::can_cast(struct_item.syntax())
878                {
879                    return;
880                }
881
882                // Adds ink! topic.
883                results.push(Completion {
884                    label: "#[ink(topic)]..field: ..".to_owned(),
885                    range,
886                    edit: text_edit::add_topic(&struct_item, range, None, None),
887                    detail: Some("ink! topic".to_owned()),
888                    kind: CompletionKind::Field,
889                });
890            }
891            _ => (),
892        }
893    } else {
894        // Computes root-level ink! entity completions.
895        if file.contracts().is_empty()
896            && is_line_affix_of(&["mod", "contract"])
897            && !is_next_node_error()
898            && (version.is_lte_v5() || !is_line_affix_of(&["trait"]))
899        {
900            // Adds ink! contract.
901            results.push(Completion {
902                label: "#[ink::contract]..mod contract {...}".to_owned(),
903                range,
904                edit: text_edit::add_contract(range, None, version),
905                detail: Some("ink! contract".to_owned()),
906                kind: CompletionKind::Mod,
907            });
908        }
909
910        // Adds ink! event 2.0.
911        add_event_v2!();
912
913        if version.is_gte_v6() {
914            // Adds ink! contract reference.
915            if is_line_affix_of(&["trait", "contract", "ref", "reference", "contract_ref"])
916                && !is_line_affix_of(&["mod"])
917            {
918                results.push(Completion {
919                    label: "#[ink::contract_ref]..pub trait Callee {...}".to_owned(),
920                    range,
921                    edit: text_edit::add_contract_ref(range, None),
922                    detail: Some("ink! contract reference".to_owned()),
923                    kind: CompletionKind::Trait,
924                });
925            }
926        }
927
928        // Adds ink! trait definition.
929        if is_line_affix_of(&["trait", "definition", "trait_definition"]) {
930            results.push(Completion {
931                label: "#[ink::trait_definition]..pub trait TraitDefinition {...}".to_owned(),
932                range,
933                edit: text_edit::add_trait_def(range, None),
934                detail: Some("ink! trait definition".to_owned()),
935                kind: CompletionKind::Trait,
936            });
937        }
938
939        if version.is_lte_v5() {
940            // Adds ink! chain extension.
941            if is_line_affix_of(&["trait", "chain", "extension", "chain_extension"]) {
942                results.push(Completion {
943                    label: format!(
944                        "#[ink::chain_extension{}]..pub trait ChainExtension {{...}}",
945                        if version.is_legacy() { "" } else { "(...)" }
946                    ),
947                    range,
948                    edit: text_edit::add_chain_extension(range, None, version),
949                    detail: Some("ink! chain extension".to_owned()),
950                    kind: CompletionKind::Trait,
951                });
952            }
953        }
954
955        // Adds ink! combine extensions definition.
956        if version.is_v5()
957            && is_line_affix_of(&["ink", "combine", "extensions", "combine_extensions"])
958        {
959            results.push(Completion {
960                label: "ink::combine_extensions! {..}".to_owned(),
961                range,
962                edit: text_edit::add_combine_extensions(range, None, Some(file)),
963                detail: Some("ink! combine extensions".to_owned()),
964                kind: CompletionKind::Struct,
965            });
966        }
967
968        // Adds ink! storage item.
969        if is_line_affix_of(&["struct", "enum", "storage", "item", "storage_item"]) {
970            results.push(Completion {
971                label: "#[ink::storage_item]..pub struct StorageItem {...}".to_owned(),
972                range,
973                edit: text_edit::add_storage_item(range, None),
974                detail: Some("ink! storage item".to_owned()),
975                kind: CompletionKind::Struct,
976            });
977        }
978
979        // Adds ink! environment.
980        if is_line_affix_of(&[
981            "struct",
982            "enum",
983            "environment",
984            "derive",
985            "scale",
986            "scale_derive",
987            "scale_info",
988            "encode",
989            "decode",
990            "type",
991            "info",
992            "type_info",
993        ]) {
994            results.push(Completion {
995                label: format!(
996                    "{}..pub enum MyEnvironment {{...}}",
997                    if version.is_legacy() {
998                        "#[derive(scale_info::TypeInfo)]"
999                    } else {
1000                        "#[ink::scale_derive(TypeInfo)]"
1001                    }
1002                ),
1003                range,
1004                edit: text_edit::add_environment(range, None, version),
1005                detail: Some("ink! environment".to_owned()),
1006                kind: CompletionKind::Enum,
1007            });
1008        }
1009    }
1010}
1011
1012#[cfg(test)]
1013mod tests {
1014    use super::*;
1015    use ink_analyzer_ir::MinorVersion;
1016    use test_utils::{parse_offset_at, remove_whitespace, PartialMatchStr};
1017
1018    macro_rules! list_results {
1019        ($list: expr, $start: literal, $end: literal) => {
1020            $list
1021                .iter()
1022                .map(|name| (*name, Some($start), Some($end)))
1023                .collect()
1024        };
1025        ($list: expr, -$exclude: literal, $start: literal, $end: literal) => {
1026            $list
1027                .iter()
1028                .filter(|name| **name != $exclude)
1029                .map(|name| (*name, Some($start), Some($end)))
1030                .collect()
1031        };
1032    }
1033
1034    #[test]
1035    fn macro_completions_works() {
1036        for (version, all_macros, adt_macros, adt_macros_sub_paths) in [
1037            (
1038                Version::Legacy,
1039                vec![
1040                    "::chain_extension",
1041                    "::contract",
1042                    "::storage_item",
1043                    "::test",
1044                    "::trait_definition",
1045                ],
1046                vec!["ink::storage_item"],
1047                vec!["::storage_item"],
1048            ),
1049            (
1050                Version::V5(MinorVersion::Base),
1051                vec![
1052                    "::chain_extension",
1053                    "::contract",
1054                    "::event",
1055                    "::scale_derive",
1056                    "::storage_item",
1057                    "::test",
1058                    "::trait_definition",
1059                ],
1060                vec!["ink::event", "ink::scale_derive", "ink::storage_item"],
1061                vec!["::event", "::scale_derive", "::storage_item"],
1062            ),
1063            (
1064                Version::V6,
1065                vec![
1066                    "::contract",
1067                    "::contract_ref",
1068                    "::error",
1069                    "::event",
1070                    "::scale_derive",
1071                    "::storage_item",
1072                    "::test",
1073                    "::trait_definition",
1074                ],
1075                vec![
1076                    "ink::event",
1077                    "ink::error",
1078                    "ink::scale_derive",
1079                    "ink::storage_item",
1080                ],
1081                vec!["::event", "::error", "::scale_derive", "::storage_item"],
1082            ),
1083        ] {
1084            for (code, pat, expected_results) in [
1085                // (code, [(pat, [(edit, pat_start, pat_end)])]) where:
1086                // code = source code,
1087                // pat = substring used to find the cursor offset (see `test_utils::parse_offset_at` doc),
1088                // edit = the text that will be inserted (represented without whitespace for simplicity),
1089                // pat_start = substring used to find the start of the edit offset (see `test_utils::parse_offset_at` doc),
1090                // pat_end = substring used to find the end of the edit offset (see `test_utils::parse_offset_at` doc).
1091
1092                // No AST item context.
1093                ("#[", None, vec![]),
1094                (
1095                    "#[i",
1096                    None,
1097                    vec![
1098                        ("ink", Some("<-i"), Some("i")),
1099                        ("ink_e2e", Some("<-i"), Some("i")),
1100                    ],
1101                ),
1102                ("#[ink_", None, vec![("ink_e2e", Some("<-i"), Some("ink_"))]),
1103                ("#[ink:", Some(":"), list_results!(all_macros, "<-:", ":")),
1104                (
1105                    "#[ink::",
1106                    Some("::"),
1107                    list_results!(all_macros, "<-::", "::"),
1108                ),
1109                (
1110                    "#[ink_e2e:",
1111                    Some(":"),
1112                    vec![("::test", Some("<-:"), Some(":"))],
1113                ),
1114                (
1115                    "#[ink_e2e::",
1116                    Some("::"),
1117                    vec![("::test", Some("<-::"), Some("::"))],
1118                ),
1119                // Module context.
1120                (
1121                    r#"
1122                    #[]
1123                    mod my_contract {}
1124                "#,
1125                    Some("["),
1126                    vec![("ink::contract", Some("["), Some("<-]"))],
1127                ),
1128                (
1129                    r#"
1130                    #[i]
1131                    mod my_contract {}
1132                "#,
1133                    Some("i"),
1134                    vec![("ink::contract", Some("<-i"), Some("i"))],
1135                ),
1136                (
1137                    r#"
1138                    #[ink]
1139                    mod my_contract {}
1140                "#,
1141                    Some("i"),
1142                    vec![("ink::contract", Some("<-ink"), Some("ink"))],
1143                ),
1144                (
1145                    r#"
1146                    #[ink::]
1147                    mod my_contract {}
1148                "#,
1149                    Some("::"),
1150                    vec![("::contract", Some("<-:"), Some("<-]"))],
1151                ),
1152                (
1153                    r#"
1154                    #[ink::co]
1155                    mod my_contract {}
1156                "#,
1157                    Some(":c"),
1158                    vec![("contract", Some("::"), Some("<-]"))],
1159                ),
1160                // Trait context.
1161                (
1162                    r#"
1163                    #[]
1164                    trait MyTrait {}
1165                "#,
1166                    Some("["),
1167                    vec![
1168                        if version.is_lte_v5() {
1169                            ("ink::chain_extension", Some("["), Some("<-]"))
1170                        } else {
1171                            ("ink::contract_ref", Some("["), Some("<-]"))
1172                        },
1173                        ("ink::trait_definition", Some("["), Some("<-]")),
1174                    ],
1175                ),
1176                (
1177                    r#"
1178                    #[i]
1179                    trait MyTrait {}
1180                "#,
1181                    Some("i"),
1182                    vec![
1183                        if version.is_lte_v5() {
1184                            ("ink::chain_extension", Some("<-i"), Some("i"))
1185                        } else {
1186                            ("ink::contract_ref", Some("<-i"), Some("i"))
1187                        },
1188                        ("ink::trait_definition", Some("<-i"), Some("i")),
1189                    ],
1190                ),
1191                (
1192                    r#"
1193                    #[ink]
1194                    trait MyTrait {}
1195                "#,
1196                    Some("i"),
1197                    vec![
1198                        if version.is_lte_v5() {
1199                            ("ink::chain_extension", Some("<-ink"), Some("ink"))
1200                        } else {
1201                            ("ink::contract_ref", Some("<-ink"), Some("ink"))
1202                        },
1203                        ("ink::trait_definition", Some("<-ink"), Some("ink")),
1204                    ],
1205                ),
1206                (
1207                    r#"
1208                    #[ink::]
1209                    trait MyTrait {}
1210                "#,
1211                    Some("::"),
1212                    vec![
1213                        if version.is_lte_v5() {
1214                            ("::chain_extension", Some("<-:"), Some("<-]"))
1215                        } else {
1216                            ("::contract_ref", Some("<-:"), Some("<-]"))
1217                        },
1218                        ("::trait_definition", Some("<-:"), Some("<-]")),
1219                    ],
1220                ),
1221                (
1222                    r#"
1223                    #[ink::c]
1224                    trait MyTrait {}
1225                "#,
1226                    Some(":c"),
1227                    if version.is_lte_v5() {
1228                        vec![("chain_extension", Some("::"), Some("<-]"))]
1229                    } else {
1230                        vec![("contract_ref", Some("::"), Some("<-]"))]
1231                    },
1232                ),
1233                (
1234                    r#"
1235                    #[ink::tr]
1236                    trait MyTrait {}
1237                "#,
1238                    Some(":t"),
1239                    vec![("trait_definition", Some("::"), Some("<-]"))],
1240                ),
1241                // ADT context.
1242                (
1243                    r#"
1244                    #[]
1245                    enum MyEnum {}
1246                "#,
1247                    Some("["),
1248                    list_results!(adt_macros, -"ink::event", "[", "<-]"),
1249                ),
1250                (
1251                    r#"
1252                    #[i]
1253                    struct MyStruct {}
1254                "#,
1255                    Some("i"),
1256                    list_results!(adt_macros, "<-i", "i"),
1257                ),
1258                (
1259                    r#"
1260                    #[ink]
1261                    union MyUnion {}
1262                "#,
1263                    Some("i"),
1264                    list_results!(adt_macros, -"ink::event", "<-ink", "ink"),
1265                ),
1266                (
1267                    r#"
1268                    #[ink::]
1269                    enum MyEnum {}
1270                "#,
1271                    Some("::"),
1272                    list_results!(adt_macros_sub_paths, -"::event", "<-:", "<-]"),
1273                ),
1274                (
1275                    r#"
1276                    #[ink::st]
1277                    struct MyStruct {}
1278                "#,
1279                    Some(":st"),
1280                    vec![("storage_item", Some("::"), Some("<-]"))],
1281                ),
1282                // Function context.
1283                (
1284                    r#"
1285                    #[]
1286                    fn my_fn() {}
1287                "#,
1288                    Some("["),
1289                    vec![
1290                        ("ink::test", Some("["), Some("<-]")),
1291                        ("ink_e2e::test", Some("["), Some("<-]")),
1292                    ],
1293                ),
1294                (
1295                    r#"
1296                    #[i]
1297                    fn my_fn() {}
1298                "#,
1299                    Some("i"),
1300                    vec![
1301                        ("ink::test", Some("<-i"), Some("i")),
1302                        ("ink_e2e::test", Some("<-i"), Some("i")),
1303                    ],
1304                ),
1305                (
1306                    r#"
1307                    #[ink]
1308                    fn my_fn() {}
1309                "#,
1310                    Some("i"),
1311                    vec![
1312                        ("ink::test", Some("<-ink"), Some("ink")),
1313                        ("ink_e2e::test", Some("<-ink"), Some("ink")),
1314                    ],
1315                ),
1316                (
1317                    r#"
1318                    #[ink::]
1319                    fn my_fn() {}
1320                "#,
1321                    Some("::"),
1322                    vec![("::test", Some("<-:"), Some("<-]"))],
1323                ),
1324                (
1325                    r#"
1326                    #[ink::te]
1327                    fn my_fn() {}
1328                "#,
1329                    Some(":t"),
1330                    vec![("test", Some("::"), Some("<-]"))],
1331                ),
1332                // Contract scope.
1333                (
1334                    r#"#
1335                    [ink::contract]
1336                    mod my_contract {
1337                        #[ink::
1338                    }
1339                "#,
1340                    Some("::->"),
1341                    list_results!(all_macros, -"::contract", "<-::->", "::->"),
1342                ),
1343            ] {
1344                let offset = TextSize::from(parse_offset_at(code, pat).unwrap() as u32);
1345
1346                let mut results = Vec::new();
1347                macro_completions(&mut results, &InkFile::parse(code), offset, version);
1348
1349                assert_eq!(
1350                    results
1351                        .iter()
1352                        .map(|completion| (completion.edit.text.trim(), completion.range))
1353                        .collect::<Vec<(&str, TextRange)>>(),
1354                    expected_results
1355                        .into_iter()
1356                        .map(|(edit, pat_start, pat_end)| (
1357                            edit,
1358                            TextRange::new(
1359                                TextSize::from(parse_offset_at(code, pat_start).unwrap() as u32),
1360                                TextSize::from(parse_offset_at(code, pat_end).unwrap() as u32)
1361                            )
1362                        ))
1363                        .collect::<Vec<(&str, TextRange)>>(),
1364                    "code: {code}, version: {:?}",
1365                    version
1366                );
1367            }
1368        }
1369    }
1370
1371    #[test]
1372    fn argument_completions_works() {
1373        let fixtures_gte_v5 = vec![
1374            (
1375                "#[ink::scale_derive(",
1376                Some("("),
1377                vec![
1378                    ("Encode", Some("("), Some("(")),
1379                    ("Decode", Some("("), Some("(")),
1380                    ("TypeInfo", Some("("), Some("(")),
1381                ],
1382            ),
1383            (
1384                "#[ink_e2e::test(backend",
1385                Some("backend"),
1386                vec![
1387                    ("(node)", Some("backend"), Some("backend")),
1388                    ("(runtime_only)", Some("backend"), Some("backend")),
1389                ],
1390            ),
1391            (
1392                "#[ink_e2e::test(backend(",
1393                Some("(->"),
1394                vec![
1395                    ("(node)", Some("<-(->"), Some("(->")),
1396                    ("(runtime_only)", Some("<-(->"), Some("(->")),
1397                ],
1398            ),
1399            (
1400                "#[ink_e2e::test(backend()",
1401                Some("(->"),
1402                vec![
1403                    ("(node)", Some("<-(->"), Some(")->")),
1404                    ("(runtime_only)", Some("<-(->"), Some(")->")),
1405                ],
1406            ),
1407            (
1408                "#[ink_e2e::test(backend(run",
1409                Some("run"),
1410                vec![("(runtime_only)", Some("<-(run"), Some("run"))],
1411            ),
1412            (
1413                "#[ink_e2e::test(backend(node",
1414                Some("node"),
1415                vec![(r#"(url="ws://127.0.0.1:9000")"#, Some("node"), Some("node"))],
1416            ),
1417            (
1418                "#[ink_e2e::test(backend(node(",
1419                Some("(->"),
1420                vec![(r#"(url="ws://127.0.0.1:9000")"#, Some("<-(->"), Some("(->"))],
1421            ),
1422        ];
1423        let fixtures_gte_v5_1 = vec![
1424            (
1425                "#[ink_e2e::test(backend(runtime_only",
1426                Some("runtime_only"),
1427                vec![(
1428                    "(sandbox=ink_e2e::DefaultSandbox)",
1429                    Some("runtime_only"),
1430                    Some("runtime_only"),
1431                )],
1432            ),
1433            (
1434                "#[ink_e2e::test(backend(runtime_only(",
1435                Some("(->"),
1436                vec![(
1437                    "(sandbox=ink_e2e::DefaultSandbox)",
1438                    Some("<-(->"),
1439                    Some("(->"),
1440                )],
1441            ),
1442        ];
1443        let fixtures_v5_only = vec![(
1444            "#[ink::event(",
1445            Some("("),
1446            vec![
1447                ("anonymous", Some("("), Some("(")),
1448                (r#"signature_topic="""#, Some("("), Some("(")),
1449            ],
1450        )];
1451        for (
1452            version,
1453            standalone_args,
1454            contract_child_args,
1455            adt_args,
1456            fn_args,
1457            event_args,
1458            extension_args,
1459            e2e_args,
1460            fixtures,
1461        ) in [
1462            (
1463                Version::Legacy,
1464                vec![
1465                    "anonymous",
1466                    "constructor",
1467                    "default",
1468                    "event",
1469                    "extension=1",
1470                    "handle_status=true",
1471                    "impl",
1472                    "message",
1473                    r#"namespace="my_namespace""#,
1474                    "payable",
1475                    "selector=1",
1476                    "storage",
1477                    "topic",
1478                ],
1479                vec![
1480                    "anonymous",
1481                    "constructor",
1482                    "default",
1483                    "event",
1484                    "impl",
1485                    "message",
1486                    r#"namespace="my_namespace""#,
1487                    "payable",
1488                    "selector=1",
1489                    "storage",
1490                ],
1491                vec!["anonymous", "event", "storage"],
1492                vec![
1493                    "constructor",
1494                    "default",
1495                    "extension=1",
1496                    "handle_status=true",
1497                    "message",
1498                    "payable",
1499                    "selector=1",
1500                ],
1501                vec!["anonymous"],
1502                vec!["extension=1", "handle_status=true"],
1503                vec![
1504                    r#"additional_contracts="""#,
1505                    "environment=ink::env::DefaultEnvironment",
1506                    r#"keep_attr="""#,
1507                ],
1508                vec![(
1509                    r#"
1510                    #[ink::chain_extension]
1511                    pub trait MyChainExtension {
1512                        #[ink(extension=1)]
1513                        fn extension_1(&self);
1514
1515                        #[ink(ext)]
1516                        fn extension_2(&self);
1517                    }
1518                    "#,
1519                    Some("#[ink(ext->"),
1520                    vec![("extension=2", Some("#[ink(->"), Some("#[ink(ext->"))],
1521                )],
1522            ),
1523            (
1524                Version::V5(MinorVersion::Base),
1525                vec![
1526                    "anonymous",
1527                    "constructor",
1528                    "default",
1529                    "event",
1530                    "function=1",
1531                    "handle_status=true",
1532                    "impl",
1533                    "message",
1534                    r#"namespace="my_namespace""#,
1535                    "payable",
1536                    "selector=1",
1537                    r#"signature_topic="""#,
1538                    "storage",
1539                    "topic",
1540                ],
1541                vec![
1542                    "anonymous",
1543                    "constructor",
1544                    "default",
1545                    "event",
1546                    "impl",
1547                    "message",
1548                    r#"namespace="my_namespace""#,
1549                    "payable",
1550                    "selector=1",
1551                    r#"signature_topic="""#,
1552                    "storage",
1553                ],
1554                vec!["anonymous", "event", r#"signature_topic="""#, "storage"],
1555                vec![
1556                    "constructor",
1557                    "default",
1558                    "function=1",
1559                    "handle_status=true",
1560                    "message",
1561                    "payable",
1562                    "selector=1",
1563                ],
1564                vec!["anonymous", r#"signature_topic="""#],
1565                vec!["function=1", "handle_status=true"],
1566                vec!["backend(node)", "environment=ink::env::DefaultEnvironment"],
1567                [
1568                    (
1569                        r#"
1570                        #[ink::chain_extension]
1571                        pub trait MyChainExtension {
1572                            #[ink(function=1)]
1573                            fn function_1(&self);
1574
1575                            #[ink(fun)]
1576                            fn function_2(&self);
1577                        }
1578                        "#,
1579                        Some("#[ink(fun->"),
1580                        vec![("function=2", Some("#[ink(->"), Some("#[ink(fun->"))],
1581                    ),
1582                    (
1583                        "#[ink_e2e::test(backend(runtime_only",
1584                        Some("runtime_only"),
1585                        vec![(
1586                            "(sandbox=ink_e2e::MinimalSandbox)",
1587                            Some("runtime_only"),
1588                            Some("runtime_only"),
1589                        )],
1590                    ),
1591                    (
1592                        "#[ink_e2e::test(backend(runtime_only(",
1593                        Some("(->"),
1594                        vec![(
1595                            "(sandbox=ink_e2e::MinimalSandbox)",
1596                            Some("<-(->"),
1597                            Some("(->"),
1598                        )],
1599                    ),
1600                ]
1601                .into_iter()
1602                .chain(fixtures_v5_only.clone())
1603                .chain(fixtures_gte_v5.clone())
1604                .collect(),
1605            ),
1606            (
1607                Version::V5(MinorVersion::Latest),
1608                vec![
1609                    "anonymous",
1610                    "constructor",
1611                    "default",
1612                    "event",
1613                    "function=1",
1614                    "handle_status=true",
1615                    "impl",
1616                    "message",
1617                    r#"namespace="my_namespace""#,
1618                    "payable",
1619                    "selector=1",
1620                    r#"signature_topic="""#,
1621                    "storage",
1622                    "topic",
1623                ],
1624                vec![
1625                    "anonymous",
1626                    "constructor",
1627                    "default",
1628                    "event",
1629                    "impl",
1630                    "message",
1631                    r#"namespace="my_namespace""#,
1632                    "payable",
1633                    "selector=1",
1634                    r#"signature_topic="""#,
1635                    "storage",
1636                ],
1637                vec!["anonymous", "event", r#"signature_topic="""#, "storage"],
1638                vec![
1639                    "constructor",
1640                    "default",
1641                    "function=1",
1642                    "handle_status=true",
1643                    "message",
1644                    "payable",
1645                    "selector=1",
1646                ],
1647                vec!["anonymous", r#"signature_topic="""#],
1648                vec!["function=1", "handle_status=true"],
1649                vec!["backend(node)", "environment=ink::env::DefaultEnvironment"],
1650                [(
1651                    r#"
1652                    #[ink::chain_extension]
1653                    pub trait MyChainExtension {
1654                        #[ink(function=1)]
1655                        fn function_1(&self);
1656
1657                        #[ink(fun)]
1658                        fn function_2(&self);
1659                    }
1660                    "#,
1661                    Some("#[ink(fun->"),
1662                    vec![("function=2", Some("#[ink(->"), Some("#[ink(fun->"))],
1663                )]
1664                .into_iter()
1665                .chain(fixtures_v5_only)
1666                .chain(fixtures_gte_v5.clone())
1667                .chain(fixtures_gte_v5_1.clone())
1668                .collect(),
1669            ),
1670            (
1671                Version::V6,
1672                vec![
1673                    r#"abi="sol""#,
1674                    "anonymous",
1675                    "constructor",
1676                    "default",
1677                    "event",
1678                    "impl",
1679                    "message",
1680                    r#"name="name""#,
1681                    r#"namespace="my_namespace""#,
1682                    "payable",
1683                    "selector=1",
1684                    r#"signature_topic="""#,
1685                    "storage",
1686                    "topic",
1687                ],
1688                vec![
1689                    "anonymous",
1690                    "constructor",
1691                    "default",
1692                    "event",
1693                    "impl",
1694                    "message",
1695                    r#"name="name""#,
1696                    r#"namespace="my_namespace""#,
1697                    "payable",
1698                    "selector=1",
1699                    r#"signature_topic="""#,
1700                    "storage",
1701                ],
1702                vec![
1703                    "anonymous",
1704                    "event",
1705                    r#"name="name""#,
1706                    r#"signature_topic="""#,
1707                    "storage",
1708                ],
1709                vec![
1710                    "constructor",
1711                    "default",
1712                    "message",
1713                    r#"name="name""#,
1714                    "payable",
1715                    "selector=1",
1716                ],
1717                vec!["anonymous", r#"name="name""#, r#"signature_topic="""#],
1718                vec![],
1719                vec!["backend(node)", "environment=ink::env::DefaultEnvironment"],
1720                [
1721                    (
1722                        "#[ink::event(",
1723                        Some("("),
1724                        vec![
1725                            ("anonymous", Some("("), Some("(")),
1726                            (r#"name="name""#, Some("("), Some("(")),
1727                            (r#"signature_topic="""#, Some("("), Some("(")),
1728                        ],
1729                    ),
1730                    (
1731                        "#[ink::contract_ref(",
1732                        Some("("),
1733                        vec![
1734                            (r#"abi="sol""#, Some("("), Some("(")),
1735                            ("env=ink::env::DefaultEnvironment", Some("("), Some("(")),
1736                        ],
1737                    ),
1738                ]
1739                .into_iter()
1740                .chain(fixtures_gte_v5)
1741                .chain(fixtures_gte_v5_1)
1742                .collect(),
1743            ),
1744        ] {
1745            for (code, pat, expected_results) in [
1746                // (code, pat, [(edit, pat_start, pat_end)]) where:
1747                // code = source code,
1748                // pat = substring used to find the cursor offset (see `test_utils::parse_offset_at` doc),
1749                // edit = the text that will be inserted (represented without whitespace for simplicity),
1750                // pat_start = substring used to find the start of the edit offset (see `test_utils::parse_offset_at` doc),
1751                // pat_end = substring used to find the end of the edit offset (see `test_utils::parse_offset_at` doc).
1752
1753                // Non ink! attribute.
1754                ("#[cfg(", None, vec![]),
1755                ("#[unknown(", None, vec![]),
1756                // No AST item context.
1757                ("#[ink(", None, list_results!(standalone_args, "(", "(")),
1758                (
1759                    "#[ink(e",
1760                    None,
1761                    if version.is_legacy() {
1762                        vec![
1763                            ("event", Some("<-e"), Some("e")),
1764                            ("extension=1", Some("<-e"), Some("e")),
1765                        ]
1766                    } else {
1767                        vec![("event", Some("<-e"), Some("e"))]
1768                    },
1769                ),
1770                (
1771                    "#[ink(con",
1772                    None,
1773                    vec![("constructor", Some("<-con"), Some("con"))],
1774                ),
1775                (
1776                    "#[ink(message, pa",
1777                    None,
1778                    vec![("payable", Some("<-pa"), Some("pa"))],
1779                ),
1780                (
1781                    r#"
1782                    mod my_module {
1783                        #[ink(
1784                    }
1785                    "#,
1786                    Some("("),
1787                    list_results!(standalone_args, "(", "("),
1788                ),
1789                (
1790                    r#"
1791                    mod my_module {
1792                        #[ink()
1793                    }
1794                    "#,
1795                    Some("("),
1796                    list_results!(standalone_args, "(", "("),
1797                ),
1798                (
1799                    r#"
1800                    mod my_module {
1801                        #[ink()]
1802                    }
1803                    "#,
1804                    Some("("),
1805                    list_results!(standalone_args, "(", "("),
1806                ),
1807                (
1808                    r#"
1809                    mod my_module {
1810                        #[ink(]
1811                    }
1812                    "#,
1813                    Some("("),
1814                    list_results!(standalone_args, "(", "("),
1815                ),
1816                // ink! attribute argument context with no AST item.
1817                ("#[ink(event,", None, list_results!(event_args, ",", ",")),
1818                (
1819                    "#[ink(constructor,",
1820                    None,
1821                    if version.is_lte_v5() {
1822                        vec![
1823                            ("default", Some(","), Some(",")),
1824                            ("payable", Some(","), Some(",")),
1825                            ("selector=1", Some(","), Some(",")),
1826                        ]
1827                    } else {
1828                        vec![
1829                            ("default", Some(","), Some(",")),
1830                            (r#"name="name""#, Some(","), Some(",")),
1831                            ("payable", Some(","), Some(",")),
1832                            ("selector=1", Some(","), Some(",")),
1833                        ]
1834                    },
1835                ),
1836                (
1837                    "#[ink(message,",
1838                    None,
1839                    if version.is_lte_v5() {
1840                        vec![
1841                            ("default", Some(","), Some(",")),
1842                            ("payable", Some(","), Some(",")),
1843                            ("selector=1", Some(","), Some(",")),
1844                        ]
1845                    } else {
1846                        vec![
1847                            ("default", Some(","), Some(",")),
1848                            (r#"name="name""#, Some(","), Some(",")),
1849                            ("payable", Some(","), Some(",")),
1850                            ("selector=1", Some(","), Some(",")),
1851                        ]
1852                    },
1853                ),
1854                (
1855                    "#[ink(extension = 1,",
1856                    None,
1857                    if version.is_legacy() {
1858                        vec![("handle_status=true", Some(","), Some(","))]
1859                    } else {
1860                        vec![]
1861                    },
1862                ),
1863                (
1864                    "#[ink(function = 1,",
1865                    None,
1866                    if version.is_v5() {
1867                        vec![("handle_status=true", Some(","), Some(","))]
1868                    } else {
1869                        vec![]
1870                    },
1871                ),
1872                (
1873                    "#[ink(impl,",
1874                    None,
1875                    vec![(r#"namespace="my_namespace""#, Some(","), Some(","))],
1876                ),
1877                (
1878                    "#[ink(impl,=",
1879                    Some(","),
1880                    vec![("namespace", Some(","), Some(","))],
1881                ),
1882                (
1883                    "#[ink(impl, =",
1884                    Some(","),
1885                    vec![("namespace", Some(","), Some(","))],
1886                ),
1887                // ink! attribute macro context with no AST item.
1888                (
1889                    "#[ink::contract(",
1890                    None,
1891                    vec![
1892                        ("env=ink::env::DefaultEnvironment", Some("("), Some("(")),
1893                        (r#"keep_attr="""#, Some("("), Some("(")),
1894                    ],
1895                ),
1896                (
1897                    "#[ink::contract(env=my::env::Types,",
1898                    None,
1899                    vec![(r#"keep_attr="""#, Some(","), Some(","))],
1900                ),
1901                (
1902                    r#"#[ink::contract(env=my::env::Types, keep_attr="foo,bar","#,
1903                    None,
1904                    vec![],
1905                ),
1906                (
1907                    "#[ink::storage_item(",
1908                    None,
1909                    if version.is_lte_v5() {
1910                        vec![("derive=true", Some("("), Some("("))]
1911                    } else {
1912                        vec![
1913                            ("packed", Some("("), Some("(")),
1914                            ("derive=true", Some("("), Some("(")),
1915                        ]
1916                    },
1917                ),
1918                (
1919                    "#[ink::trait_definition(",
1920                    None,
1921                    vec![
1922                        (r#"keep_attr="""#, Some("("), Some("(")),
1923                        (r#"namespace="my_namespace""#, Some("("), Some("(")),
1924                    ],
1925                ),
1926                (
1927                    r#"#[ink::trait_definition(namespace="my_namespace","#,
1928                    None,
1929                    vec![(r#"keep_attr="""#, Some(","), Some(","))],
1930                ),
1931                ("#[ink_e2e::test(", None, list_results!(e2e_args, "(", "(")),
1932                // Struct context.
1933                (
1934                    r#"
1935                    #[ink(
1936                        struct MyStruct {}
1937                    "#,
1938                    Some("("),
1939                    list_results!(adt_args, "(", "("),
1940                ),
1941                (
1942                    r#"
1943                        #[ink()]
1944                        struct MyStruct {}
1945                    "#,
1946                    Some("("),
1947                    list_results!(adt_args, "(", "("),
1948                ),
1949                (
1950                    r#"
1951                    #[ink(]
1952                    struct MyStruct {}
1953                    "#,
1954                    Some("("),
1955                    list_results!(adt_args, "(", "("),
1956                ),
1957                // Struct field context.
1958                (
1959                    r#"
1960                    struct MyStruct {
1961                        #[ink(
1962                        value: bool,
1963                    }
1964                    "#,
1965                    Some("("),
1966                    vec![("topic", Some("("), Some("("))],
1967                ),
1968                (
1969                    r#"
1970                    struct MyStruct {
1971                        #[ink()]
1972                        value: bool,
1973                    }
1974                    "#,
1975                    Some("("),
1976                    vec![("topic", Some("("), Some("("))],
1977                ),
1978                (
1979                    r#"
1980                    struct MyStruct {
1981                        #[ink(]
1982                        value: bool,
1983                    }
1984                    "#,
1985                    Some("("),
1986                    vec![("topic", Some("("), Some("("))],
1987                ),
1988                // Fn context.
1989                (
1990                    r#"
1991                    #[ink(
1992                    pub fn my_fn() {}
1993                    "#,
1994                    Some("("),
1995                    list_results!(fn_args, "(", "("),
1996                ),
1997                (
1998                    r#"
1999                    #[ink(constructor)]
2000                    #[ink(
2001                    pub fn my_fn() {}
2002                    "#,
2003                    Some("ink(->"),
2004                    if version.is_lte_v5() {
2005                        vec![
2006                            ("default", Some("ink(->"), Some("ink(->")),
2007                            ("payable", Some("ink(->"), Some("ink(->")),
2008                            ("selector=1", Some("ink(->"), Some("ink(->")),
2009                        ]
2010                    } else {
2011                        vec![
2012                            ("default", Some("ink(->"), Some("ink(->")),
2013                            (r#"name="name""#, Some("ink(->"), Some("ink(->")),
2014                            ("payable", Some("ink(->"), Some("ink(->")),
2015                            ("selector=1", Some("ink(->"), Some("ink(->")),
2016                        ]
2017                    },
2018                ),
2019                // Impl context.
2020                (
2021                    r#"
2022                    #[ink(
2023                    impl MyImpl {}
2024                    "#,
2025                    Some("("),
2026                    vec![
2027                        ("impl", Some("("), Some("(")),
2028                        (r#"namespace="my_namespace""#, Some("("), Some("(")),
2029                    ],
2030                ),
2031                // Contract scope.
2032                (
2033                    r#"
2034                    #[ink::contract]
2035                    mod my_contract {
2036                        #[ink(
2037                    }
2038                    "#,
2039                    Some("("),
2040                    list_results!(contract_child_args, "(", "("),
2041                ),
2042                (
2043                    r#"
2044                    #[ink::contract]
2045                    mod my_contract {
2046                        #[ink(
2047                        pub struct MyContract {}
2048                    }
2049                    "#,
2050                    Some("("),
2051                    list_results!(adt_args, "(", "("),
2052                ),
2053                (
2054                    r#"
2055                    #[ink::contract]
2056                    mod my_contract {
2057                        #[ink(event,
2058                        pub struct MyContract {}
2059                    }
2060                    "#,
2061                    Some("("),
2062                    list_results!(event_args, "(", "("),
2063                ),
2064                (
2065                    r#"
2066                    #[ink::contract]
2067                    mod my_contract {
2068                        #[ink(
2069                        impl MyContract {}
2070                    }
2071                    "#,
2072                    Some("("),
2073                    vec![
2074                        ("impl", Some("("), Some("(")),
2075                        (r#"namespace="my_namespace""#, Some("("), Some("(")),
2076                    ],
2077                ),
2078                (
2079                    r#"
2080                    #[ink::contract]
2081                    mod my_contract {
2082                        impl MyContract {
2083                            #[ink(
2084                            pub fn my_fn() {}
2085                        }
2086                    }
2087                    "#,
2088                    Some("("),
2089                    if version.is_lte_v5() {
2090                        vec![
2091                            ("constructor", Some("("), Some("(")),
2092                            ("default", Some("("), Some("(")),
2093                            ("message", Some("("), Some("(")),
2094                            ("payable", Some("("), Some("(")),
2095                            ("selector=1", Some("("), Some("(")),
2096                        ]
2097                    } else {
2098                        vec![
2099                            ("constructor", Some("("), Some("(")),
2100                            ("default", Some("("), Some("(")),
2101                            ("message", Some("("), Some("(")),
2102                            (r#"name="name""#, Some("("), Some("(")),
2103                            ("payable", Some("("), Some("(")),
2104                            ("selector=1", Some("("), Some("(")),
2105                        ]
2106                    },
2107                ),
2108                // Chain extension scope.
2109                (
2110                    r#"
2111                    #[ink::chain_extension]
2112                    pub trait MyChainExtension {
2113                        #[ink(
2114                    }
2115                    "#,
2116                    Some("("),
2117                    list_results!(extension_args, "(", "("),
2118                ),
2119                (
2120                    r#"
2121                    #[ink::chain_extension]
2122                    pub trait MyChainExtension {
2123                        #[ink(
2124                        fn my_extension();
2125                    }
2126                    "#,
2127                    Some("("),
2128                    list_results!(extension_args, "(", "("),
2129                ),
2130                // Trait definition scope.
2131                (
2132                    r#"
2133                    #[ink::trait_definition]
2134                    pub trait MyTrait {
2135                        #[ink(
2136                    }
2137                    "#,
2138                    Some("("),
2139                    if version.is_lte_v5() {
2140                        vec![
2141                            ("default", Some("("), Some("(")),
2142                            ("message", Some("("), Some("(")),
2143                            ("payable", Some("("), Some("(")),
2144                            ("selector=1", Some("("), Some("(")),
2145                        ]
2146                    } else {
2147                        vec![
2148                            ("default", Some("("), Some("(")),
2149                            ("message", Some("("), Some("(")),
2150                            (r#"name="name""#, Some("("), Some("(")),
2151                            ("payable", Some("("), Some("(")),
2152                            ("selector=1", Some("("), Some("(")),
2153                        ]
2154                    },
2155                ),
2156                (
2157                    r#"
2158                    #[ink::trait_definition]
2159                    pub trait MyTrait {
2160                        #[ink(
2161                        fn my_message(&self);
2162                    }
2163                    "#,
2164                    Some("("),
2165                    if version.is_lte_v5() {
2166                        vec![
2167                            ("default", Some("("), Some("(")),
2168                            ("message", Some("("), Some("(")),
2169                            ("payable", Some("("), Some("(")),
2170                            ("selector=1", Some("("), Some("(")),
2171                        ]
2172                    } else {
2173                        vec![
2174                            ("default", Some("("), Some("(")),
2175                            ("message", Some("("), Some("(")),
2176                            (r#"name="name""#, Some("("), Some("(")),
2177                            ("payable", Some("("), Some("(")),
2178                            ("selector=1", Some("("), Some("(")),
2179                        ]
2180                    },
2181                ),
2182                // Unique ids.
2183                (
2184                    r#"
2185                    #[ink::contract]
2186                    mod my_contract {
2187                        impl MyContract {
2188                            #[ink(constructor, selector=1)]
2189                            pub fn constructor_1(&self) {}
2190
2191                            #[ink(constructor, sel)]
2192                            pub fn constructor_2(&self) {}
2193                        }
2194                    }
2195                    "#,
2196                    Some("#[ink(constructor, sel->"),
2197                    vec![(
2198                        "selector=2",
2199                        Some("#[ink(constructor, ->"),
2200                        Some("#[ink(constructor, sel->"),
2201                    )],
2202                ),
2203                (
2204                    r#"
2205                    #[ink::contract]
2206                    mod my_contract {
2207                        impl MyContract {
2208                            #[ink(message, selector=1)]
2209                            pub fn message_1(&self) {}
2210
2211                            #[ink(message, sel)]
2212                            pub fn message_2(&self) {}
2213                        }
2214                    }
2215                    "#,
2216                    Some("#[ink(message, sel->"),
2217                    vec![(
2218                        "selector=2",
2219                        Some("#[ink(message, ->"),
2220                        Some("#[ink(message, sel->"),
2221                    )],
2222                ),
2223                (
2224                    r#"
2225                    #[ink::trait_definition]
2226                    pub trait MyTrait {
2227                        #[ink(message, selector=1)]
2228                        fn message_1(&self);
2229
2230                        #[ink(message, sel)]
2231                        fn message_2(&self);
2232                    }
2233                    "#,
2234                    Some("#[ink(message, sel->"),
2235                    vec![(
2236                        "selector=2",
2237                        Some("#[ink(message, ->"),
2238                        Some("#[ink(message, sel->"),
2239                    )],
2240                ),
2241            ]
2242            .into_iter()
2243            .chain(fixtures)
2244            {
2245                let offset = TextSize::from(parse_offset_at(code, pat).unwrap() as u32);
2246
2247                let mut results = Vec::new();
2248                argument_completions(&mut results, &InkFile::parse(code), offset, version);
2249
2250                assert_eq!(
2251                    results
2252                        .into_iter()
2253                        .map(|completion| (
2254                            remove_whitespace(completion.edit.text),
2255                            completion.range
2256                        ))
2257                        .collect::<Vec<(String, TextRange)>>(),
2258                    expected_results
2259                        .into_iter()
2260                        .map(|(edit, pat_start, pat_end)| (
2261                            remove_whitespace(edit.to_owned()),
2262                            TextRange::new(
2263                                TextSize::from(parse_offset_at(code, pat_start).unwrap() as u32),
2264                                TextSize::from(parse_offset_at(code, pat_end).unwrap() as u32)
2265                            )
2266                        ))
2267                        .collect::<Vec<(String, TextRange)>>(),
2268                    "code: {code}, version: {:?}",
2269                    version
2270                );
2271            }
2272        }
2273    }
2274
2275    #[test]
2276    fn entity_completions_works() {
2277        for version in [
2278            Version::Legacy,
2279            Version::V5(MinorVersion::Base),
2280            Version::V6,
2281        ] {
2282            for (code, pat, expected_results) in [
2283                // (code, [(pat, [(edit, pat_start, pat_end)])]) where:
2284                // code = source code,
2285                // pat = substring used to find the cursor offset (see `test_utils::parse_offset_at` doc),
2286                // edit = the text that will be inserted (represented without whitespace for simplicity),
2287                // pat_start = substring used to find the start of the edit offset (see `test_utils::parse_offset_at` doc),
2288                // pat_end = substring used to find the end of the edit offset (see `test_utils::parse_offset_at` doc).
2289
2290                // Root entities.
2291                ("", None, vec![]),
2292                // `mod`.
2293                (
2294                    "mod",
2295                    Some("mo"),
2296                    vec![("#[ink::contract]", Some("<-mod"), Some("mod"))],
2297                ),
2298                (
2299                    "mo",
2300                    Some("mo"),
2301                    vec![("#[ink::contract]", Some("<-mo"), Some("mo"))],
2302                ),
2303                (
2304                    "contract",
2305                    Some("con"),
2306                    if version.is_lte_v5() {
2307                        vec![("#[ink::contract]", Some("<-contract"), Some("contract"))]
2308                    } else {
2309                        vec![
2310                            ("#[ink::contract]", Some("<-contract"), Some("contract")),
2311                            ("#[ink::contract_ref]", Some("<-contract"), Some("contract")),
2312                        ]
2313                    },
2314                ),
2315                (
2316                    "pub mod",
2317                    Some("mod"),
2318                    vec![("#[ink::contract]", Some("<-pub"), Some("mod"))],
2319                ),
2320                (
2321                    "pub mod",
2322                    Some("pub"),
2323                    vec![("#[ink::contract]", Some("<-pub"), Some("mod"))],
2324                ),
2325                (
2326                    "pub mod",
2327                    Some("pub "),
2328                    vec![("#[ink::contract]", Some("<-pub"), Some("mod"))],
2329                ),
2330                (
2331                    "pub mod contract",
2332                    Some("contract"),
2333                    vec![("#[ink::contract]", Some("<-pub"), Some("contract"))],
2334                ),
2335                (
2336                    "pub mod contract {",
2337                    Some("{"),
2338                    vec![("#[ink::contract]", Some("<-pub"), Some("{"))],
2339                ),
2340                (
2341                    "pub mod contract {}",
2342                    Some("{"),
2343                    vec![("#[ink::contract]", Some("<-pub"), Some("}"))],
2344                ),
2345                (
2346                    r"
2347//! ink! example.
2348
2349pub mod contract",
2350                    Some("mod"),
2351                    vec![("#[ink::contract]", Some("<-pub"), Some("contract"))],
2352                ),
2353                (
2354                    r"
2355#[ink::contract]
2356pub mod contract {}
2357
2358pub mod",
2359                    Some("mod->"),
2360                    vec![],
2361                ),
2362                (
2363                    r"
2364#[ink::contract]
2365pub mod contract1 {}
2366
2367pub mod contract2",
2368                    Some("contract2"),
2369                    vec![],
2370                ),
2371                (
2372                    r"
2373pub mod
2374
2375#[ink::contract]
2376pub mod contract {}",
2377                    Some("mod"),
2378                    vec![],
2379                ),
2380                (
2381                    r"
2382pub mod contract1
2383
2384#[ink::contract]
2385pub mod contract2 {}",
2386                    Some("contract1"),
2387                    vec![],
2388                ),
2389                (
2390                    r"
2391#[ink::contract]
2392pub mod contract1 {
2393    pub mod contract2
2394}",
2395                    Some("contract2"),
2396                    vec![],
2397                ),
2398                // `trait`.
2399                (
2400                    "trait",
2401                    Some("trait"),
2402                    if version.is_lte_v5() {
2403                        vec![
2404                            ("#[ink::trait_definition]", Some("<-trait"), Some("trait")),
2405                            ("#[ink::chain_extension", Some("<-trait"), Some("trait")),
2406                        ]
2407                    } else {
2408                        vec![
2409                            ("#[ink::contract_ref]", Some("<-trait"), Some("trait")),
2410                            ("#[ink::trait_definition]", Some("<-trait"), Some("trait")),
2411                        ]
2412                    },
2413                ),
2414                (
2415                    "contract_ref",
2416                    Some("contract_ref"),
2417                    if version.is_lte_v5() {
2418                        vec![]
2419                    } else {
2420                        vec![(
2421                            "#[ink::contract_ref]",
2422                            Some("<-contract_ref"),
2423                            Some("contract_ref"),
2424                        )]
2425                    },
2426                ),
2427                (
2428                    "trait_def",
2429                    Some("trait_def"),
2430                    vec![(
2431                        "#[ink::trait_definition]",
2432                        Some("<-trait_def"),
2433                        Some("trait_def"),
2434                    )],
2435                ),
2436                (
2437                    "chain",
2438                    Some("chain"),
2439                    if version.is_lte_v5() {
2440                        vec![("#[ink::chain_extension", Some("<-chain"), Some("chain"))]
2441                    } else {
2442                        vec![]
2443                    },
2444                ),
2445                (
2446                    "extension",
2447                    Some("extension"),
2448                    if version.is_legacy() {
2449                        vec![(
2450                            "#[ink::chain_extension",
2451                            Some("<-extension"),
2452                            Some("extension"),
2453                        )]
2454                    } else if version.is_v5() {
2455                        vec![
2456                            (
2457                                "#[ink::chain_extension",
2458                                Some("<-extension"),
2459                                Some("extension"),
2460                            ),
2461                            (
2462                                "ink::combine_extensions!",
2463                                Some("<-extension"),
2464                                Some("extension"),
2465                            ),
2466                        ]
2467                    } else {
2468                        vec![]
2469                    },
2470                ),
2471                (
2472                    r"
2473#[ink::trait_definition]
2474pub trait MyTrait {
2475
2476}
2477
2478trait",
2479                    Some("trait->"),
2480                    if version.is_lte_v5() {
2481                        vec![
2482                            (
2483                                "#[ink::trait_definition]",
2484                                Some("<-trait->"),
2485                                Some("trait->"),
2486                            ),
2487                            ("#[ink::chain_extension", Some("<-trait->"), Some("trait->")),
2488                        ]
2489                    } else {
2490                        vec![
2491                            ("#[ink::contract_ref]", Some("<-trait->"), Some("trait->")),
2492                            (
2493                                "#[ink::trait_definition]",
2494                                Some("<-trait->"),
2495                                Some("trait->"),
2496                            ),
2497                        ]
2498                    },
2499                ),
2500                (
2501                    r"
2502#[ink::trait_definition]
2503pub trait MyTrait1 {
2504
2505}
2506
2507trait MyTrait2",
2508                    Some("MyTrait2"),
2509                    if version.is_lte_v5() {
2510                        vec![
2511                            (
2512                                "#[ink::trait_definition]",
2513                                Some("<-trait->"),
2514                                Some("MyTrait2"),
2515                            ),
2516                            (
2517                                "#[ink::chain_extension",
2518                                Some("<-trait->"),
2519                                Some("MyTrait2"),
2520                            ),
2521                        ]
2522                    } else {
2523                        vec![
2524                            ("#[ink::contract_ref]", Some("<-trait->"), Some("MyTrait2")),
2525                            (
2526                                "#[ink::trait_definition]",
2527                                Some("<-trait->"),
2528                                Some("MyTrait2"),
2529                            ),
2530                        ]
2531                    },
2532                ),
2533                (
2534                    r"
2535trait
2536
2537#[ink::trait_definition]
2538pub trait MyTrait {
2539
2540}",
2541                    Some("trait"),
2542                    if version.is_lte_v5() {
2543                        vec![
2544                            ("#[ink::trait_definition]", Some("<-trait"), Some("trait")),
2545                            ("#[ink::chain_extension", Some("<-trait"), Some("trait")),
2546                        ]
2547                    } else {
2548                        vec![
2549                            ("#[ink::contract_ref]", Some("<-trait"), Some("trait")),
2550                            ("#[ink::trait_definition]", Some("<-trait"), Some("trait")),
2551                        ]
2552                    },
2553                ),
2554                (
2555                    r"
2556trait MyTrait1
2557
2558#[ink::trait_definition]
2559pub trait MyTrait2 {
2560
2561}",
2562                    Some("MyTrait1"),
2563                    if version.is_lte_v5() {
2564                        vec![
2565                            (
2566                                "#[ink::trait_definition]",
2567                                Some("<-trait"),
2568                                Some("MyTrait1"),
2569                            ),
2570                            ("#[ink::chain_extension", Some("<-trait"), Some("MyTrait1")),
2571                        ]
2572                    } else {
2573                        vec![
2574                            ("#[ink::contract_ref]", Some("<-trait"), Some("MyTrait1")),
2575                            (
2576                                "#[ink::trait_definition]",
2577                                Some("<-trait"),
2578                                Some("MyTrait1"),
2579                            ),
2580                        ]
2581                    },
2582                ),
2583                (
2584                    r"
2585#[ink::chain_extension]
2586pub trait MyChainExtension {
2587
2588}
2589
2590trait",
2591                    Some("trait->"),
2592                    if version.is_lte_v5() {
2593                        vec![
2594                            (
2595                                "#[ink::trait_definition]",
2596                                Some("<-trait->"),
2597                                Some("trait->"),
2598                            ),
2599                            ("#[ink::chain_extension", Some("<-trait->"), Some("trait->")),
2600                        ]
2601                    } else {
2602                        vec![
2603                            ("#[ink::contract_ref]", Some("<-trait->"), Some("trait->")),
2604                            (
2605                                "#[ink::trait_definition]",
2606                                Some("<-trait->"),
2607                                Some("trait->"),
2608                            ),
2609                        ]
2610                    },
2611                ),
2612                // `struct`.
2613                (
2614                    "struct",
2615                    Some("struct"),
2616                    if version.is_legacy() {
2617                        vec![
2618                            ("#[ink::storage_item]", Some("<-struct"), Some("struct")),
2619                            ("pub enum MyEnvironment {", Some("<-struct"), Some("struct")),
2620                        ]
2621                    } else {
2622                        vec![
2623                            ("#[ink::event]", Some("<-struct"), Some("struct")),
2624                            ("#[ink::storage_item]", Some("<-struct"), Some("struct")),
2625                            ("pub enum MyEnvironment {", Some("<-struct"), Some("struct")),
2626                        ]
2627                    },
2628                ),
2629                (
2630                    "event",
2631                    Some("event"),
2632                    if version.is_legacy() {
2633                        vec![]
2634                    } else {
2635                        vec![("#[ink::event]", Some("<-event"), Some("event"))]
2636                    },
2637                ),
2638                (
2639                    "storage",
2640                    Some("storage"),
2641                    vec![("#[ink::storage_item]", Some("<-storage"), Some("storage"))],
2642                ),
2643                (
2644                    "environ",
2645                    Some("environ"),
2646                    vec![("pub enum MyEnvironment", Some("<-environ"), Some("environ"))],
2647                ),
2648                // `enum`.
2649                (
2650                    "enum",
2651                    Some("enum"),
2652                    vec![
2653                        ("#[ink::storage_item]", Some("<-enum"), Some("enum")),
2654                        ("pub enum MyEnvironment {", Some("<-enum"), Some("enum")),
2655                    ],
2656                ),
2657                // macro rules.
2658                (
2659                    "combine_extensions!",
2660                    Some("combine_extensions!"),
2661                    if version.is_v5() {
2662                        vec![(
2663                            "ink::combine_extensions!",
2664                            Some("<-combine_extensions!"),
2665                            Some("combine_extensions!"),
2666                        )]
2667                    } else {
2668                        vec![]
2669                    },
2670                ),
2671                (
2672                    "combine",
2673                    Some("combine"),
2674                    if version.is_v5() {
2675                        vec![(
2676                            "ink::combine_extensions!",
2677                            Some("<-combine"),
2678                            Some("combine"),
2679                        )]
2680                    } else {
2681                        vec![]
2682                    },
2683                ),
2684                (
2685                    "extensions",
2686                    Some("extensions"),
2687                    if version.is_v5() {
2688                        vec![(
2689                            "ink::combine_extensions!",
2690                            Some("<-extensions"),
2691                            Some("extensions"),
2692                        )]
2693                    } else {
2694                        vec![]
2695                    },
2696                ),
2697                (
2698                    "ink",
2699                    Some("ink"),
2700                    if version.is_v5() {
2701                        vec![("ink::combine_extensions!", Some("<-ink"), Some("ink"))]
2702                    } else {
2703                        vec![]
2704                    },
2705                ),
2706                // `mod` entities.
2707                (
2708                    r"
2709mod contract {
2710
2711}",
2712                    Some("contract {\n"),
2713                    vec![],
2714                ),
2715                (
2716                    r"
2717#[ink::contract]
2718mod contract {
2719
2720}",
2721                    Some("contract {\n"),
2722                    if version.is_legacy() {
2723                        vec![
2724                            (
2725                                "#[ink(storage)]",
2726                                Some("contract {\n"),
2727                                Some("contract {\n"),
2728                            ),
2729                            ("#[ink(event)]", Some("contract {\n"), Some("contract {\n")),
2730                        ]
2731                    } else {
2732                        vec![
2733                            (
2734                                "#[ink(storage)]",
2735                                Some("contract {\n"),
2736                                Some("contract {\n"),
2737                            ),
2738                            ("#[ink::event]", Some("contract {\n"), Some("contract {\n")),
2739                            ("#[ink(event)]", Some("contract {\n"), Some("contract {\n")),
2740                        ]
2741                    },
2742                ),
2743                (
2744                    r"
2745#[ink::contract]
2746mod contract {
2747    struct
2748}",
2749                    Some("struct"),
2750                    if version.is_legacy() {
2751                        vec![
2752                            ("#[ink(storage)]", Some("<-    struct"), Some("struct")),
2753                            ("#[ink(event)]", Some("<-    struct"), Some("struct")),
2754                        ]
2755                    } else {
2756                        vec![
2757                            ("#[ink(storage)]", Some("<-    struct"), Some("struct")),
2758                            ("#[ink::event]", Some("<-    struct"), Some("struct")),
2759                            ("#[ink(event)]", Some("<-    struct"), Some("struct")),
2760                        ]
2761                    },
2762                ),
2763                (
2764                    r"
2765#[ink::contract]
2766mod contract {
2767    storage
2768}",
2769                    Some("storage"),
2770                    vec![("#[ink(storage)]", Some("<-    storage"), Some("storage"))],
2771                ),
2772                (
2773                    r"
2774#[ink::contract]
2775mod contract {
2776    event
2777}",
2778                    Some("event"),
2779                    if version.is_legacy() {
2780                        vec![("#[ink(event)]", Some("<-    event"), Some("event"))]
2781                    } else {
2782                        vec![
2783                            ("#[ink::event]", Some("<-    event"), Some("event")),
2784                            ("#[ink(event)]", Some("<-    event"), Some("event")),
2785                        ]
2786                    },
2787                ),
2788                (
2789                    r"
2790#[ink::contract]
2791mod contract {
2792    #[ink(storage)]
2793    pub struct Contract;
2794
2795    #[ink(event)]
2796    pub struct Event {}
2797
2798    struct
2799}",
2800                    Some("struct->"),
2801                    if version.is_legacy() {
2802                        vec![("#[ink(event)]", Some("<-    struct->"), Some("struct->"))]
2803                    } else {
2804                        vec![
2805                            ("#[ink::event]", Some("<-    struct->"), Some("struct->")),
2806                            ("#[ink(event)]", Some("<-    struct->"), Some("struct->")),
2807                        ]
2808                    },
2809                ),
2810                (
2811                    r"
2812#[ink::contract]
2813mod contract {
2814    struct
2815
2816    #[ink(event)]
2817    pub struct Event {}
2818}",
2819                    Some("struct"),
2820                    // No `storage` suggestion because `event` item is parsed as an error node.
2821                    if version.is_legacy() {
2822                        vec![("#[ink(event)]", Some("<-    struct"), Some("struct"))]
2823                    } else {
2824                        vec![
2825                            ("#[ink::event]", Some("<-    struct"), Some("struct")),
2826                            ("#[ink(event)]", Some("<-    struct"), Some("struct")),
2827                        ]
2828                    },
2829                ),
2830                (
2831                    r"
2832#[ink::contract]
2833mod contract {
2834    struct Contract
2835
2836    #[ink(event)]
2837    pub struct Event {}
2838}",
2839                    Some("struct"),
2840                    if version.is_legacy() {
2841                        vec![
2842                            ("#[ink(storage)]", Some("<-    struct"), Some("Contract")),
2843                            ("#[ink(event)]", Some("<-    struct"), Some("Contract")),
2844                        ]
2845                    } else {
2846                        vec![
2847                            ("#[ink(storage)]", Some("<-    struct"), Some("Contract")),
2848                            ("#[ink::event]", Some("<-    struct"), Some("Contract")),
2849                            ("#[ink(event)]", Some("<-    struct"), Some("Contract")),
2850                        ]
2851                    },
2852                ),
2853                (
2854                    r"
2855#[ink::contract]
2856mod contract {
2857    #[ink(storage)]
2858    pub struct Contract;
2859
2860    storage
2861}",
2862                    Some("storage"),
2863                    vec![],
2864                ),
2865                (
2866                    r"
2867#[ink::contract]
2868mod contract {
2869    #[ink(event)]
2870    pub struct Event {}
2871
2872    event
2873}",
2874                    Some("event->"),
2875                    if version.is_legacy() {
2876                        vec![("#[ink(event)]", Some("<-    event->"), Some("event->"))]
2877                    } else {
2878                        vec![
2879                            ("#[ink::event]", Some("<-    event->"), Some("event->")),
2880                            ("#[ink(event)]", Some("<-    event->"), Some("event->")),
2881                        ]
2882                    },
2883                ),
2884                (
2885                    r"
2886#[cfg(test)]
2887mod tests {
2888
2889}",
2890                    Some("tests {\n"),
2891                    vec![("#[ink::test]", Some("tests {\n"), Some("tests {\n"))],
2892                ),
2893                (
2894                    r"
2895#[cfg(test)]
2896mod tests {
2897    fn
2898}",
2899                    Some("fn"),
2900                    vec![("#[ink::test]", Some("<-    fn"), Some("fn"))],
2901                ),
2902                (
2903                    r#"
2904#[cfg(all(test, feature = "e2e-tests"))]
2905mod e2e_tests {
2906
2907}"#,
2908                    Some("e2e_tests {\n"),
2909                    vec![
2910                        ("#[ink::test]", Some("e2e_tests {\n"), Some("e2e_tests {\n")),
2911                        (
2912                            "#[ink_e2e::test]",
2913                            Some("e2e_tests {\n"),
2914                            Some("e2e_tests {\n"),
2915                        ),
2916                    ],
2917                ),
2918                (
2919                    r#"
2920#[cfg(all(test, feature = "e2e-tests"))]
2921mod e2e_tests {
2922    e2e
2923}"#,
2924                    Some("e2e->"),
2925                    vec![("#[ink_e2e::test]", Some("<-    e2e->"), Some("e2e->"))],
2926                ),
2927                // `impl` entities.
2928                (
2929                    r"
2930impl Contract {
2931
2932}",
2933                    Some("Contract {\n"),
2934                    vec![],
2935                ),
2936                (
2937                    r"
2938#[ink::contract]
2939mod contract {
2940    impl Contract {
2941
2942    }
2943}",
2944                    Some("Contract {\n"),
2945                    vec![
2946                        (
2947                            "#[ink(constructor)]",
2948                            Some("Contract {\n"),
2949                            Some("Contract {\n"),
2950                        ),
2951                        (
2952                            "#[ink(message)]",
2953                            Some("Contract {\n"),
2954                            Some("Contract {\n"),
2955                        ),
2956                    ],
2957                ),
2958                (
2959                    r"
2960#[ink(impl)]
2961impl Contract {
2962
2963}",
2964                    Some("Contract {\n"),
2965                    vec![
2966                        (
2967                            "#[ink(constructor)]",
2968                            Some("Contract {\n"),
2969                            Some("Contract {\n"),
2970                        ),
2971                        (
2972                            "#[ink(message)]",
2973                            Some("Contract {\n"),
2974                            Some("Contract {\n"),
2975                        ),
2976                    ],
2977                ),
2978                (
2979                    r"
2980#[ink::contract]
2981mod contract {
2982    impl Contract {
2983        fn
2984    }
2985}",
2986                    Some("fn"),
2987                    vec![
2988                        ("#[ink(constructor)]", Some("<-        fn"), Some("fn")),
2989                        ("#[ink(message)]", Some("<-        fn"), Some("fn")),
2990                    ],
2991                ),
2992                (
2993                    r"
2994#[ink::contract]
2995mod contract {
2996    impl Contract {
2997        new
2998    }
2999}",
3000                    Some("new"),
3001                    vec![("#[ink(constructor)]", Some("<-        new"), Some("new"))],
3002                ),
3003                (
3004                    r"
3005#[ink::contract]
3006mod contract {
3007    impl Contract {
3008        message
3009    }
3010}",
3011                    Some("message"),
3012                    vec![(
3013                        "#[ink(message)]",
3014                        Some("<-        message"),
3015                        Some("message"),
3016                    )],
3017                ),
3018                (
3019                    r"
3020#[ink::contract]
3021mod contract {
3022    impl Contract {
3023        #[ink(constructor)]
3024        pub fn new() -> Self {}
3025
3026        fn
3027    }
3028}",
3029                    Some("fn->"),
3030                    vec![
3031                        ("#[ink(constructor)]", Some("<-        fn->"), Some("fn->")),
3032                        ("#[ink(message)]", Some("<-        fn->"), Some("fn->")),
3033                    ],
3034                ),
3035                (
3036                    r"
3037#[ink::contract]
3038mod contract {
3039    impl Contract {
3040        fn
3041
3042        #[ink(constructor)]
3043        pub fn new() -> Self {}
3044    }
3045}",
3046                    Some("fn"),
3047                    vec![
3048                        ("#[ink(constructor)]", Some("<-        fn"), Some("fn")),
3049                        ("#[ink(message)]", Some("<-        fn"), Some("fn")),
3050                    ],
3051                ),
3052                // `trait` entities.
3053                (
3054                    r"
3055pub trait MyTrait {
3056
3057}",
3058                    Some("MyTrait {\n"),
3059                    vec![],
3060                ),
3061                (
3062                    r"
3063#[ink::trait_definition]
3064pub trait MyTrait {
3065
3066}",
3067                    Some("MyTrait {\n"),
3068                    vec![("#[ink(message)]", Some("MyTrait {\n"), Some("MyTrait {\n"))],
3069                ),
3070                (
3071                    r"
3072#[ink::trait_definition]
3073pub trait MyTrait {
3074    fn
3075}",
3076                    Some("fn"),
3077                    vec![("#[ink(message)]", Some("<-    fn"), Some("fn"))],
3078                ),
3079                (
3080                    r"
3081#[ink::chain_extension]
3082pub trait MyChainExtension {
3083
3084}",
3085                    Some("MyChainExtension {\n"),
3086                    vec![
3087                        (
3088                            "type ErrorCode = ()",
3089                            Some("MyChainExtension {\n"),
3090                            Some("MyChainExtension {\n"),
3091                        ),
3092                        (
3093                            if version.is_legacy() {
3094                                "#[ink(extension = 1)]"
3095                            } else {
3096                                "#[ink(function = 1)]"
3097                            },
3098                            Some("MyChainExtension {\n"),
3099                            Some("MyChainExtension {\n"),
3100                        ),
3101                    ],
3102                ),
3103                (
3104                    r"
3105#[ink::chain_extension]
3106pub trait MyChainExtension {
3107    type
3108}",
3109                    Some("type"),
3110                    vec![("type ErrorCode = ()", Some("<-    type"), Some("type"))],
3111                ),
3112                (
3113                    r"
3114#[ink::chain_extension]
3115pub trait MyChainExtension {
3116    fn
3117}",
3118                    Some("fn"),
3119                    vec![(
3120                        if version.is_legacy() {
3121                            "#[ink(extension = 1)]"
3122                        } else {
3123                            "#[ink(function = 1)]"
3124                        },
3125                        Some("<-    fn"),
3126                        Some("fn"),
3127                    )],
3128                ),
3129                // `struct` entities.
3130                (
3131                    r"
3132struct MyStruct {
3133
3134}",
3135                    Some("MyStruct {\n"),
3136                    vec![],
3137                ),
3138                (
3139                    r"
3140struct MyStruct {
3141    field
3142}",
3143                    Some("field"),
3144                    vec![],
3145                ),
3146                (
3147                    r"
3148#[ink(storage)]
3149struct MyStruct {
3150
3151}",
3152                    Some("MyStruct {\n"),
3153                    vec![],
3154                ),
3155                (
3156                    if version.is_legacy() {
3157                        r"
3158#[ink(event)]
3159struct MyStruct {
3160
3161}"
3162                    } else {
3163                        r"
3164#[ink::event]
3165struct MyStruct {
3166
3167}"
3168                    },
3169                    Some("MyStruct {\n"),
3170                    vec![("#[ink(topic)]", Some("MyStruct {\n"), Some("MyStruct {\n"))],
3171                ),
3172                (
3173                    if version.is_legacy() {
3174                        r"
3175#[ink(event)]
3176struct MyStruct {
3177    field
3178}"
3179                    } else {
3180                        r"
3181#[ink::event]
3182struct MyStruct {
3183    field
3184}"
3185                    },
3186                    Some("field"),
3187                    vec![("#[ink(topic)]", Some("<-    field"), Some("field"))],
3188                ),
3189                // multi-line items.
3190                (
3191                    r"
3192mod contract {
3193}",
3194                    Some("mod contract {"),
3195                    vec![],
3196                ),
3197                (
3198                    r"
3199mod contract {
3200}",
3201                    Some("}"),
3202                    vec![],
3203                ),
3204                (
3205                    r"
3206#[ink::contract]
3207mod contract {
3208}",
3209                    Some("#[ink::contract]\n"),
3210                    vec![],
3211                ),
3212                (
3213                    r"
3214#[ink::trait_definition]
3215pub trait MyTrait {
3216}",
3217                    Some("#[ink::trait_definition]\n"),
3218                    vec![],
3219                ),
3220            ] {
3221                let offset = TextSize::from(parse_offset_at(code, pat).unwrap() as u32);
3222
3223                let mut results = Vec::new();
3224                entity_completions(&mut results, &InkFile::parse(code), offset, version);
3225
3226                assert_eq!(
3227                    results
3228                        .iter()
3229                        .map(|completion| (
3230                            PartialMatchStr::from(completion.edit.text.as_str()),
3231                            completion.range
3232                        ))
3233                        .collect::<Vec<(PartialMatchStr, TextRange)>>(),
3234                    expected_results
3235                        .into_iter()
3236                        .map(|(edit, pat_start, pat_end)| (
3237                            PartialMatchStr::from(edit),
3238                            TextRange::new(
3239                                TextSize::from(parse_offset_at(code, pat_start).unwrap() as u32),
3240                                TextSize::from(parse_offset_at(code, pat_end).unwrap() as u32)
3241                            )
3242                        ))
3243                        .collect::<Vec<(PartialMatchStr, TextRange)>>(),
3244                    "code: {code}, version: {:?}",
3245                    version
3246                );
3247            }
3248        }
3249    }
3250}