use indoc::{formatdoc, indoc};
use lsp_types::{
ClientCapabilities, CodeActionContext, CodeActionOrCommand, CodeActionParams,
HoverClientCapabilities, MarkupKind, Range, TextDocumentClientCapabilities, lsp_request,
};
use serde_json::json;
use crate::macros::SCARB_TEST_MACROS_V2_PACKAGE;
use crate::support::cairo_project_toml::{CAIRO_PROJECT_TOML, CAIRO_PROJECT_TOML_2025_12};
use crate::support::cursor::Cursor;
use crate::support::fixture::Fixture;
use crate::support::{cursors, fixture, sandbox};
mod create_module_file;
mod fill_struct_fields;
mod fill_trait_members;
mod lint;
mod macro_expand;
mod make_variable_mutable;
mod missing_import;
mod missing_trait;
mod rename_unused_variable;
mod scarb_manifest;
mod similar_identifier;
mod similar_member;
mod similar_method;
fn caps(base: ClientCapabilities) -> ClientCapabilities {
ClientCapabilities {
text_document: base.text_document.or_else(Default::default).map(|it| {
TextDocumentClientCapabilities {
hover: Some(HoverClientCapabilities {
dynamic_registration: Some(false),
content_format: Some(vec![MarkupKind::Markdown, MarkupKind::PlainText]),
}),
..it
}
}),
..base
}
}
fn quick_fix_with_linter(cairo_code: &str) -> String {
quick_fix_general(
cairo_code,
fixture! {
"cairo_project.toml" => CAIRO_PROJECT_TOML_2025_12,
},
true,
false,
)
}
fn quick_fix_with_macros(cairo_code: &str) -> String {
quick_fix_general(
cairo_code,
fixture! {
"Scarb.toml" => indoc! (r#"
[package]
name = "a"
version = "0.1.0"
edition = "2025_12"
[dev-dependencies]
snforge_std = "0.50.0"
[tool.scarb]
allow-prebuilt-plugins = ["snforge_std"]
"#)
},
true,
true,
)
}
fn quick_fix_with_scarb_macros(cairo_code: &str) -> String {
quick_fix_general(
cairo_code,
fixture! {
"Scarb.toml" => formatdoc!(
r#"
[package]
name = "test_package"
version = "0.1.0"
edition = "2025_12"
[dependencies]
cairols_test_macros_v2 = {{ path = "{}" }}
"#,
SCARB_TEST_MACROS_V2_PACKAGE.display()
)
},
true,
true,
)
}
fn quick_fix(cairo_code: &str) -> String {
quick_fix_general(
cairo_code,
fixture! {
"cairo_project.toml" => CAIRO_PROJECT_TOML_2025_12,
},
false,
false,
)
}
fn quick_fix_without_visibility_constraints(cairo_code: &str) -> String {
quick_fix_general(
cairo_code,
fixture! {
"cairo_project.toml" => CAIRO_PROJECT_TOML,
},
false,
false,
)
}
fn quick_fix_general(cairo_code: &str, mut fixture: Fixture, linter: bool, macros: bool) -> String {
let (cairo, cursors) = cursors(cairo_code);
fixture.add_file("src/lib.cairo", cairo);
let mut ls = sandbox! {
fixture = fixture;
client_capabilities = caps;
workspace_configuration = json!({
"cairo1": {
"enableLinter": linter,
"enableProcMacros": macros,
}
});
};
let diagnostics = ls.open_and_wait_for_diagnostics("src/lib.cairo");
let range = match cursors.assert_single() {
Cursor::Caret(position) => Range { start: position, end: position },
Cursor::Selection(range) => range,
};
let root_path = ls.fixture.root_path().to_string_lossy().to_string();
let code_action_params = CodeActionParams {
text_document: ls.doc_id("src/lib.cairo"),
range,
context: CodeActionContext {
diagnostics: diagnostics
.into_iter()
.filter(|diagnostic| {
diagnostic.range.start <= range.end && range.start <= diagnostic.range.end
})
.collect(),
only: None,
trigger_kind: None,
},
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
};
let code_actions =
ls.send_request::<lsp_request!("textDocument/codeAction")>(code_action_params);
let Some(code_actions) = code_actions else {
panic!("Code actions request failed.");
};
render_code_actions_or_commands(code_actions, &root_path)
}
fn render_code_actions_or_commands(
code_actions_or_commands: Vec<CodeActionOrCommand>,
root_path: &str,
) -> String {
if code_actions_or_commands.is_empty() {
return "No code actions.\n".to_string();
}
let mut result = String::new();
for code_action_or_command in code_actions_or_commands {
result.push_str(&render_code_action_or_command(&code_action_or_command, root_path));
}
result
}
fn render_code_action_or_command(
code_action_or_command: &CodeActionOrCommand,
root_path: &str,
) -> String {
let mut result = String::new();
match code_action_or_command {
CodeActionOrCommand::Command(_) => todo!("Not implemented yet"),
CodeActionOrCommand::CodeAction(code_action) => {
result.push_str(&format!("Title: {}\n", code_action.title));
if let Some(document_changes) =
code_action.edit.as_ref().and_then(|edit| edit.document_changes.as_ref())
{
result.push_str(&format!(
"Document changes json: {}\n",
serde_json::to_string_pretty(&document_changes).unwrap().replace(root_path, "")
));
}
let Some(changes) = code_action.edit.as_ref().and_then(|edit| edit.changes.as_ref())
else {
return result;
};
for edits in changes.values() {
for edit in edits {
result.push_str(&format!(
"Add new text: \"{}\"\nAt: {:?}\n",
edit.new_text, edit.range
));
}
}
}
}
result
}