use std::collections::HashSet;
use cairo_lang_defs::ids::ImportableId;
use cairo_lang_semantic::items::us::get_use_path_segments;
use cairo_lang_syntax::node::ast::{ItemUse, UsePathLeaf, UsePathMulti, UsePathSingle};
use cairo_lang_syntax::node::kind::SyntaxKind::{
UsePathLeaf as UsePathLeafKind, UsePathMulti as UsePathMultiKind,
UsePathSingle as UsePathSingleKind, UsePathStar,
};
use cairo_lang_syntax::node::{
Token, TypedSyntaxNode,
ast::{PathSegment, UsePath},
};
use super::{helpers::item::first_segment_completion_candidates, path::path_prefix_completions};
use crate::ide::completion::CompletionItemOrderable;
use crate::lang::analysis_context::AnalysisContext;
use crate::lang::db::AnalysisDatabase;
pub fn use_completions<'db>(
db: &'db AnalysisDatabase,
ctx: &AnalysisContext<'db>,
) -> Vec<CompletionItemOrderable> {
if let Some(single) = ctx.node.ancestor_of_type::<UsePathSingle>(db)
&& let Some(use_completions) = use_statement(db, single, ctx)
{
return use_completions;
}
if ctx.node.ancestor_of_type::<UsePathSingle>(db).is_none()
&& let Some(leaf) = ctx.node.ancestor_of_type::<UsePathLeaf>(db)
&& let Some(use_completions) = use_statement_first_segment(db, leaf, ctx)
{
return use_completions;
}
if ctx
.node
.ancestor_of_kinds(db, &[UsePathSingleKind, UsePathLeafKind, UsePathMultiKind, UsePathStar])
.is_none()
&& ctx.node.ancestor_of_type::<ItemUse>(db).is_some()
&& let Some(use_completions) = first_segment(db, "", ctx)
{
return use_completions;
}
vec![]
}
fn use_statement<'db>(
db: &'db AnalysisDatabase,
use_path_single: UsePathSingle<'db>,
ctx: &AnalysisContext<'db>,
) -> Option<Vec<CompletionItemOrderable>> {
get_use_path_segments(db, UsePath::Single(use_path_single))
.ok()
.and_then(|segments| path_prefix_completions(db, ctx, segments.segments))
.map(|items| {
let excluded = already_imported_in_multi(db, ctx);
items.into_iter().filter(|item| !excluded.contains(&item.item.label)).collect()
})
}
fn already_imported_in_multi<'db>(
db: &'db AnalysisDatabase,
ctx: &AnalysisContext<'db>,
) -> HashSet<String> {
let Some(multi) = ctx.node.ancestor_of_type::<UsePathMulti>(db) else {
return HashSet::default();
};
let current_leaf_ptr =
ctx.node.ancestor_of_type::<UsePathLeaf>(db).map(|leaf| leaf.stable_ptr(db));
multi
.use_paths(db)
.elements(db)
.filter_map(|use_path| {
let UsePath::Leaf(leaf) = use_path else { return None };
if Some(leaf.stable_ptr(db)) == current_leaf_ptr {
return None;
}
let PathSegment::Simple(simple) = leaf.ident(db) else { return None };
Some(simple.ident(db).token(db).text(db).to_string(db))
})
.collect()
}
fn use_statement_first_segment<'db>(
db: &'db AnalysisDatabase,
use_path_leaf: UsePathLeaf<'db>,
ctx: &AnalysisContext<'db>,
) -> Option<Vec<CompletionItemOrderable>> {
get_use_path_segments(db, UsePath::Leaf(use_path_leaf)).ok().and_then(|mut segments| {
let typed = segments.segments.pop()?;
if segments.segments.is_empty() {
if let PathSegment::Simple(typed) = typed {
first_segment(db, &typed.ident(db).token(db).text(db).to_string(db), ctx)
} else {
None
}
} else {
None
}
})
}
fn first_segment<'db>(
db: &'db AnalysisDatabase,
typed: &str,
ctx: &AnalysisContext<'db>,
) -> Option<Vec<CompletionItemOrderable>> {
Some(
first_segment_completion_candidates(db, ctx, typed)
.into_iter()
.filter_map(|candidate| {
match candidate.completion.importable_id {
ImportableId::Submodule(_) | ImportableId::Crate(_) | ImportableId::Enum(_)
=>
{
Some(candidate.into_path_completion())
}
_ => None,
}
})
.collect(),
)
}