flop_cli/
editor.rs

1use anyhow::{Context, Result};
2use regex::Regex;
3use std::collections::{HashMap, HashSet};
4use std::fs;
5use std::path::PathBuf;
6
7use crate::types::Match;
8
9pub fn apply_changes(matches: &[Match], uncomment: bool) -> Result<()> {
10    // Group matches by file
11    let mut files_map: HashMap<PathBuf, Vec<&Match>> = HashMap::new();
12
13    for m in matches {
14        files_map.entry(m.file_path.clone()).or_default().push(m);
15    }
16
17    for (file_path, file_matches) in files_map {
18        let content = fs::read_to_string(&file_path)?;
19        let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
20
21        // Sort by line number in reverse order to avoid index shifting
22        let mut sorted_matches = file_matches;
23        sorted_matches.sort_by(|a, b| b.line_number.cmp(&a.line_number));
24
25        for m in sorted_matches {
26            let idx = m.line_number - 1;
27            if idx < lines.len() {
28                if uncomment {
29                    // Remove the comment
30                    lines[idx] = uncomment_line(&lines[idx]);
31                } else {
32                    // Add comment
33                    lines[idx] = comment_line(&lines[idx]);
34                }
35            }
36        }
37
38        let new_content = lines.join("\n") + "\n";
39        fs::write(&file_path, new_content)
40            .with_context(|| format!("Failed to write file: {}", file_path.display()))?;
41    }
42
43    Ok(())
44}
45
46pub fn delete_changes(matches: &[Match]) -> Result<()> {
47    // Group matches by file
48    let mut files_map: HashMap<PathBuf, Vec<&Match>> = HashMap::new();
49
50    for m in matches {
51        files_map.entry(m.file_path.clone()).or_default().push(m);
52    }
53
54    for (file_path, file_matches) in files_map {
55        let content = fs::read_to_string(&file_path)?;
56        let lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
57
58        // Sort by line number in reverse order to avoid index shifting
59        let mut sorted_matches = file_matches;
60        sorted_matches.sort_by(|a, b| b.line_number.cmp(&a.line_number));
61
62        // Collect line numbers to delete
63        let mut lines_to_delete: HashSet<usize> = HashSet::new();
64        for m in sorted_matches {
65            lines_to_delete.insert(m.line_number - 1);
66        }
67
68        // Filter out lines to delete
69        let new_lines: Vec<String> = lines
70            .into_iter()
71            .enumerate()
72            .filter(|(idx, _)| !lines_to_delete.contains(idx))
73            .map(|(_, line)| line)
74            .collect();
75
76        let new_content = new_lines.join("\n") + "\n";
77        fs::write(&file_path, new_content)
78            .with_context(|| format!("Failed to write file: {}", file_path.display()))?;
79    }
80
81    Ok(())
82}
83
84fn comment_line(line: &str) -> String {
85    // Find the first non-whitespace character and insert // before it
86    let trimmed = line.trim_start();
87    let leading_spaces = line.len() - trimmed.len();
88    format!("{}// {}", " ".repeat(leading_spaces), trimmed)
89}
90
91fn uncomment_line(line: &str) -> String {
92    // Remove the // comment marker
93    let re = Regex::new(r"^(\s*)//\s*(.*)$").unwrap();
94    if let Some(caps) = re.captures(line) {
95        format!("{}{}", &caps[1], &caps[2])
96    } else {
97        line.to_string()
98    }
99}