cairo-language-server 2.20.0

The Cairo Language Server
Documentation
use std::collections::HashSet;

use cairo_lang_defs::ids::{GenericTypeId, LanguageElementId};
use cairo_lang_semantic::diagnostic::{NotFoundItemType, SemanticDiagnostics};
use cairo_lang_semantic::items::structure::StructSemantic;
use cairo_lang_semantic::resolve::ResolutionContext;
use cairo_lang_semantic::resolve::ResolvedGenericItem;
use cairo_lang_syntax::node::{
    Token, TypedSyntaxNode,
    ast::{PatternEnum, PatternIdentifier, PatternStruct, PatternStructParam},
};
use itertools::Itertools;
use lsp_types::{CompletionItem, CompletionItemKind, CompletionItemLabelDetails};

use crate::ide::completion::helpers::formatting::format_type_in_node_context;
use crate::ide::completion::{CompletionItemOrderable, CompletionRelevance};
use crate::lang::db::AnalysisDatabase;
use crate::lang::text_matching::text_matches;
use crate::lang::visibility::peek_visible_in_with_edition;
use crate::{
    ide::completion::path::path_prefix_completions, lang::analysis_context::AnalysisContext,
};

pub fn struct_pattern_completions<'db>(
    db: &'db AnalysisDatabase,
    ctx: &AnalysisContext<'db>,
) -> Vec<CompletionItemOrderable> {
    let (all_members, existing_members, typed, struct_parent_module) = if let Some(pattern) =
        ctx.node.ancestor_of_type::<PatternStruct>(db)
        && let typed = ctx.node.ancestor_of_type::<PatternIdentifier>(db).filter(|ident| {
            ident.as_syntax_node().parent(db).and_then(|p| p.parent(db))
                == Some(pattern.as_syntax_node())
        })
        && let Ok(ResolvedGenericItem::GenericType(GenericTypeId::Struct(struct_item))) =
            ctx.resolver(db).resolve_generic_path(
                &mut SemanticDiagnostics::new(ctx.module_id),
                &pattern.path(db),
                NotFoundItemType::Type,
                ResolutionContext::Default,
            )
        && let Ok(all_members) = db.struct_members(struct_item)
    {
        (all_members, pattern.params(db).elements(db), typed, struct_item.parent_module(db))
    } else {
        return Default::default();
    };

    // Don't propose fields if any are not visible from the current module.
    if !all_members.values().all(|member| {
        peek_visible_in_with_edition(db, member.visibility, struct_parent_module, ctx.module_id)
    }) {
        return Default::default();
    }

    let existing_members: HashSet<_> = existing_members
        .into_iter()
        .filter_map(|member| match member {
            PatternStructParam::Single(ident) => Some(ident.name(db).token(db).text(db)),
            PatternStructParam::WithExpr(params) => Some(params.name(db).token(db).text(db)),
            PatternStructParam::Tail(_) => None,
        })
        .collect();

    let typed_string = typed
        .clone()
        .map(|ident| ident.name(db).token(db).text(db).to_string(db))
        .unwrap_or_default();

    all_members
        .iter()
        .filter(|(member_name, _)| !existing_members.contains(member_name))
        .filter(|(member_name, _)| text_matches(member_name.to_string(db), &typed_string))
        .map(|(member_name, member)| CompletionItemOrderable {
            item: CompletionItem {
                label: member_name.to_string(db),
                label_details: Some(CompletionItemLabelDetails {
                    description: Some(format_type_in_node_context(
                        db,
                        typed
                            .clone()
                            .map(|typed_pattern| typed_pattern.as_syntax_node())
                            .unwrap_or(ctx.node),
                        &member.ty,
                    )),
                    detail: None,
                }),
                kind: Some(CompletionItemKind::VARIABLE),
                ..CompletionItem::default()
            },
            relevance: CompletionRelevance::Highest,
        })
        .collect()
}

pub fn enum_pattern_completions<'db>(
    db: &'db AnalysisDatabase,
    ctx: &AnalysisContext<'db>,
) -> Vec<CompletionItemOrderable> {
    if let Some(pattern) = ctx.node.ancestor_of_type::<PatternEnum>(db)
        && let path = pattern.path(db)
        && let mut segments = path.segments(db).elements(db).collect_vec()
        && let _ = {
            // If there is tail (ie. some::path::) last segment will be of type missing, remove it.
            if path.segments(db).has_tail(db) {
                segments.pop();
            }
        }
        && let Some(result) = path_prefix_completions(db, ctx, segments)
    {
        result
    } else {
        Default::default()
    }
}