Skip to content

Commit

Permalink
Refactor mod completions (#247)
Browse files Browse the repository at this point in the history
  • Loading branch information
Draggu authored Feb 7, 2025
1 parent 4d6a71d commit 61a6f01
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 54 deletions.
42 changes: 36 additions & 6 deletions src/ide/completion/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use attribute::{attribute_completions, derive_completions};
use cairo_lang_syntax::node::TypedSyntaxNode;
use cairo_lang_syntax::node::ast::{
self, Attribute, BinaryOperator, ExprBinary, ExprPath, ExprStructCtorCall, UsePathLeaf,
UsePathSingle,
self, Attribute, BinaryOperator, ExprBinary, ExprPath, ExprStructCtorCall, ItemModule,
TokenIdentifier, UsePathLeaf, UsePathSingle,
};
use cairo_lang_syntax::node::kind::SyntaxKind;
use cairo_lang_syntax::node::{Token, TypedSyntaxNode};
use colon_colon::{expr_path, use_statement};
use completions::{dot_completions, struct_constructor_completions};
use if_chain::if_chain;
Expand Down Expand Up @@ -133,9 +133,39 @@ pub fn complete(params: CompletionParams, db: &AnalysisDatabase) -> Option<Compl
}
);

if completions.is_empty() && trigger_kind == CompletionTriggerKind::INVOKED && !was_single {
let result = mod_completions(db, node, file_id)
.unwrap_or_else(|| generic_completions(db, module_file_id, lookup_items));
if_chain!(
if let Some(ident) = TokenIdentifier::cast(db, node.clone());
if let Some(module_item) = node.parent_of_type::<ItemModule>(db);
// We are in nested mod, we should not show completions for file modules.
if module_item.as_syntax_node().ancestor_of_kind(db, SyntaxKind::ItemModule).is_none();
if let Some(mod_names_completions) = mod_completions(db, module_item, file_id, &ident.text(db));

then {
completions.extend(mod_names_completions);
}
);

if_chain!(
// if there is no name `mod <cursor>` we will be on `mod`.
if node.kind(db) == SyntaxKind::TokenModule;
if let Some(module_item) = node.parent_of_type::<ItemModule>(db);
// We are in nested mod, we should not show completions for file modules.
if module_item.as_syntax_node().ancestor_of_kind(db, SyntaxKind::ItemModule).is_none();
// use "" as typed text in this case.
if let Some(mod_names_completions) = mod_completions(db, module_item, file_id, "");

then {
completions.extend(mod_names_completions);
}
);

if completions.is_empty()
&& trigger_kind == CompletionTriggerKind::INVOKED
&& !was_single
// Another quickfix, `generic_completions` must be split into smaller parts to fit into system.
&& node.ancestor_of_kind(db, SyntaxKind::ExprStructCtorCall).is_none()
{
let result = generic_completions(db, module_file_id, lookup_items);

completions.extend(result);
}
Expand Down
53 changes: 5 additions & 48 deletions src/ide/completion/mod_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,29 @@ use std::path::{Path, PathBuf};
use cairo_lang_defs::db::DefsGroup;
use cairo_lang_defs::ids::ModuleId;
use cairo_lang_filesystem::ids::FileId;
use cairo_lang_syntax::node::TypedSyntaxNode;
use cairo_lang_syntax::node::ast::{ItemModule, MaybeModuleBody};
use cairo_lang_syntax::node::kind::SyntaxKind;
use cairo_lang_syntax::node::{SyntaxNode, Token, TypedSyntaxNode};
use lsp_types::{CompletionItem, CompletionItemKind, Url};

use crate::lang::db::AnalysisDatabase;
use crate::lang::lsp::LsProtoGroup;
use crate::lang::syntax::SyntaxNodeExt;

pub fn mod_completions(
db: &AnalysisDatabase,
origin_node: SyntaxNode,
module: ItemModule,
file: FileId,
typed_module_name: &str,
) -> Option<Vec<CompletionItem>> {
let node = origin_node
.ancestors_with_self()
.find(|node| node.parent_kind(db) == Some(SyntaxKind::ItemModule))?;

// We are in nested mod, we should not show completions for file modules.
if node.ancestor_of_kind(db, SyntaxKind::ItemModule).is_some() {
return Some(Vec::new());
}

let module = ItemModule::from_syntax_node(db, node.parent().unwrap());

let semicolon_missing = match module.body(db) {
MaybeModuleBody::None(semicolon) => {
semicolon.token(db).as_syntax_node().kind(db) == SyntaxKind::TokenMissing
}
// If this module has body (ie. { /* some code */ }) we should not propose file names as
// completion.
MaybeModuleBody::Some(body) => {
let body_node = body.as_syntax_node();

return if std::iter::successors(Some(origin_node.clone()), SyntaxNode::parent)
.any(|node| node == body_node)
{
// If we are in body allow other completions.
None
} else {
// Otherwise we are on keyword, name or semicolon, we should not complete anything.
Some(Vec::new())
};
}
MaybeModuleBody::Some(_) => return None,
};

let typed_module_name = already_typed_text(db, module, node)?;

let mut url = db.url_for_file(file)?;

let file_name = pop_path(&mut url)?;
Expand All @@ -77,7 +52,7 @@ pub fn mod_completions(
if !existing_modules_files.contains(&cairo_file.to_string_lossy().to_string()) {
let file_name = file.strip_suffix(".cairo").unwrap_or(&file);

if file_name.starts_with(&typed_module_name) {
if file_name.starts_with(typed_module_name) {
let label = file_name.strip_suffix(".cairo").unwrap_or(file_name);
let semicolon = semicolon_missing.then(|| ";".to_string()).unwrap_or_default();

Expand All @@ -93,24 +68,6 @@ pub fn mod_completions(
Some(result)
}

fn already_typed_text(
db: &AnalysisDatabase,
module: ItemModule,
node: SyntaxNode,
) -> Option<String> {
if module.module_kw(db).as_syntax_node() == node {
Some(String::new())
} else {
let module_name = module.name(db);

if module_name.as_syntax_node() == node {
Some(module_name.token(db).text(db).to_string())
} else {
None
}
}
}

fn pop_path(url: &mut Url) -> Option<String> {
let file_name = url.path_segments()?.next_back()?.to_string();

Expand Down

0 comments on commit 61a6f01

Please sign in to comment.