use std::fs;
use std::io::Write;
use std::path::Path;
use anyhow::Result;
const IGNORE_DIRS: &[&str] = &[".ccgo", "cmake_build"];
pub fn update_ide_ignores(project_dir: &Path) -> Result<()> {
update_gitignore(project_dir)?;
update_vscode(project_dir)?;
update_jetbrains(project_dir)?;
Ok(())
}
fn update_gitignore(project_dir: &Path) -> Result<()> {
let path = project_dir.join(".gitignore");
let existing = if path.exists() {
fs::read_to_string(&path)?
} else {
String::new()
};
let to_add: Vec<&str> = IGNORE_DIRS
.iter()
.filter(|&&dir| {
!existing.contains(&format!("{dir}/"))
&& !existing.split_whitespace().any(|token| token == dir)
})
.copied()
.collect();
if to_add.is_empty() {
return Ok(());
}
let mut file = if path.exists() {
fs::OpenOptions::new().append(true).open(&path)?
} else {
fs::File::create(&path)?
};
writeln!(file, "\n# Generated by ccgo")?;
for dir in &to_add {
writeln!(file, "{dir}/")?;
println!(" Added {dir}/ to .gitignore");
}
Ok(())
}
fn update_vscode(project_dir: &Path) -> Result<()> {
let vscode_dir = project_dir.join(".vscode");
let settings_path = vscode_dir.join("settings.json");
let mut settings: serde_json::Value = if settings_path.exists() {
let content = fs::read_to_string(&settings_path)?;
match serde_json::from_str(&content) {
Ok(v) => v,
Err(_) => return Ok(()),
}
} else {
serde_json::json!({})
};
let obj = match settings.as_object_mut() {
Some(o) => o,
None => return Ok(()),
};
let mut changed = false;
for section in &["files.exclude", "search.exclude"] {
let map = obj
.entry(*section)
.or_insert_with(|| serde_json::json!({}));
if let Some(map) = map.as_object_mut() {
for dir in IGNORE_DIRS {
if !map.contains_key(*dir) {
map.insert((*dir).to_string(), serde_json::Value::Bool(true));
changed = true;
}
}
}
}
if !changed {
return Ok(());
}
fs::create_dir_all(&vscode_dir)?;
let json = serde_json::to_string_pretty(&settings)?;
fs::write(&settings_path, format!("{json}\n"))?;
println!(" Updated .vscode/settings.json (files.exclude / search.exclude)");
Ok(())
}
fn update_jetbrains(project_dir: &Path) -> Result<()> {
let idea_dir = project_dir.join(".idea");
if !idea_dir.is_dir() {
return Ok(());
}
let iml_files: Vec<_> = fs::read_dir(&idea_dir)?
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().map(|x| x == "iml").unwrap_or(false))
.collect();
for entry in iml_files {
let path = entry.path();
let content = fs::read_to_string(&path)?;
let to_add: Vec<&str> = IGNORE_DIRS
.iter()
.filter(|&&dir| !content.contains(&format!("$MODULE_DIR$/{dir}")))
.copied()
.collect();
if to_add.is_empty() {
continue;
}
let Some(close_pos) = content.find("</content>") else {
continue;
};
let close_indent = content[..close_pos]
.rfind('\n')
.map(|nl| {
let after = &content[nl + 1..close_pos];
let ws = after.len() - after.trim_start().len();
" ".repeat(ws)
})
.unwrap_or_else(|| " ".to_string());
let inner_indent = format!("{close_indent} ");
let insert: String = to_add
.iter()
.map(|dir| {
format!(
"{inner_indent}<excludeFolder url=\"file://$MODULE_DIR$/{dir}\" />\n"
)
})
.collect();
let new_content =
format!("{}{insert}{close_indent}</content>{}", &content[..close_pos], &content[close_pos + "</content>".len()..]);
fs::write(&path, new_content)?;
println!(
" Updated {} (excludeFolder for {})",
path.display(),
to_add.join(", ")
);
}
Ok(())
}