Rust · 4841 bytes Raw Blame History
1 use std::path::PathBuf;
2
3 /// Status indicators for a file in git
4 #[derive(Debug, Clone, Default, PartialEq, Eq)]
5 pub struct FileStatus {
6 pub is_staged: bool,
7 pub is_unstaged: bool,
8 pub is_untracked: bool,
9 pub has_incoming: bool,
10 pub is_gitignored: bool,
11 }
12
13 impl FileStatus {
14 pub fn new() -> Self {
15 Self::default()
16 }
17
18 pub fn staged() -> Self {
19 Self {
20 is_staged: true,
21 ..Default::default()
22 }
23 }
24
25 pub fn unstaged() -> Self {
26 Self {
27 is_unstaged: true,
28 ..Default::default()
29 }
30 }
31
32 pub fn untracked() -> Self {
33 Self {
34 is_untracked: true,
35 ..Default::default()
36 }
37 }
38
39 /// Merge status flags from another FileStatus
40 pub fn merge(&mut self, other: &FileStatus) {
41 self.is_staged |= other.is_staged;
42 self.is_unstaged |= other.is_unstaged;
43 self.is_untracked |= other.is_untracked;
44 self.has_incoming |= other.has_incoming;
45 self.is_gitignored |= other.is_gitignored;
46 }
47
48 /// Check if file has any modifications
49 pub fn is_dirty(&self) -> bool {
50 self.is_staged || self.is_unstaged || self.is_untracked
51 }
52 }
53
54 /// A file entry from git status
55 #[derive(Debug, Clone)]
56 pub struct FileEntry {
57 pub path: PathBuf,
58 pub status: FileStatus,
59 }
60
61 impl FileEntry {
62 pub fn new(path: PathBuf, status: FileStatus) -> Self {
63 Self { path, status }
64 }
65 }
66
67 /// Node in the file tree (first-child, next-sibling representation)
68 #[derive(Debug, Clone)]
69 pub struct TreeNode {
70 pub name: String,
71 pub full_path: PathBuf,
72 pub is_file: bool,
73 pub status: FileStatus,
74 pub is_expanded: bool,
75 pub children: Vec<TreeNode>,
76 }
77
78 impl TreeNode {
79 pub fn new_directory(name: String, full_path: PathBuf) -> Self {
80 Self {
81 name,
82 full_path,
83 is_file: false,
84 status: FileStatus::new(),
85 is_expanded: true,
86 children: Vec::new(),
87 }
88 }
89
90 pub fn new_file(name: String, full_path: PathBuf, status: FileStatus) -> Self {
91 Self {
92 name,
93 full_path,
94 is_file: true,
95 status,
96 is_expanded: true, // Files are always "expanded"
97 children: Vec::new(),
98 }
99 }
100
101 pub fn root() -> Self {
102 Self {
103 name: ".".to_string(),
104 full_path: PathBuf::from("."),
105 is_file: false,
106 status: FileStatus::new(),
107 is_expanded: true,
108 children: Vec::new(),
109 }
110 }
111
112 /// Toggle expanded state (only meaningful for directories)
113 pub fn toggle_expanded(&mut self) {
114 if !self.is_file {
115 self.is_expanded = !self.is_expanded;
116 }
117 }
118
119 /// Sort children alphabetically
120 pub fn sort_children(&mut self) {
121 self.children
122 .sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
123 for child in &mut self.children {
124 child.sort_children();
125 }
126 }
127 }
128
129 /// A flattened item for display and navigation
130 #[derive(Debug, Clone)]
131 pub struct SelectableItem {
132 pub path: PathBuf,
133 pub name: String,
134 pub is_file: bool,
135 pub status: FileStatus,
136 pub depth: usize,
137 pub is_expanded: bool,
138 pub is_last_sibling: bool,
139 /// Track ancestor "is_last" states for drawing tree lines
140 pub ancestors_are_last: Vec<bool>,
141 }
142
143 impl SelectableItem {
144 pub fn from_node(node: &TreeNode, depth: usize, is_last: bool, ancestors: Vec<bool>) -> Self {
145 Self {
146 path: node.full_path.clone(),
147 name: node.name.clone(),
148 is_file: node.is_file,
149 status: node.status.clone(),
150 depth,
151 is_expanded: node.is_expanded,
152 is_last_sibling: is_last,
153 ancestors_are_last: ancestors,
154 }
155 }
156 }
157
158 /// Application mode
159 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
160 pub enum AppMode {
161 Normal,
162 Git,
163 }
164
165 impl AppMode {
166 pub fn toggle(&mut self) {
167 *self = match self {
168 AppMode::Normal => AppMode::Git,
169 AppMode::Git => AppMode::Normal,
170 };
171 }
172 }
173
174 /// Status of commit operation
175 #[derive(Debug, Clone, PartialEq, Eq)]
176 pub enum CommitStatus {
177 Editing,
178 Committing,
179 Success,
180 Failed,
181 }
182
183 /// Input mode for special states
184 #[derive(Debug, Clone, PartialEq, Eq)]
185 pub enum InputMode {
186 Navigation,
187 Rename { buffer: String, cursor: usize },
188 Search { buffer: String },
189 Commit { buffer: String, cursor: usize, amend: bool, status: CommitStatus },
190 Confirm { message: String, action: ConfirmAction },
191 }
192
193 #[derive(Debug, Clone, PartialEq, Eq)]
194 pub enum ConfirmAction {
195 Delete(PathBuf),
196 Discard(PathBuf),
197 StageAll,
198 UnstageAll,
199 }
200