tally-todo 0.7.0

Make TODO management a little more automatic
use anyhow::{Result, anyhow};
use std::collections::HashSet;
use std::env;
use std::io::ErrorKind;
use std::process::Command;

use crate::services::storage::config_storage::ConfigStorage;
use crate::utils::project_paths::ProjectPaths;

pub fn cmd_edit() -> Result<()> {
    let paths = ProjectPaths::get_paths()?;
    let config_storage = ConfigStorage::new(&paths.config_file)?;
    let config = config_storage.get_config();

    let mut candidates = Vec::new();

    if let Some(editor) = config
        .preferences
        .editor
        .as_deref()
        .map(str::trim)
        .filter(|s| !s.is_empty())
    {
        candidates.push(editor.to_string());
    }

    if let Ok(editor) = env::var("EDITOR") {
        let editor = editor.trim();
        if !editor.is_empty() {
            candidates.push(editor.to_string());
        }
    }

    for editor in ["nvim", "vim", "nano", "vi"] {
        candidates.push(editor.to_string());
    }

    let mut seen = HashSet::new();
    let unique_candidates: Vec<String> = candidates
        .into_iter()
        .filter(|editor| seen.insert(editor.clone()))
        .collect();

    for candidate in unique_candidates {
        let mut parts = candidate.split_whitespace();
        let Some(command) = parts.next() else {
            continue;
        };
        let args: Vec<&str> = parts.collect();

        match Command::new(command)
            .args(args)
            .arg(&paths.todo_file)
            .status()
        {
            Ok(status) if status.success() => return Ok(()),
            Ok(status) => {
                return Err(anyhow!(
                    "Editor '{}' exited with non-zero status: {}",
                    candidate,
                    status
                ));
            }
            Err(err) if err.kind() == ErrorKind::NotFound => continue,
            Err(err) => return Err(anyhow!("Failed to launch editor '{}': {}", candidate, err)),
        }
    }

    Err(anyhow!(
        "No editor available. Set 'preferences.editor' with `tally config set preferences.editor <editor>` or export EDITOR"
    ))
}