use regex::Regex;
#[derive(Debug, Clone)]
pub struct IncompletePlan {
pub path: String,
pub filename: String,
pub done: usize,
pub pending: usize,
pub total: usize,
}
pub fn find_incomplete_plans(project_path: &std::path::Path) -> Vec<IncompletePlan> {
let plans_dir = project_path.join("plans");
if !plans_dir.exists() {
return Vec::new();
}
let task_regex = Regex::new(r"^\s*-\s*\[([ x~!])\]").unwrap();
let mut incomplete = Vec::new();
if let Ok(entries) = std::fs::read_dir(&plans_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().map(|e| e == "md").unwrap_or(false)
&& let Ok(content) = std::fs::read_to_string(&path)
{
let mut done = 0;
let mut pending = 0;
let mut in_progress = 0;
for line in content.lines() {
if let Some(caps) = task_regex.captures(line) {
match caps.get(1).map(|m| m.as_str()) {
Some("x") => done += 1,
Some(" ") => pending += 1,
Some("~") => in_progress += 1,
Some("!") => done += 1, _ => {}
}
}
}
let total = done + pending + in_progress;
if total > 0 && (pending > 0 || in_progress > 0) {
let rel_path = path
.strip_prefix(project_path)
.map(|p| p.display().to_string())
.unwrap_or_else(|_| path.display().to_string());
incomplete.push(IncompletePlan {
path: rel_path,
filename: path
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_default(),
done,
pending: pending + in_progress,
total,
});
}
}
}
}
incomplete.sort_by(|a, b| b.filename.cmp(&a.filename));
incomplete
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PlanMode {
#[default]
Standard,
Planning,
}
impl PlanMode {
pub fn toggle(&self) -> Self {
match self {
PlanMode::Standard => PlanMode::Planning,
PlanMode::Planning => PlanMode::Standard,
}
}
pub fn is_planning(&self) -> bool {
matches!(self, PlanMode::Planning)
}
pub fn display_name(&self) -> &'static str {
match self {
PlanMode::Standard => "standard mode",
PlanMode::Planning => "plan mode",
}
}
}