Skip to main content

forgekit_core/edit/
undo.rs

1use std::path::PathBuf;
2
3use crate::error::Result;
4
5pub(crate) enum UndoableOp {
6    CreateFile {
7        path: PathBuf,
8    },
9    WriteFile {
10        path: PathBuf,
11        previous: Option<String>,
12    },
13    CreateDirectory {
14        path: PathBuf,
15    },
16}
17
18pub struct PendingUndo {
19    pub(crate) operation: UndoableOp,
20}
21
22pub enum UndoResult {
23    Undone { operation: String },
24    Empty,
25}
26
27impl super::EditModule {
28    pub fn with_undo_capacity(mut self, capacity: usize) -> Self {
29        self.undo_capacity = capacity;
30        self
31    }
32
33    pub async fn undo(&self) -> Result<UndoResult> {
34        let op = {
35            let mut stack = self.undo_stack.lock();
36            if stack.is_empty() {
37                return Ok(UndoResult::Empty);
38            }
39            stack
40                .pop()
41                .expect("invariant: stack non-empty after is_empty check")
42        };
43
44        match op.operation {
45            UndoableOp::CreateFile { path } => {
46                let full = self.store.codebase_path.join(&path);
47                if full.exists() {
48                    tokio::fs::remove_file(&full).await?;
49                }
50                Ok(UndoResult::Undone {
51                    operation: format!("create_file({})", path.display()),
52                })
53            }
54            UndoableOp::WriteFile { path, previous } => {
55                let full = self.store.codebase_path.join(&path);
56                match previous {
57                    Some(content) => {
58                        tokio::fs::write(&full, content).await?;
59                    }
60                    None => {
61                        if full.exists() {
62                            tokio::fs::remove_file(&full).await?;
63                        }
64                    }
65                }
66                Ok(UndoResult::Undone {
67                    operation: format!("write_file({})", path.display()),
68                })
69            }
70            UndoableOp::CreateDirectory { path } => {
71                let full = self.store.codebase_path.join(&path);
72                if full.exists() {
73                    tokio::fs::remove_dir(&full).await?;
74                }
75                Ok(UndoResult::Undone {
76                    operation: format!("create_directory({})", path.display()),
77                })
78            }
79        }
80    }
81
82    pub fn can_undo(&self) -> bool {
83        !self.undo_stack.lock().is_empty()
84    }
85
86    pub fn undo_depth(&self) -> usize {
87        self.undo_stack.lock().len()
88    }
89
90    pub fn clear_undo_stack(&self) {
91        self.undo_stack.lock().clear();
92    }
93
94    pub(crate) fn push_undo(&self, op: UndoableOp) {
95        let mut stack = self.undo_stack.lock();
96        if stack.len() >= self.undo_capacity {
97            stack.remove(0);
98        }
99        stack.push(PendingUndo { operation: op });
100    }
101}