use std::collections::HashMap;
use cairo_lang_defs::ids::LanguageElementId;
use cairo_lang_semantic::Expr;
use cairo_lang_semantic::db::SemanticGroup;
use cairo_lang_semantic::items::function_with_body::{
FunctionWithBodySemantic, SemanticExprLookup,
};
use cairo_lang_semantic::items::structure::StructSemantic;
use cairo_lang_semantic::lookup_item::LookupItemEx;
use cairo_lang_semantic::lsp_helpers::LspHelpers;
use cairo_lang_syntax::node::ast::{ExprStructCtorCall, StructArg};
use cairo_lang_syntax::node::kind::SyntaxKind;
use cairo_lang_syntax::node::{SyntaxNode, TypedSyntaxNode};
use cairo_language_common::CommonGroup;
use lsp_types::{CodeAction, CodeActionKind, CodeActionParams, Range, TextEdit, WorkspaceEdit};
use tracing::error;
use crate::lang::db::{AnalysisDatabase, LsSemanticGroup};
use crate::lang::lsp::ToLsp;
use crate::lang::visibility::peek_visible_in_with_edition;
pub fn fill_struct_fields<'db>(
db: &'db AnalysisDatabase,
node: SyntaxNode<'db>,
params: &CodeActionParams,
) -> Option<CodeAction> {
db.get_node_resultants(node)?.iter().find_map(|resultant_node| {
let module_id = db.find_module_containing_node(*resultant_node)?;
let original_file_id = node.stable_ptr(db).file_id(db);
let function_id = db.find_lookup_item(*resultant_node)?.function_with_body()?;
let constructor_expr = node.ancestor_of_type::<ExprStructCtorCall>(db)?;
let constructor_expr_semantic =
resultant_node.ancestor_of_type::<ExprStructCtorCall>(db)?;
let mut last_important_element = None;
let mut has_trailing_comma = false;
for node in constructor_expr.as_syntax_node().descendants(db) {
match node.kind(db) {
SyntaxKind::TokenComma => {
has_trailing_comma = true;
last_important_element = Some(node)
}
SyntaxKind::StructArgSingle => {
has_trailing_comma = false;
last_important_element = Some(node)
}
SyntaxKind::StructArgTail => return None,
_ => {}
}
}
let code_prefix =
String::from(if !has_trailing_comma && last_important_element.is_some() {
", "
} else {
" "
});
let struct_arguments = constructor_expr.arguments(db);
let left_brace = struct_arguments.lbrace(db);
let struct_arguments = struct_arguments.arguments(db).elements(db);
let already_present_arguments = struct_arguments
.map(|member| match member {
StructArg::StructArgSingle(argument) => {
argument.identifier(db).token(db).as_syntax_node().get_text_without_trivia(db)
}
StructArg::StructArgTail(_) => unreachable!(),
})
.collect::<Vec<_>>();
let constructor_resultant_expr_id = db
.lookup_expr_by_ptr(function_id, constructor_expr_semantic.stable_ptr(db).into())
.ok()?;
let semantic_db: &dyn SemanticGroup = db;
let constructor_semantic = match semantic_db
.expr_semantic(function_id, constructor_resultant_expr_id)
{
Expr::StructCtor(semantic) => semantic,
_ => {
error!(
"Semantic expression obtained from StructCtorCall doesn't refer to constructor."
);
return None;
}
};
let concrete_struct_id = constructor_semantic.concrete_struct_id;
let struct_parent_module_id = concrete_struct_id.struct_id(db).parent_module(db);
let arguments_to_complete = db
.concrete_struct_members(concrete_struct_id)
.ok()?
.iter()
.filter_map(|(name, member)| {
if already_present_arguments.contains(name) {
None
} else if peek_visible_in_with_edition(
db,
member.visibility,
struct_parent_module_id,
module_id,
) {
Some(format!("{}: ()", name.to_string(db)))
} else {
None
}
})
.collect::<Vec<_>>();
let code_to_insert = code_prefix + &arguments_to_complete.join(", ");
let edit_start = last_important_element
.unwrap_or(left_brace.as_syntax_node())
.span_end_without_trivia(db)
.position_in_file(db, original_file_id)?
.to_lsp();
let mut changes = HashMap::new();
let url = params.text_document.uri.clone();
let change =
TextEdit { range: Range::new(edit_start, edit_start), new_text: code_to_insert };
changes.insert(url, vec![change]);
let edit = WorkspaceEdit::new(changes);
Some(CodeAction {
title: String::from("Fill struct fields"),
kind: Some(CodeActionKind::QUICKFIX),
edit: Some(edit),
..Default::default()
})
})
}