1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct Fold {
10 pub start_row: usize,
12 pub end_row: usize,
14 pub closed: bool,
16}
17
18impl Fold {
19 pub fn contains(&self, row: usize) -> bool {
20 row >= self.start_row && row <= self.end_row
21 }
22
23 pub fn hides(&self, row: usize) -> bool {
26 self.closed && row > self.start_row && row <= self.end_row
27 }
28
29 pub fn line_count(&self) -> usize {
31 self.end_row.saturating_sub(self.start_row) + 1
32 }
33}
34
35impl crate::Buffer {
36 pub fn folds(&self) -> &[Fold] {
37 &self.folds
38 }
39
40 pub fn add_fold(&mut self, start_row: usize, end_row: usize, closed: bool) {
44 if end_row < start_row {
45 return;
46 }
47 let last = self.row_count().saturating_sub(1);
48 if start_row > last {
49 return;
50 }
51 let end_row = end_row.min(last);
52 let fold = Fold {
53 start_row,
54 end_row,
55 closed,
56 };
57 if let Some(idx) = self.folds.iter().position(|f| f.start_row == start_row) {
58 self.folds[idx] = fold;
59 } else {
60 let pos = self
61 .folds
62 .iter()
63 .position(|f| f.start_row > start_row)
64 .unwrap_or(self.folds.len());
65 self.folds.insert(pos, fold);
66 }
67 self.dirty_gen_bump();
68 }
69
70 pub fn remove_fold_at(&mut self, row: usize) -> bool {
73 let Some(idx) = self.folds.iter().position(|f| f.contains(row)) else {
74 return false;
75 };
76 self.folds.remove(idx);
77 self.dirty_gen_bump();
78 true
79 }
80
81 pub fn open_fold_at(&mut self, row: usize) -> bool {
83 let Some(f) = self.folds.iter_mut().find(|f| f.contains(row)) else {
84 return false;
85 };
86 if !f.closed {
87 return false;
88 }
89 f.closed = false;
90 self.dirty_gen_bump();
91 true
92 }
93
94 pub fn close_fold_at(&mut self, row: usize) -> bool {
96 let Some(f) = self.folds.iter_mut().find(|f| f.contains(row)) else {
97 return false;
98 };
99 if f.closed {
100 return false;
101 }
102 f.closed = true;
103 self.dirty_gen_bump();
104 true
105 }
106
107 pub fn toggle_fold_at(&mut self, row: usize) -> bool {
109 let Some(f) = self.folds.iter_mut().find(|f| f.contains(row)) else {
110 return false;
111 };
112 f.closed = !f.closed;
113 self.dirty_gen_bump();
114 true
115 }
116
117 pub fn open_all_folds(&mut self) {
119 let mut changed = false;
120 for f in self.folds.iter_mut() {
121 if f.closed {
122 f.closed = false;
123 changed = true;
124 }
125 }
126 if changed {
127 self.dirty_gen_bump();
128 }
129 }
130
131 pub fn clear_all_folds(&mut self) {
133 if !self.folds.is_empty() {
134 self.folds.clear();
135 self.dirty_gen_bump();
136 }
137 }
138
139 pub fn close_all_folds(&mut self) {
141 let mut changed = false;
142 for f in self.folds.iter_mut() {
143 if !f.closed {
144 f.closed = true;
145 changed = true;
146 }
147 }
148 if changed {
149 self.dirty_gen_bump();
150 }
151 }
152
153 pub fn fold_at_row(&self, row: usize) -> Option<&Fold> {
157 self.folds.iter().find(|f| f.contains(row))
158 }
159
160 pub fn is_row_hidden(&self, row: usize) -> bool {
162 self.folds.iter().any(|f| f.hides(row))
163 }
164
165 pub fn next_visible_row(&self, row: usize) -> Option<usize> {
169 let last = self.row_count().saturating_sub(1);
170 if last == 0 && row == 0 {
171 return None;
172 }
173 let mut r = row.checked_add(1)?;
174 while r <= last && self.is_row_hidden(r) {
175 r += 1;
176 }
177 (r <= last).then_some(r)
178 }
179
180 pub fn prev_visible_row(&self, row: usize) -> Option<usize> {
183 let mut r = row.checked_sub(1)?;
184 while self.is_row_hidden(r) {
185 r = r.checked_sub(1)?;
186 }
187 Some(r)
188 }
189
190 pub fn invalidate_folds_in_range(&mut self, start_row: usize, end_row: usize) {
194 let before = self.folds.len();
195 self.folds
196 .retain(|f| f.end_row < start_row || f.start_row > end_row);
197 if self.folds.len() != before {
198 self.dirty_gen_bump();
199 }
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use crate::Buffer;
206
207 fn b() -> Buffer {
208 Buffer::from_str("a\nb\nc\nd\ne")
209 }
210
211 #[test]
212 fn add_keeps_folds_in_start_row_order() {
213 let mut buf = b();
214 buf.add_fold(2, 3, true);
215 buf.add_fold(0, 1, false);
216 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
217 assert_eq!(starts, vec![0, 2]);
218 }
219
220 #[test]
221 fn add_replaces_existing_with_same_start_row() {
222 let mut buf = b();
223 buf.add_fold(1, 2, true);
224 buf.add_fold(1, 4, false);
225 assert_eq!(buf.folds().len(), 1);
226 assert_eq!(buf.folds()[0].end_row, 4);
227 assert!(!buf.folds()[0].closed);
228 }
229
230 #[test]
231 fn add_clamps_end_row_to_buffer_bounds() {
232 let mut buf = b();
233 buf.add_fold(2, 99, true);
234 assert_eq!(buf.folds()[0].end_row, 4);
235 }
236
237 #[test]
238 fn add_rejects_inverted_range() {
239 let mut buf = b();
240 buf.add_fold(3, 1, true);
241 assert!(buf.folds().is_empty());
242 }
243
244 #[test]
245 fn toggle_flips_state() {
246 let mut buf = b();
247 buf.add_fold(1, 3, false);
248 assert!(!buf.folds()[0].closed);
249 assert!(buf.toggle_fold_at(2));
250 assert!(buf.folds()[0].closed);
251 assert!(buf.toggle_fold_at(2));
252 assert!(!buf.folds()[0].closed);
253 }
254
255 #[test]
256 fn is_row_hidden_excludes_start_row() {
257 let mut buf = b();
258 buf.add_fold(1, 3, true);
259 assert!(!buf.is_row_hidden(0));
260 assert!(!buf.is_row_hidden(1)); assert!(buf.is_row_hidden(2));
262 assert!(buf.is_row_hidden(3));
263 assert!(!buf.is_row_hidden(4));
264 }
265
266 #[test]
267 fn open_close_all_changes_every_fold() {
268 let mut buf = b();
269 buf.add_fold(0, 1, false);
270 buf.add_fold(2, 3, true);
271 buf.close_all_folds();
272 assert!(buf.folds().iter().all(|f| f.closed));
273 buf.open_all_folds();
274 assert!(buf.folds().iter().all(|f| !f.closed));
275 }
276
277 #[test]
278 fn invalidate_drops_overlapping_folds() {
279 let mut buf = b();
280 buf.add_fold(0, 1, true);
281 buf.add_fold(2, 3, true);
282 buf.add_fold(4, 4, true);
283 buf.invalidate_folds_in_range(2, 3);
284 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
285 assert_eq!(starts, vec![0, 4]);
286 }
287}